C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VLB;
namespace Oxide.Plugins
{
[Info("Drone Turrets", "WhiteThunder", "1.4.3")]
[Description("Allows players to deploy auto turrets to RC drones.")]
internal class DroneTurrets : CovalencePlugin
{
#region Fields
[PluginReference]
private readonly Plugin DroneSettings;
private Configuration _config;
private const float TurretScale = 0.6f;
private const string PermissionDeploy = "droneturrets.deploy";
private const string PermissionDeployNpc = "droneturrets.deploynpc";
private const string PermissionDeployFree = "droneturrets.deploy.free";
private const string PermissionAutoDeploy = "droneturrets.autodeploy";
private const string PermissionControl = "droneturrets.control";
private const string AutoTurretPrefab = "assets/prefabs/npc/autoturret/autoturret_deployed.prefab";
private const string NpcAutoTurretPrefab = "assets/content/props/sentry_scientists/sentry.bandit.static.prefab";
private const string ElectricSwitchPrefab = "assets/prefabs/deployable/playerioents/simpleswitch/switch.prefab";
// private const string AlarmPrefab = "assets/prefabs/io/electric/other/alarmsound.prefab";
private const string AlarmPrefab = "assets/prefabs/deployable/playerioents/alarms/audioalarm.prefab";
private const string SirenLightPrefab = "assets/prefabs/io/electric/lights/sirenlightorange.prefab";
private const string DeployEffectPrefab = "assets/prefabs/npc/autoturret/effects/autoturret-deploy.prefab";
private const string CodeLockDeniedEffectPrefab = "assets/prefabs/locks/keypad/effects/lock.code.denied.prefab";
private const int AutoTurretItemId = -2139580305;
private const BaseEntity.Slot TurretSlot = BaseEntity.Slot.UpperModifier;
private static readonly Vector3 TurretLocalPosition = new(0, -0.14f, 0);
private static readonly Vector3 TurretSwitchPosition = new(0, 0.36f, -0.32f);
private static readonly Quaternion TurretSwitchRotation = Quaternion.Euler(0, 180, 0);
private static readonly Vector3 TurretTransformScale = Vector3.one * TurretScale;
private static readonly Vector3 TurretTransformInvertedScale = Vector3.one / TurretScale;
private readonly object True = true;
private readonly object False = false;
private DynamicHookSubscriber<NetworkableId> _turretDroneTracker;
#endregion
#region Hooks
private void Init()
{
permission.RegisterPermission(PermissionDeploy, this);
permission.RegisterPermission(PermissionDeployNpc, this);
permission.RegisterPermission(PermissionDeployFree, this);
permission.RegisterPermission(PermissionAutoDeploy, this);
permission.RegisterPermission(PermissionControl, this);
var dynamicHookNames = new List<string>
{
nameof(OnServerCommand),
nameof(OnSwitchToggle),
nameof(OnSwitchToggled),
nameof(OnTurretTarget),
nameof(OnEntityTakeDamage),
nameof(OnEntityKill),
nameof(OnEntityDeath),
nameof(CanPickupEntity),
nameof(OnBookmarkControlStarted),
nameof(OnBookmarkControlEnded),
nameof(canRemove),
};
_turretDroneTracker = new DynamicHookSubscriber<NetworkableId>(this, dynamicHookNames.ToArray());
_turretDroneTracker.UnsubscribeAll();
}
private void OnServerInitialized()
{
foreach (var entity in BaseNetworkable.serverEntities)
{
var drone = entity as Drone;
if (drone == null || !RCUtils.IsRCDrone(drone))
continue;
var turret = GetDroneTurret(drone);
if (turret == null)
continue;
RefreshDroneTurret(drone, turret);
}
foreach (var player in BasePlayer.activePlayerList)
{
var turret = RCUtils.GetControlledEntity<AutoTurret>(player, out var station);
if (turret == null)
continue;
OnBookmarkControlStarted(station, player, turret.GetIdentifier(), turret);
}
}
private void Unload()
{
DroneController.DestroyAll();
foreach (var entity in BaseNetworkable.serverEntities)
{
var drone = entity as Drone;
if (drone == null || !RCUtils.IsRCDrone(drone))
continue;
var turret = GetDroneTurret(drone);
if (turret == null)
continue;
turret.inventory.onItemAddedRemoved -= HandleTurretInventoryItemAddedRemoved;
}
}
private object OnServerCommand(ConsoleSystem.Arg arg)
{
if (arg.Connection == null)
return null;
var player = arg.Player();
if (player == null)
return null;
if (arg.cmd.FullName == "vehicle.swapseats")
{
HandleSwapSeats(player);
return null;
}
if (arg.cmd.FullName == "inventory.lighttoggle")
return HandleLightToggle(player);
return null;
}
private void OnEntityBuilt(Planner planner, GameObject go)
{
if (planner == null || go == null)
return;
var drone = go.ToBaseEntity() as Drone;
if (drone == null)
return;
var player = planner.GetOwnerPlayer();
if (player == null)
return;
var drone2 = drone;
NextTick(() =>
{
// Delay this check to allow time for other plugins to deploy an entity to this slot.
if (drone2 == null || player == null || drone2.GetSlot(TurretSlot) != null)
return;
if (permission.UserHasPermission(player.UserIDString, PermissionAutoDeploy))
{
DeployAutoTurret(drone2, player, 1);
}
else if (permission.UserHasPermission(player.UserIDString, PermissionDeploy)
&& UnityEngine.Random.Range(0, 100) < _config.TipChance)
{
ChatMessage(player, Lang.TipDeployCommand);
}
});
}
private object OnSwitchToggle(ElectricSwitch electricSwitch, BasePlayer player)
{
var turret = GetParentTurret(electricSwitch);
if (turret == null)
return null;
var drone = GetParentDrone(turret);
if (drone == null)
return null;
if (!player.CanBuild())
{
// Disallow switching the turret on and off while building blocked.
Effect.server.Run(CodeLockDeniedEffectPrefab, electricSwitch, 0, Vector3.zero, Vector3.forward);
return False;
}
return null;
}
private void OnSwitchToggled(ElectricSwitch electricSwitch)
{
var turret = GetParentTurret(electricSwitch);
if (turret == null)
return;
var drone = GetParentDrone(turret);
if (drone == null)
return;
if (electricSwitch.IsOn())
{
turret.InitiateStartup();
}
else
{
turret.InitiateShutdown();
}
RefreshAlarmState(drone, turret);
}
private object OnTurretTarget(AutoTurret turret, BaseCombatEntity target)
{
if (turret == null || target == null || GetParentDrone(turret) == null)
return null;
if (target is Rust.Ai.Gen2.Chicken or FarmableAnimal)
return False;
if (!_config.TargetAnimals && target is BaseAnimalNPC)
return False;
var basePlayer = target as BasePlayer;
if (basePlayer != null)
{
if (!_config.TargetNPCs && basePlayer.IsNpc)
return False;
if (!_config.TargetPlayers && basePlayer.userID.IsSteamId())
return False;
// Don't target human or NPC players in safe zones, unless they are hostile.
if (basePlayer.InSafeZone() && (basePlayer.IsNpc || !basePlayer.IsHostile()))
return False;
return null;
}
return null;
}
// Redirect damage from the turret to the drone.
private object OnEntityTakeDamage(AutoTurret turret, HitInfo info)
{
var drone = GetParentDrone(turret);
if (drone == null)
return null;
drone.Hurt(info);
return True;
}
// Redirect damage from the turret switch to the drone.
private object OnEntityTakeDamage(ElectricSwitch electricSwitch, HitInfo info)
{
var autoTurret = GetParentTurret(electricSwitch);
if (autoTurret == null)
return null;
var drone = GetParentDrone(autoTurret);
if (drone == null)
return null;
drone.Hurt(info);
return True;
}
private void OnEntityKill(Drone drone)
{
if (GetDroneTurret(drone))
return;
_turretDroneTracker.Remove(drone.net.ID);
}
private void OnEntityKill(AutoTurret turret)
{
var drone = GetParentDrone(turret);
if (drone == null)
return;
_turretDroneTracker.Remove(drone.net.ID);
var drone2 = drone;
drone.Invoke(() => RefreshDroneSettingsProfile(drone2), 0);
}
private void OnEntityDeath(Drone drone)
{
if (!RCUtils.IsRCDrone(drone))
return;
var turret = GetDroneTurret(drone);
if (turret != null)
{
// Causing the turret to die allows its inventory to potentially be dropped.
// This approach is intentionally used, as opposed to dropping the inventory
// directly, in order to respect vanilla behavior around `turret.dropChance`, and
// to allow other plugins to intercept the OnEntityDeath(AutoTurret) hook to kill
// the inventory before vanilla logic drops it.
turret.Die();
}
}
private object CanPickupEntity(BasePlayer player, Drone drone)
{
if (CanPickupInternal(drone))
return null;
ChatMessage(player, Lang.ErrorCannotPickupWithTurret);
return False;
}
private void OnBookmarkControlStarted(ComputerStation station, BasePlayer player, string bookmarkName, Drone drone)
{
var turret = GetDroneTurret(drone);
if (turret == null)
return;
RefreshAlarmState(drone, turret);
}
private void OnBookmarkControlStarted(ComputerStation station, BasePlayer player, string bookmarkName, AutoTurret turret)
{
var drone = GetParentDrone(turret);
if (drone == null)
return;
if (!RCUtils.HasController(turret, player))
return;
if (!HasPermissionToControl(player))
{
RCUtils.RemoveController(turret);
RCUtils.AddFakeViewer(turret);
RCUtils.AddViewer(turret, player);
RCUtils.RemoveController(turret);
using var flagsScope = station.StartSetFlags(BaseEntity.FlagsUpdateMode.SendNetworkUpdate_Flags);
flagsScope.Set(ComputerStation.Flag_HasFullControl, false);
return;
}
if (RCUtils.CanControl(player, drone))
{
RCUtils.RemoveController(drone);
RCUtils.AddViewer(drone, player);
DroneController.AddToDrone(drone, turret, player);
ExposedHooks.OnBookmarkControlStarted(station, player, drone.GetIdentifier(), drone);
}
}
private void OnBookmarkControlEnded(ComputerStation station, BasePlayer player, Drone drone)
{
if (drone == null)
return;
var turret = GetDroneTurret(drone);
if (turret != null)
{
var drone2 = drone;
var turret2 = turret;
// Delay in case the drone is hovering.
NextTick(() =>
{
if (drone2 == null || turret2 == null)
return;
RefreshAlarmState(drone2, turret2);
});
}
}
private void OnBookmarkControlEnded(ComputerStation station, BasePlayer player, AutoTurret turret)
{
if (turret == null)
return;
var drone = GetParentDrone(turret);
if (drone == null)
return;
if (!RCUtils.HasController(drone, player))
return;
RCUtils.RemoveController(drone);
// Notify other plugins such as Drone Hover, Limited Drone Range and Limited Drone Height.
ExposedHooks.OnBookmarkControlEnded(station, player, drone);
}
// This hook is exposed by plugin: Remover Tool (RemoverTool).
private string canRemove(BasePlayer player, Drone drone)
{
if (CanPickupInternal(drone))
return null;
return GetMessage(player, Lang.ErrorCannotPickupWithTurret);
}
// This hook is exposed by plugin: Drone Settings (DroneSettings).
private string OnDroneTypeDetermine(Drone drone)
{
return GetDroneTurret(drone) != null ? Name : null;
}
#endregion
#region API
[HookMethod(nameof(API_DeployAutoTurret))]
public AutoTurret API_DeployAutoTurret(Drone drone, BasePlayer player)
{
if (GetDroneTurret(drone) != null
|| drone.GetSlot(TurretSlot) != null
|| DeployTurretWasBlocked(drone, player))
return null;
return DeployAutoTurret(drone, player);
}
[HookMethod(nameof(API_DeployNpcAutoTurret))]
public NPCAutoTurret API_DeployNpcAutoTurret(Drone drone, BasePlayer player)
{
if (GetDroneTurret(drone) != null
|| drone.GetSlot(TurretSlot) != null
|| DeployNpcTurretWasBlocked(drone))
return null;
return DeployNpcAutoTurret(drone, player);
}
#endregion
#region Exposed Hooks
private static class ExposedHooks
{
// Oxide hook.
public static void OnBookmarkControlStarted(ComputerStation station, BasePlayer player, string name, IRemoteControllable controllable)
{
Interface.CallHook("OnBookmarkControlStarted", station, player, name, controllable);
}
// Oxide hook.
public static void OnBookmarkControlEnded(ComputerStation station, BasePlayer player, IRemoteControllable controllable)
{
Interface.CallHook("OnBookmarkControlEnded", station, player, controllable);
}
// Oxide hook.
public static void OnEntityBuilt(BaseEntity heldEntity, GameObject gameObject)
{
Interface.CallHook("OnEntityBuilt", heldEntity, gameObject);
}
public static object OnDroneTurretDeploy(Drone drone, BasePlayer deployer)
{
return Interface.CallHook("OnDroneTurretDeploy", drone, deployer);
}
public static void OnDroneTurretDeployed(Drone drone, AutoTurret turret, BasePlayer deployer)
{
Interface.CallHook("OnDroneTurretDeployed", drone, turret, deployer);
}
public static object OnDroneNpcTurretDeploy(Drone drone, BasePlayer deployer)
{
return Interface.CallHook("OnDroneNpcTurretDeploy", drone, deployer);
}
public static void OnDroneNpcTurretDeployed(Drone drone, NPCAutoTurret turret, BasePlayer deployer)
{
Interface.CallHook("OnDroneNpcTurretDeployed", drone, turret, deployer);
}
}
#endregion
#region Commands
[Command("droneturret")]
private void DroneTurretCommand(IPlayer player)
{
if (player.IsServer)
return;
var basePlayer = player.Object as BasePlayer;
if (!basePlayer.CanInteract())
return;
if (!VerifyPermission(player, PermissionDeploy)
|| !VerifyDroneFound(player, out var drone)
|| !VerifyCanBuild(player, drone)
|| !VerifyDroneHasNoTurret(player, drone)
|| !VerifyDroneHasSlotVacant(player, drone))
return;
Item autoTurretPaymentItem = null;
var conditionFraction = 1f;
if (!player.HasPermission(PermissionDeployFree))
{
autoTurretPaymentItem = FindPlayerAutoTurretItem(basePlayer);
if (autoTurretPaymentItem == null)
{
ReplyToPlayer(player, Lang.ErrorNoTurretItem);
return;
}
conditionFraction = GetItemConditionFraction(autoTurretPaymentItem);
}
if (DeployTurretWasBlocked(drone, basePlayer))
return;
if (DeployAutoTurret(drone, basePlayer, conditionFraction) == null)
{
ReplyToPlayer(player, Lang.ErrorDeployFailed);
return;
}
if (autoTurretPaymentItem != null)
{
UseItem(basePlayer, autoTurretPaymentItem);
}
}
[Command("dronenpcturret")]
private void DroneNpcTurretCommand(IPlayer player, string cmd, string[] args)
{
if (player.IsServer)
return;
var basePlayer = player.Object as BasePlayer;
if (!basePlayer.CanInteract())
return;
if (!VerifyPermission(player, PermissionDeployNpc)
|| !VerifyDroneFound(player, out var drone)
|| !VerifyCanBuild(player, drone)
|| !VerifyDroneHasNoTurret(player, drone)
|| !VerifyDroneHasSlotVacant(player, drone)
|| DeployNpcTurretWasBlocked(drone, basePlayer))
return;
if (DeployNpcAutoTurret(drone, basePlayer) == null)
{
ReplyToPlayer(player, Lang.ErrorDeployFailed);
}
}
#endregion
#region Helper Methods - Command Checks
private bool VerifyPermission(IPlayer player, string perm)
{
if (player.HasPermission(perm))
return true;
ReplyToPlayer(player, Lang.ErrorNoPermission);
return false;
}
private bool VerifyDroneFound(IPlayer player, out Drone drone)
{
var basePlayer = player.Object as BasePlayer;
drone = GetLookEntity(basePlayer, 3) as Drone;
if (drone != null && RCUtils.IsRCDrone(drone))
return true;
ReplyToPlayer(player, Lang.ErrorNoDroneFound);
return false;
}
private bool VerifyCanBuild(IPlayer player, Drone drone)
{
var basePlayer = player.Object as BasePlayer;
if (basePlayer.CanBuild() && basePlayer.CanBuild(drone.WorldSpaceBounds()))
return true;
ReplyToPlayer(player, Lang.ErrorBuildingBlocked);
return false;
}
private bool VerifyDroneHasNoTurret(IPlayer player, Drone drone)
{
if (GetDroneTurret(drone) == null)
return true;
ReplyToPlayer(player, Lang.ErrorAlreadyHasTurret);
return false;
}
private bool VerifyDroneHasSlotVacant(IPlayer player, Drone drone)
{
if (drone.GetSlot(TurretSlot) == null)
return true;
ReplyToPlayer(player, Lang.ErrorIncompatibleAttachment);
return false;
}
#endregion
#region Helpers
private static class RCUtils
{
public static bool IsRCDrone(Drone drone)
{
return drone is not DeliveryDrone;
}
public static bool HasController(IRemoteControllable controllable)
{
return controllable.ControllingViewerId.HasValue;
}
public static bool HasController(IRemoteControllable controllable, BasePlayer player)
{
return controllable.ControllingViewerId?.SteamId == player.userID;
}
public static bool HasFakeController(IRemoteControllable controllable)
{
return controllable.ControllingViewerId?.SteamId == 0;
}
public static bool HasRealController(IRemoteControllable controllable)
{
return controllable.ControllingViewerId.GetValueOrDefault().SteamId != 0;
}
public static bool CanControl(BasePlayer player, IRemoteControllable controllable)
{
return !HasRealController(controllable) || HasController(controllable, player);
}
public static void RemoveController(IRemoteControllable controllable)
{
var controllerId = controllable.ControllingViewerId;
if (controllerId.HasValue)
{
controllable.StopControl(controllerId.Value);
}
}
public static bool AddViewer(IRemoteControllable controllable, BasePlayer player)
{
return controllable.InitializeControl(new CameraViewerId(player.userID, 0));
}
public static void RemoveViewer(IRemoteControllable controllable, BasePlayer player)
{
controllable.StopControl(new CameraViewerId(player.userID, 0));
}
public static bool AddFakeViewer(IRemoteControllable controllable)
{
return controllable.InitializeControl(new CameraViewerId());
}
public static T GetControlledEntity<T>(BasePlayer player, out ComputerStation station) where T : class
{
station = player.GetMounted() as ComputerStation;
if ((object)station == null)
return null;
return station.currentlyControllingEnt.Get(serverside: true) as T;
}
public static T GetControlledEntity<T>(BasePlayer player) where T : class
{
return GetControlledEntity<T>(player, out _);
}
}
private static bool DeployTurretWasBlocked(Drone drone, BasePlayer deployer)
{
var hookResult = ExposedHooks.OnDroneTurretDeploy(drone, deployer);
return hookResult is false;
}
private static bool DeployNpcTurretWasBlocked(Drone drone, BasePlayer deployer = null)
{
var hookResult = ExposedHooks.OnDroneNpcTurretDeploy(drone, deployer);
return hookResult is false;
}
private static Drone GetParentDrone(BaseEntity entity)
{
return entity.GetParentEntity() as Drone;
}
private static AutoTurret GetDroneTurret(Drone drone)
{
return drone.GetSlot(TurretSlot) as AutoTurret;
}
private static T GetChildOfType<T>(BaseEntity entity, string prefabName = null) where T : BaseEntity
{
foreach (var child in entity.children)
{
var childOfType = child as T;
if (childOfType != null && (prefabName == null || child.PrefabName == prefabName))
return childOfType;
}
return null;
}
private static IOEntity GetTurretAlarm(AutoTurret turret)
{
return GetChildOfType<IOEntity>(turret, AlarmPrefab);
}
private static IOEntity GetTurretLight(AutoTurret turret)
{
return GetChildOfType<IOEntity>(turret, SirenLightPrefab);
}
private static bool ShouldPowerAlarm(Drone drone, AutoTurret turret)
{
return drone.IsBeingControlled && (turret.booting || turret.IsOn());
}
private static bool CanPickupInternal(Drone drone)
{
if (!RCUtils.IsRCDrone(drone))
return true;
var turret = GetDroneTurret(drone);
if (turret == null)
return true;
// Prevent drone pickup while it has a turret (the turret must be removed first).
// Ignores NPC turrets since they can't be picked up.
return turret == null || turret is NPCAutoTurret;
}
private static void RemoveProblemComponents(BaseEntity entity)
{
foreach (var collider in entity.GetComponentsInChildren<Collider>())
{
if (!collider.isTrigger)
{
UnityEngine.Object.DestroyImmediate(collider);
}
}
UnityEngine.Object.DestroyImmediate(entity.GetComponent<DestroyOnGroundMissing>());
UnityEngine.Object.DestroyImmediate(entity.GetComponent<GroundWatch>());
}
private static void AddRigidBodyToTriggerCollider(AutoTurret turret)
{
// Without this hack, the drone's sweep test can collide with other entities using the
// turret trigger collider, causing the drone to occasionally reduce altitude like when
// it's close to the ground.
turret.targetTrigger.GetOrAddComponent<Rigidbody>().isKinematic = true;
}
private static IOEntity AttachTurretAlarm(Drone drone, AutoTurret turret)
{
var turretAlarm = GameManager.server.CreateEntity(AlarmPrefab, new Vector3(0, 0.185f, 0)) as IOEntity;
if (turretAlarm == null)
return null;
turretAlarm.networkEntityScale = true;
turretAlarm.pickup.enabled = false;
turretAlarm.SetFlagLocal(IOEntity.Flag_HasPower, ShouldPowerAlarm(drone, turret));
RemoveProblemComponents(turretAlarm);
HideInputsAndOutputs(turretAlarm);
turretAlarm.SetParent(turret);
turretAlarm.Spawn();
return turretAlarm;
}
private static IOEntity AttachTurretLight(Drone drone, AutoTurret turret)
{
var turretLight = GameManager.server.CreateEntity(SirenLightPrefab, new Vector3(0, 0.3f, 0), Quaternion.Euler(180, 0, 0)) as IOEntity;
if (turretLight == null)
return null;
turretLight.networkEntityScale = true;
turretLight.SetFlagLocal(IOEntity.Flag_HasPower, ShouldPowerAlarm(drone, turret));
RemoveProblemComponents(turretLight);
HideInputsAndOutputs(turretLight);
turretLight.SetParent(turret);
turretLight.Spawn();
return turretLight;
}
private static ElectricSwitch AttachTurretSwitch(AutoTurret autoTurret)
{
var electricSwitch = GameManager.server.CreateEntity(ElectricSwitchPrefab, TurretSwitchPosition, TurretSwitchRotation) as ElectricSwitch;
if (electricSwitch == null)
return null;
electricSwitch.networkEntityScale = true;
SetupTurretSwitch(electricSwitch);
electricSwitch.SetParent(autoTurret);
electricSwitch.Spawn();
return electricSwitch;
}
private static void HideInputsAndOutputs(IOEntity ioEntity)
{
// Hide the inputs and outputs on the client.
foreach (var input in ioEntity.inputs)
{
input.type = IOEntity.IOType.Generic;
}
foreach (var output in ioEntity.outputs)
{
output.type = IOEntity.IOType.Generic;
}
}
private static void RefreshScaledChildEntity(BaseEntity entity)
{
if (entity.networkEntityScale)
return;
// There is a bug where when the client first spawns the entity, the parent entity's modified scale
// isn't taken into account, unless the child entity's localScale is also networked.
// Additionally, if the entity is already rendered at the incorrect scale, sending a network update that
// includes localScale will correct it.
entity.networkEntityScale = true;
entity.SendNetworkUpdate();
}
private static void SetupTurretSwitch(ElectricSwitch electricSwitch)
{
// Damage will be processed by the drone.
electricSwitch.baseProtection = null;
using var flagsScope = electricSwitch.StartSetFlags(BaseEntity.FlagsUpdateMode.SendNetworkUpdate_Flags);
flagsScope.Set(IOEntity.Flag_HasPower, true);
electricSwitch.pickup.enabled = false;
RemoveProblemComponents(electricSwitch);
HideInputsAndOutputs(electricSwitch);
if (electricSwitch.IsFullySpawned())
{
var transform = electricSwitch.transform;
if (transform.localPosition != TurretSwitchPosition)
{
transform.localPosition = TurretSwitchPosition;
electricSwitch.InvalidateNetworkCache();
electricSwitch.SendNetworkUpdate_Position();
}
RefreshScaledChildEntity(electricSwitch);
}
}
private static BaseEntity GetLookEntity(BasePlayer basePlayer, float maxDistance = 3)
{
return Physics.Raycast(basePlayer.eyes.HeadRay(), out var hit, maxDistance, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore)
? hit.GetEntity()
: null;
}
private static AutoTurret GetParentTurret(BaseEntity entity)
{
return entity.GetParentEntity() as AutoTurret;
}
private static void RunOnEntityBuilt(Item turretItem, AutoTurret autoTurret)
{
ExposedHooks.OnEntityBuilt(turretItem.GetHeldEntity(), autoTurret.gameObject);
}
private static void UseItem(BasePlayer basePlayer, Item item, int amountToConsume = 1)
{
item.UseItem(amountToConsume);
basePlayer.Command("note.inv", item.info.itemid, -amountToConsume);
}
private static float GetItemConditionFraction(Item item)
{
return item.hasCondition ? item.condition / item.info.condition.max : 1.0f;
}
private static Item FindPlayerAutoTurretItem(BasePlayer basePlayer)
{
return basePlayer.inventory.FindItemByItemID(AutoTurretItemId);
}
private static void ToggleScaleNetworking(Item item, bool enabled, bool sendUpdate = false)
{
if (item.position != 0)
return;
var heldWeapon = item.GetHeldEntity() as BaseProjectile;
if (heldWeapon == null)
return;
// Ensure the turret's weapon scale is correct.
heldWeapon.networkEntityScale = enabled;
if (sendUpdate)
{
heldWeapon.SendNetworkUpdate();
}
}
private static void HandleTurretInventoryItemAddedRemoved(Item item, bool wasAdded, BasePlayer sourcePlayer)
{
ToggleScaleNetworking(item, wasAdded);
}
private void RefreshDroneSettingsProfile(Drone drone)
{
DroneSettings?.Call("API_RefreshDroneProfile", drone);
}
private void SwitchControl(BasePlayer player, ComputerStation station, IRemoteControllable previous, IRemoteControllable next)
{
var nextEnt = next.GetEnt();
if (nextEnt == null)
return;
station.StopControl(player);
station.SendNetworkUpdateImmediate();
var forcedDroneHover = false;
var drone = previous as Drone ?? next as Drone;
// Check for a controller, in case Drone Hover has already added a fake one.
if ((object)drone != null && !RCUtils.HasController(drone))
{
RCUtils.AddFakeViewer(drone);
forcedDroneHover = true;
}
NextTick(() =>
{
if (player == null || player.IsDestroyed
|| station == null || station.IsDestroyed
|| ((previous as BaseEntity)?.IsDestroyed ?? false)
|| ((next as BaseEntity)?.IsDestroyed ?? false)
|| station.currentlyControllingEnt.IsValid(serverside: true))
{
if (forcedDroneHover && RCUtils.HasFakeController(drone))
{
RCUtils.RemoveController(drone);
}
return;
}
RCUtils.RemoveController(next);
station.currentlyControllingEnt.uid = nextEnt.net.ID;
station.currentPlayerID = player.userID;
var isControlling = RCUtils.AddViewer(next, player);
station.SetFlagLocal(ComputerStation.Flag_HasFullControl, isControlling);
station.SendNetworkUpdateImmediate();
// station.SendControlBookmarks(player);
station.InvokeRepeating(station.ControlCheck, 0f, 0f);
ExposedHooks.OnBookmarkControlStarted(station, player, next.GetIdentifier(), next);
});
}
private bool HasPermissionToControl(BasePlayer player)
{
if (!_config.RequirePermission)
return true;
return permission.UserHasPermission(player.UserIDString, PermissionControl);
}
private void RefreshAlarmState(Drone drone, AutoTurret turret)
{
if (_config.EnableAudioAlarm)
{
var turretAlarm = GetTurretAlarm(turret);
if (turretAlarm != null)
{
using var flagsScope = turretAlarm.StartSetFlags(BaseEntity.FlagsUpdateMode.SendNetworkUpdate_Flags);
flagsScope.Set(IOEntity.Flag_HasPower, ShouldPowerAlarm(drone, turret));
}
}
if (_config.EnableSirenLight)
{
var turretLight = GetTurretLight(turret);
if (turretLight != null)
{
using var flagsScope = turretLight.StartSetFlags(BaseEntity.FlagsUpdateMode.SendNetworkUpdate_Flags);
flagsScope.Set(IOEntity.Flag_HasPower, ShouldPowerAlarm(drone, turret));
}
}
}
private void SetupDroneTurretBeforeSpawn(AutoTurret turret)
{
turret.transform.localScale = TurretTransformScale;
turret.networkEntityScale = true;
}
private void SetupDroneTurretAfterSpawn(Drone drone, AutoTurret turret)
{
var transform = turret.transform;
if (!turret.networkEntityScale || transform.localScale != TurretTransformScale)
{
transform.localScale = TurretTransformScale;
turret.networkEntityScale = true;
turret.SendNetworkUpdate();
}
turret.inventory.onItemAddedRemoved += HandleTurretInventoryItemAddedRemoved;
var turretWeaponItem = turret.inventory.GetSlot(0);
if (turretWeaponItem != null)
{
ToggleScaleNetworking(turretWeaponItem, enabled: true, sendUpdate: true);
}
// Invert the localScale of the target trigger to compensate for the turret localScale being increased.
// Without doing this, the range of the turret corresponds to the turret scale.
var targetTrigger = turret.GetComponentInChildren<TargetTrigger>();
if (targetTrigger)
{
targetTrigger.transform.localScale = TurretTransformInvertedScale;
}
var warningZone = turret.GetComponentInChildren<TriggerHostileWarningZone>();
if (warningZone != null)
{
warningZone.transform.localScale = TurretTransformInvertedScale;
}
// Damage will be processed by the drone.
turret.baseProtection = null;
turret.sightRange = _config.TurretRange;
turret.targetTrigger.GetComponent<SphereCollider>().radius = _config.TurretRange;
RemoveProblemComponents(turret);
HideInputsAndOutputs(turret);
AddRigidBodyToTriggerCollider(turret);
if (_config.EnableAudioAlarm)
{
var turretAlarm = GetTurretAlarm(turret);
if (turretAlarm != null)
{
using var flagsScope = turretAlarm.StartSetFlags(BaseEntity.FlagsUpdateMode.SendNetworkUpdate_Flags);
flagsScope.Set(IOEntity.Flag_HasPower, ShouldPowerAlarm(drone, turret));
RefreshScaledChildEntity(turretAlarm);
}
else
{
AttachTurretAlarm(drone, turret);
}
}
if (_config.EnableSirenLight)
{
var turretLight = GetTurretLight(turret);
if (turretLight != null)
{
using var flagsScope = turretLight.StartSetFlags(BaseEntity.FlagsUpdateMode.SendNetworkUpdate_Flags);
flagsScope.Set(IOEntity.Flag_HasPower, ShouldPowerAlarm(drone, turret));
RefreshScaledChildEntity(turretLight);
}
else
{
AttachTurretLight(drone, turret);
}
}
if (_config.EnableAudioAlarm || _config.EnableSirenLight)
{
var drone2 = drone;
var turret2 = turret;
// Delay refreshing the alarm state in case the turret is being automatically powered on.
NextTick(() =>
{
if (drone2 == null || turret2 == null)
return;
RefreshAlarmState(drone2, turret2);
});
}
RefreshDroneSettingsProfile(drone);
_turretDroneTracker.Add(drone.net.ID);
}
private void RefreshDroneTurret(Drone drone, AutoTurret turret)
{
var sphereEntity = turret.GetParentEntity() as SphereEntity;
if (sphereEntity != null)
{
turret.SetParent(drone, worldPositionStays: true);
sphereEntity.Kill();
}
SetupDroneTurretAfterSpawn(drone, turret);
// The switch is only expected to be present for normal auto turrets, not for NPC auto turrets.
var electricSwitch = turret.GetComponentInChildren<ElectricSwitch>();
if (electricSwitch != null)
{
SetupTurretSwitch(electricSwitch);
}
}
private NPCAutoTurret DeployNpcAutoTurret(Drone drone, BasePlayer deployer)
{
var turret = GameManager.server.CreateEntity(NpcAutoTurretPrefab, TurretLocalPosition) as NPCAutoTurret;
if (turret == null)
return null;
SetupDroneTurretBeforeSpawn(turret);
turret.SetParent(drone);
turret.Spawn();
SetupDroneTurretAfterSpawn(drone, turret);
drone.SetSlot(TurretSlot, turret);
Effect.server.Run(DeployEffectPrefab, turret.transform.position);
ExposedHooks.OnDroneNpcTurretDeployed(drone, turret, deployer);
return turret;
}
private AutoTurret DeployAutoTurret(Drone drone, BasePlayer basePlayer, float conditionFraction = 1)
{
var turret = GameManager.server.CreateEntity(AutoTurretPrefab, TurretLocalPosition) as AutoTurret;
if (turret == null)
return null;
if (basePlayer != null)
{
turret.OwnerID = basePlayer.userID;
}
SetupDroneTurretBeforeSpawn(turret);
turret.SetFlagLocal(IOEntity.Flag_HasPower, true);
turret.SetParent(drone);
turret.Spawn();
turret.SetHealth(turret.MaxHealth() * conditionFraction);
AttachTurretSwitch(turret);
drone.SetSlot(TurretSlot, turret);
SetupDroneTurretAfterSpawn(drone, turret);
Effect.server.Run(DeployEffectPrefab, turret.transform.position);
ExposedHooks.OnDroneTurretDeployed(drone, turret, basePlayer);
if (basePlayer == null)
return turret;
if (!turret.IsAuthed(basePlayer))
{
turret.authorizedPlayers.Add(basePlayer.userID);
turret.UpdateMaxAuthCapacity();
turret.SendNetworkUpdate();
}
// Allow other plugins to detect the auto turret being deployed (e.g., to add a weapon automatically).
var turretItem = FindPlayerAutoTurretItem(basePlayer);
if (turretItem != null)
{
RunOnEntityBuilt(turretItem, turret);
}
else
{
// Temporarily increase the player inventory capacity to ensure there is enough space.
basePlayer.inventory.containerMain.capacity++;
var temporaryTurretItem = ItemManager.CreateByItemID(AutoTurretItemId);
if (basePlayer.inventory.GiveItem(temporaryTurretItem))
{
RunOnEntityBuilt(temporaryTurretItem, turret);
temporaryTurretItem.RemoveFromContainer();
}
temporaryTurretItem.Remove();
basePlayer.inventory.containerMain.capacity--;
}
return turret;
}
private object HandleLightToggle(BasePlayer player)
{
var turret = RCUtils.GetControlledEntity<AutoTurret>(player, out _);
if (turret == null || !RCUtils.HasController(turret, player))
return null;
var weapon = turret.GetAttachedWeapon();
if (weapon == null)
return null;
weapon.SetLightsOn(!weapon.LightsOn());
return False;
}
private void HandleSwapSeats(BasePlayer player)
{
var controllable = RCUtils.GetControlledEntity<IRemoteControllable>(player, out var station);
if (controllable == null)
return;
AutoTurret turret;
var drone = controllable as Drone;
if (drone != null)
{
turret = GetDroneTurret(drone);
if (turret != null && turret.IsOn() && !turret.PeacekeeperMode() && turret is not NPCAutoTurret)
{
SwitchControl(player, station, drone, turret);
}
return;
}
turret = controllable as AutoTurret;
if (turret != null)
{
drone = GetParentDrone(turret);
if (drone != null)
{
SwitchControl(player, station, turret, drone);
}
return;
}
}
#endregion
#region Dynamic Hook Subscriptions
private class DynamicHookSubscriber<T>
{
private DroneTurrets _plugin;
private HashSet<T> _list = new();
private string[] _hookNames;
public DynamicHookSubscriber(DroneTurrets plugin, params string[] hookNames)
{
_plugin = plugin;
_hookNames = hookNames;
}
public void Add(T item)
{
if (_list.Add(item) && _list.Count == 1)
{
SubscribeAll();
}
}
public void Remove(T item)
{
if (_list.Remove(item) && _list.Count == 0)
{
UnsubscribeAll();
}
}
public void SubscribeAll()
{
foreach (var hookName in _hookNames)
{
_plugin.Subscribe(hookName);
}
}
public void UnsubscribeAll()
{
foreach (var hookName in _hookNames)
{
_plugin.Unsubscribe(hookName);
}
}
}
#endregion
#region Drone Controller
private class DroneController : ListComponent<DroneController>
{
public static void AddToDrone(Drone drone, AutoTurret turret, BasePlayer player)
{
var component = drone.gameObject.GetComponent<DroneController>();
if (component == null)
{
component = drone.gameObject.AddComponent<DroneController>();
component._drone = drone;
component._turret = turret;
}
else
{
component.enabled = true;
}
component._controller = player;
component._viewerId = new CameraViewerId(player.userID, 0);
}
public static void DestroyAll()
{
foreach (var component in InstanceList.ToArray())
{
DestroyImmediate(component);
}
}
private Drone _drone;
private AutoTurret _turret;
private BasePlayer _controller;
private CameraViewerId _viewerId;
private void Update()
{
if (!RCUtils.HasController(_turret, _controller)
|| RCUtils.GetControlledEntity<AutoTurret>(_controller) != _turret)
{
enabled = false;
return;
}
// Optimization: Skip if there was no user input this frame.
if (_con
// ... [Code preview truncated due to length. Full source in attached file] ...