C#:
/*******************************************************
* Copyright (C) 2024 Adam Polk <user.adam.polk@gmail.com>
* AgileZones can not be copied and/or distributed without the express permission of Adam Polk.
*******************************************************/
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Oxide.Core.Plugins;
using Oxide.Core.Configuration;
using Newtonsoft.Json;
using System;
using Oxide.Core.Libraries.Covalence;
using static BaseCombatEntity;
using System.Runtime.InteropServices;
namespace Oxide.Plugins {
[Info("AgileZones", "Goblox", "1.1.14")]
[Description("Automatically Manage Zones around TCs, SupplyDrops, Tugboats, CargoShip, BradleyAPC, and HeliCrash")]
public class AgileZones : RustPlugin {
[PluginReference] Plugin ZoneManager, ZoneDomes, TruePVE, NextGenPVE;
#region Fields
private bool serverInitialized = false;
private ConfigData configData;
private DynamicConfigFile data;
private BasePlayer player;
private Dictionary<SupplyDrop, string> supplyDrops = new Dictionary<SupplyDrop, string>();
private Dictionary<CargoShip, string> cargoShips = new Dictionary<CargoShip, string>();
private Dictionary<BradleyAPC, string> BradleyAPCs = new Dictionary<BradleyAPC, string>();
private Dictionary<Tugboat, string> Tugboats = new Dictionary<Tugboat, string>();
DateTime heliCrashSiteThrottle = DateTime.Now;
private List<Vector3> heliCrashSiteCache = new List<Vector3>();
string[] tcMessages;
string[] tugboatMessages;
string[] supplyDropMessages;
string[] cargoShipMessages;
string[] heliCrashMessages;
string[] apcMessages;
string tcName;
string tugboatName;
string supplyDropName;
string apcName;
string cargoShipName;
string helicrashName;
#endregion
#region Hooks
private void OnServerInitialized() {
if (ZoneManager)
serverInitialized = true;
else {
PrintWarning("ZoneManager Must be Installed! AGILEZONES DISABLED!");
return;
}
if (!TruePVE && configData.UseTruePVE) {
PrintWarning("TruePVE Enabled but not Found! DISABLING TRUEPVE");
configData.UseTruePVE = false;
}
if (!NextGenPVE && configData.UseNextGenPVE) {
PrintWarning("NextGenPVE Enabled but not Found! DISABLING NextGenPVE");
configData.UseNextGenPVE = false;
}
if (!ZoneDomes && configData.UseZoneDomes) {
PrintWarning("ZoneDomes Enabled but not Found! DISABLING ZoneDomes");
configData.UseZoneDomes = false;
}
tcName = "ToolCupboard" + configData.ZoneTag;
tugboatName = "TugBoat" + configData.ZoneTag;
supplyDropName = "SupplyDrop" + configData.ZoneTag;
apcName = "BradleyAPC" + configData.ZoneTag;
cargoShipName = "CargoShip" + configData.ZoneTag;
helicrashName = "HeliCrash" + configData.ZoneTag;
tcMessages = new string[] { "name", tcName, "enter_message", configData.EnterMessage, "leave_message", configData.LeaveMessage, "radius", configData.TCZoneRadius.ToString() };
tugboatMessages = new string[] { "name", tugboatName, "enter_message", configData.EnterMessage, "leave_message", configData.LeaveMessage, "radius", configData.TugboatsZoneRadius.ToString() };
supplyDropMessages = new string[] { "name", supplyDropName, "enter_message", configData.EnterMessage, "leave_message", configData.LeaveMessage, "radius", configData.SupplyDropZoneRadius.ToString() };
apcMessages = new string[] { "name", apcName, "enter_message", configData.EnterMessage, "leave_message", configData.LeaveMessage, "radius", configData.ApcZoneRadius.ToString() };
cargoShipMessages = new string[] { "name", cargoShipName, "enter_message", configData.EnterMessage, "leave_message", configData.LeaveMessage, "radius", configData.CargoShipZoneRadius.ToString() };
heliCrashMessages = new string[] { "name", helicrashName, "enter_message", configData.EnterMessage, "leave_message", configData.LeaveMessage, "radius", configData.CargoShipZoneRadius.ToString() };
ReconcileZones();
}
private void OnEntityBuilt(Planner plan, GameObject go) {
if (!serverInitialized || configData.TCZoneRadius <= 0)
return;
if (go.name.Contains("deployable/tool cupboard")) {
Puts($"Adding Zone in {configData.AddZoneDelay} seconds");
timer.Once(configData.AddZoneDelay, () => AddZone(go, tcMessages, configData.TCRuleSet, (configData.UseZoneDomes && configData.EnableTCDomes), configData.TCZoneFlags));
SendReply(plan.GetOwnerPlayer(), $"A PVP zone will be created around this TC in {configData.AddZoneDelay / 60} Minutes.");
}
}
private void OnEntityKill(BaseNetworkable entity) {
if (!serverInitialized)
return;
if (entity.name.Contains("deployable/tool cupboard")) {
var foundZone = ZoneManager?.CallHook("GetZoneName", Vector3ToId(entity.transform.position));
if (foundZone != null && foundZone.ToString() == tcName) {
Puts($"Remove Zone in {configData.RemoveZoneDelay} seconds");
Vector3 cachedValue = entity.transform.position;
timer.Once(configData.RemoveZoneDelay, () => RemoveZone(Vector3ToId(cachedValue)));
}
}
}
private void OnEntitySpawned(CargoShip cargoShip) {
if (!serverInitialized || configData.CargoShipZoneRadius <= 0)
return;
HandleCargoShip(cargoShip);
}
private void OnEntityKill(CargoShip cargoShip) {
if (!serverInitialized)
return;
if (cargoShips.ContainsKey(cargoShip)) {
RemoveZone(cargoShips[cargoShip]);
cargoShips.Remove(cargoShip);
}
}
private void OnEntitySpawned(SupplyDrop drop) {
if (!serverInitialized || configData.SupplyDropZoneRadius <= 0)
return;
HandleSupplyDrop(drop);
}
private void OnEntityKill(SupplyDrop drop) {
if (!serverInitialized)
return;
if (supplyDrops.ContainsKey(drop)) {
RemoveZone(supplyDrops[drop]);
supplyDrops.Remove(drop);
}
}
private void OnEntitySpawned(BradleyAPC apc) {
if (!serverInitialized || configData.ApcZoneRadius <= 0)
return;
HandleApc(apc);
}
private void OnEntityKill(BradleyAPC apc) {
if (!serverInitialized)
return;
if (BradleyAPCs.ContainsKey(apc)) {
RemoveZone(BradleyAPCs[apc]);
BradleyAPCs.Remove(apc);
}
}
private void OnEntitySpawned(Tugboat tug) {
if (!serverInitialized || configData.TugboatsZoneRadius <= 0)
return;
HandleTugboat(tug);
}
private void OnEntityKill(Tugboat tug) {
if (!serverInitialized)
return;
if (Tugboats.ContainsKey(tug)) {
RemoveZone(Tugboats[tug]);
Tugboats.Remove(tug);
}
}
private void OnEntitySpawned(HelicopterDebris debris) {
if (!serverInitialized || configData.HeliCrashZoneRadius <= 0)
return;
if (DateTime.Now > heliCrashSiteThrottle) {
heliCrashSiteThrottle = DateTime.Now.AddSeconds(2);
string cachedId = debris.GetInstanceID().ToString();
timer.Once(2, () => AddZone(debris.gameObject, heliCrashMessages, configData.HeliRuleSet, (configData.UseZoneDomes && configData.EnableHeliCrashDomes), configData.HeliZoneFlags, cachedId));
timer.Once(1200, () => {
RemoveZone(cachedId);
});
}
}
private void Unload() {
if (!serverInitialized)
return;
foreach (var drop in supplyDrops) {
RemoveZone(drop.Value);
}
foreach (var ship in cargoShips) {
RemoveZone(ship.Value);
}
}
[ChatCommand("ReloadTCs")]
private void ReloadTCs(BasePlayer player, string command, string[] args) {
if (!player.IsAdmin) return;
Puts($"Reloading TCs");
ReconcileZones(true);
}
#endregion
#region functions
private void AddZone(GameObject go, string[] messages, string ruleSet, bool createDome, string[] flags = null, string id = null) {
if (ruleSet == string.Empty) {
ruleSet = "exclude";
}
id = id ?? Vector3ToId(go.transform.position);
if (go != null) {
Puts("Adding PVP Zone");
}
else {
Puts("Attempted to add zone but GameObject already destroyed");
return;
}
ZoneManager?.CallHook("CreateOrUpdateZone", id, messages, go.transform.position);
if (flags != null)
foreach (var flag in flags)
ZoneManager?.CallHook("AddFlag", id, flag);
if (configData.UseTruePVE) {
TruePVE?.CallHook("AddOrUpdateMapping", id, ruleSet);
}
else if (configData.UseNextGenPVE) {
NextGenPVE?.CallHook("AddOrUpdateMapping", id, ruleSet);
}
if (createDome) {
ZoneDomes?.CallHook("AddNewDome", player, id);
}
}
private void RemoveZone(string zoneId) {
Puts($"Removing PVP Zone");
ZoneManager?.CallHook("EraseZone", zoneId);
if (configData.UseTruePVE) {
TruePVE?.CallHook("RemoveMapping", zoneId);
}
else if (configData.UseNextGenPVE) {
NextGenPVE?.CallHook("RemoveMapping", zoneId);
}
if (configData.UseZoneDomes) {
ZoneDomes?.CallHook("RemoveExistingDome", player, zoneId);
}
}
private void HandleCargoShip(CargoShip cargoShip) {
if (cargoShip == null || cargoShip.IsDestroyed || configData.CargoShipZoneRadius <= 0)
return;
string id = cargoShip.GetInstanceID().ToString();
GameObject zone = CreateAgileZone(id, cargoShipMessages, configData.CargoRuleSet, configData.CargoZoneFlags, cargoShip, configData.EnableCargoShipDomes);
zone.transform.SetParent(cargoShip.transform);
zone.transform.localPosition = Vector3.zero;
cargoShips.Add(cargoShip, id);
}
private void HandleApc(BradleyAPC apc) {
if (apc == null || apc.IsDestroyed || configData.ApcZoneRadius <= 0)
return;
string id = apc.GetInstanceID().ToString();
GameObject zone = CreateAgileZone(id, apcMessages, configData.ApcRuleSet, configData.ApcZoneFlags, apc, configData.EnableBradleyAPCDomes);
zone.transform.SetParent(apc.transform);
zone.transform.localPosition = Vector3.zero;
BradleyAPCs.Add(apc, id);
}
private void HandleTugboat(Tugboat tug) {
if (tug == null || tug.IsDestroyed || configData.TugboatsZoneRadius <= 0)
return;
string id = tug.GetInstanceID().ToString();
GameObject zone = CreateAgileZone(id, tugboatMessages, configData.TugboatsRuleSet, configData.TugboatsZoneFlags, tug, configData.EnableTugboatsDomes);
zone.transform.SetParent(tug.transform);
zone.transform.localPosition = Vector3.zero;
Tugboats.Add(tug, id);
}
private void HandleSupplyDrop(SupplyDrop supplyDrop) {
if (supplyDrop == null || configData.SupplyDropZoneRadius <= 0)
return;
string id = $"SupplyDrop{supplyDrops.Count}";
GameObject zone = CreateAgileZone(id, supplyDropMessages, configData.SupplyDropRuleSet, configData.SupplyDropZoneFlags, supplyDrop, configData.EnableSupplyDropDomes);
zone.transform.SetParent(supplyDrop.transform);
zone.transform.localPosition = Vector3.zero;
supplyDrops.Add(supplyDrop, id);
}
private void ReconcileZones(bool deleteTcs = false) {
Puts("Begin Reconcile");
string[] ZoneManagerIds = ZoneManager?.CallHook("GetZoneIDs") as string[];
List<string> tcIds = new List<string>();
//filterZones
foreach (string zoneId in ZoneManagerIds) {
string zoneName = string.Empty;
try {
zoneName = ZoneManager.CallHook("GetZoneName", zoneId).ToString();
}
catch (Exception ex) {
continue;
}
if (zoneName.Contains("ToolCupboard")) {
if (!deleteTcs) {
tcIds.Add(zoneId);
continue;
}
else {
RemoveZone(zoneId);
continue;
}
}
if (zoneName == cargoShipName ||
zoneName == supplyDropName ||
zoneName == apcName ||
zoneName == helicrashName ||
zoneName == tugboatName) {
RemoveZone(zoneId);
continue;
}
continue;
}
ReconcileTCs(tcIds);
ReconcileCargoShips();
ReconcileSupplyDrops();
ReconcileApcs();
ReconcileHeliCrashes();
ReconcileTugs();
}
private void ReconcileHeliCrashes() {
Puts("Reload HeliCrashes");
List<HelicopterDebris> foundSites = new List<HelicopterDebris>();
foreach (HelicopterDebris debris in BaseNetworkable.serverEntities.OfType<HelicopterDebris>().ToList()) {
if (foundSites.Count == 0) {
foundSites.Add(debris);
continue;
}
bool found = false;
foreach (HelicopterDebris site in foundSites) {
if ((debris.transform.position - site.transform.position).magnitude < 30) {
found = true;
}
}
if (!found) {
foundSites.Add(debris);
}
}
foreach (HelicopterDebris site in foundSites) {
string cachedId = site.GetInstanceID().ToString();
AddZone(site.gameObject, heliCrashMessages, configData.HeliRuleSet, (configData.UseZoneDomes && configData.EnableHeliCrashDomes), configData.HeliZoneFlags, cachedId);
timer.Once(1200, () => {
RemoveZone(cachedId);
});
}
}
private void ReconcileSupplyDrops() {
Puts("Reload Supply Drops");
supplyDrops.Clear();
foreach (SupplyDrop drop in BaseNetworkable.serverEntities.OfType<SupplyDrop>().ToList()) {
HandleSupplyDrop(drop);
}
}
private void ReconcileTugs() {
Puts("Reload Tugboats");
Tugboats.Clear();
foreach (Tugboat tug in BaseNetworkable.serverEntities.OfType<Tugboat>().ToList()) {
HandleTugboat(tug);
}
}
private void ReconcileApcs() {
Puts("Reload Bradley APCs");
BradleyAPCs.Clear();
foreach (BradleyAPC apc in BaseNetworkable.serverEntities.OfType<BradleyAPC>().ToList()) {
HandleApc(apc);
}
}
private void ReconcileCargoShips() {
Puts("Reload Cargo Ships");
cargoShips.Clear();
foreach (CargoShip ship in BaseNetworkable.serverEntities.OfType<CargoShip>().ToList()) {
HandleCargoShip(ship);
}
}
private void ReconcileTCs(List<string> ZoneManagerIds) {
Puts("Reconcile TCs");
List<Vector3> ZoneManagerZones = ZoneManagerIds.Select(x => IdToVector3(x)).ToList();
int orphanedZoneCount = 0;
int missingZoneCount = 0;
List<Vector3> Toolcupboards = new List<Vector3>();
if (configData.TCZoneRadius > 0) {
foreach (BuildingPrivlidge buildingPrivlidge in BaseNetworkable.serverEntities.OfType<BuildingPrivlidge>()) {
if (!ZoneManagerZones.Contains(RoundVector3(buildingPrivlidge.Transform.position))) {
AddZone(buildingPrivlidge.gameObject, tcMessages, configData.TCRuleSet, (configData.UseZoneDomes && configData.EnableTCDomes), configData.TCZoneFlags);
missingZoneCount++;
}
Toolcupboards.Add(RoundVector3(buildingPrivlidge.Transform.position));
}
}
IEnumerable<Vector3> OrphanedZones = ZoneManagerZones.Except(Toolcupboards);
foreach (Vector3 zone in OrphanedZones) {
RemoveZone(Vector3ToId(zone));
orphanedZoneCount++;
}
if (orphanedZoneCount > 0 || missingZoneCount > 0) {
Puts($"OrphanedTCs: {orphanedZoneCount}, MissingTCZones {missingZoneCount}");
}
}
private GameObject CreateAgileZone(string id, string[] messages, string ruleSet, string[] zoneFlags, BaseEntity parent, bool CreateDome) {
Puts($"Add {messages[1]} Zone");
if (ruleSet == string.Empty) {
ruleSet = "exclude";
}
GameObject output = null;
ZoneManager?.CallHook("CreateOrUpdateZone", id, messages, new Vector3(0, -1000, 0));
if (zoneFlags != null)
foreach (var flag in zoneFlags)
ZoneManager?.CallHook("AddFlag", id, flag);
Ray ray = new Ray(new Vector3(0, -1000, -1000), Vector3.forward);
RaycastHit[] hits = Physics.RaycastAll(ray, 1100);
bool success = false;
if (hits.Length > 0) {
foreach (RaycastHit hit in hits) {
//ZoneManager currently doesn't just give out gameObjects. so... we're going to back into them.
if (hit.transform.gameObject.name != "ZoneManager" || success) {
//We hit something else.
continue;
}
//Looks like we found it, do the rest of the Zone setup.
success = true;
output = hit.transform.gameObject;
if (configData.UseTruePVE) {
TruePVE?.CallHook("AddOrUpdateMapping", id, ruleSet);
}
else if (configData.UseNextGenPVE) {
NextGenPVE?.CallHook("AddOrUpdateMapping", id, ruleSet);
}
if (configData.UseZoneDomes && CreateDome) {
for (int i = 0; i < configData.ZoneDomesSphereDarkness; i++) { //ZoneDomes doesn't currently support moving objects. ...let's use the same sphere.prefeab.
BaseEntity sphereBase = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", parent.transform.position, new Quaternion(), true);
SphereEntity sphereEnt = sphereBase.GetComponent<SphereEntity>();
sphereEnt.currentRadius = float.Parse(messages[7]) * 2;
sphereEnt.lerpSpeed = 0f;
sphereBase.SetParent(parent);
sphereBase.transform.localPosition = Vector3.zero;
sphereBase.Spawn();
}
}
}
}
if (!success) {
Puts("Critical Error: ZoneManager Object Not Found");
//Looks like we didn't find it. This shouldn't happen.
return null;
}
return output;
}
#endregion
#region utility
private Vector3 RoundVector3(Vector3 input) {
return new Vector3(float.Parse(input.x.ToString("0.0")), float.Parse(input.y.ToString("0.0")), float.Parse(input.z.ToString("0.0")));
}
private string Vector3ToId(Vector3 input) {
Vector3 rounded = RoundVector3(input);
return $"{rounded.x},{rounded.y},{rounded.z}";
}
private Vector3 IdToVector3(string id) {
string[] vs = id.Split(',');
if (vs.Length != 3)
return Vector3.zero;
return new Vector3(float.Parse(vs[0]), float.Parse(vs[1]), float.Parse(vs[2]));
}
#endregion
#region config
protected override void LoadConfig() {
base.LoadConfig();
configData = Config.ReadObject<ConfigData>();
}
protected override void SaveConfig() => Config.WriteObject(configData, true);
protected override void LoadDefaultConfig() => configData = GetBaseConfig();
private ConfigData GetBaseConfig() {
return new ConfigData
{
UseTruePVE = true,
UseZoneDomes = true,
EnableTCDomes = false,
EnableSupplyDropDomes = true,
EnableCargoShipDomes = true,
EnableHeliCrashDomes = true,
EnableBradleyAPCDomes = true,
EnableTugboatsDomes = false,
AddZoneDelay = 600,
RemoveZoneDelay = 900,
TCZoneRadius = 50,
TCRuleSet = "exclude",
CargoShipZoneRadius = 125,
CargoRuleSet = "exclude",
SupplyDropZoneRadius = 50,
SupplyDropRuleSet = "exclude",
ApcZoneRadius = 50,
ApcRuleSet = "exclude",
HeliCrashZoneRadius = 50,
HeliRuleSet = "exclude",
TugboatsZoneRadius = 50,
TugboatsRuleSet = "exclude",
ZoneDomesSphereDarkness = 1,
EnterMessage = "WARNING: You are now entering a PVP Zone",
LeaveMessage = "Leaving a PVP Zone"
};
}
class ConfigData {
[JsonProperty(PropertyName = "Enable TruePVE")]
public bool UseTruePVE { get; set; }
[JsonProperty(PropertyName = "Enable NextGenPVE")]
public bool UseNextGenPVE { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes")]
public bool UseZoneDomes { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes For TCs")]
public bool EnableTCDomes { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes For SupplyDrops")]
public bool EnableSupplyDropDomes { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes For Cargo")]
public bool EnableCargoShipDomes { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes For HeliCrash")]
public bool EnableHeliCrashDomes { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes For BradleyAPC")]
public bool EnableBradleyAPCDomes { get; set; }
[JsonProperty(PropertyName = "Enable ZoneDomes For Tugboats")]
public bool EnableTugboatsDomes { get; set; }
[JsonProperty(PropertyName = "Visible Domes for Moving Zones Darkness (Default 1)")]
public int ZoneDomesSphereDarkness { get; set; }
[JsonProperty(PropertyName = "Delay creating a zone after placing a TC in seconds")]
public float AddZoneDelay { get; set; }
[JsonProperty(PropertyName = "Delay removing a zone after a TC is destroyed")]
public float RemoveZoneDelay { get; set; }
[JsonProperty(PropertyName = "TC Zone Radius in meters - set zero to disable")]
public int TCZoneRadius { get; set; }
[JsonProperty(PropertyName = "TC RuleSet")]
public string TCRuleSet { get; set; }
[JsonProperty(PropertyName = "TC Zone Flags")]
public string[] TCZoneFlags { get; set; }
[JsonProperty(PropertyName = "CargoShip Zone Radius in meters - set zero to disable")]
public int CargoShipZoneRadius { get; set; }
[JsonProperty(PropertyName = "Cargo RuleSet")]
public string CargoRuleSet { get; set; }
[JsonProperty(PropertyName = "Cargo Zone Flags")]
public string[] CargoZoneFlags { get; set; }
[JsonProperty(PropertyName = "SupplyDrop Zone Radius in meters - set zero to disable")]
public int SupplyDropZoneRadius { get; set; }
[JsonProperty(PropertyName = "SupplyDrop RuleSet")]
public string SupplyDropRuleSet { get; set; }
[JsonProperty(PropertyName = "Supply Drop Zone Flags")]
public string[] SupplyDropZoneFlags { get; set; }
[JsonProperty(PropertyName = "Apc Zone Radius in meters - set zero to disable")]
public int ApcZoneRadius { get; set; }
[JsonProperty(PropertyName = "Apc RuleSet")]
public string ApcRuleSet { get; set; }
[JsonProperty(PropertyName = "Apc Zone Flags")]
public string[] ApcZoneFlags { get; set; }
[JsonProperty(PropertyName = "HeliCrash Zone Radius in meters - set zero to disable")]
public int HeliCrashZoneRadius { get; set; }
[JsonProperty(PropertyName = "Heli RuleSet")]
public string HeliRuleSet { get; set; }
[JsonProperty(PropertyName = "Heli Zone Flags")]
public string[] HeliZoneFlags { get; set; }
[JsonProperty(PropertyName = "Tugboats Zone Radius in meters - set zero to disable")]
public int TugboatsZoneRadius { get; set; }
[JsonProperty(PropertyName = "Tugboats RuleSet")]
public string TugboatsRuleSet { get; set; }
[JsonProperty(PropertyName = "Tugboats Zone Flags")]
public string[] TugboatsZoneFlags { get; set; }
[JsonProperty(PropertyName = "Entering Zone Message")]
public string EnterMessage { get; set; }
[JsonProperty(PropertyName = "Leaving Zone Message")]
public string LeaveMessage { get; set; }
[JsonProperty(PropertyName = "Zone Tag (Adds this string to zone names)")]
public string ZoneTag { get; set; }
}
#endregion
}
}