C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Linq;
using Network;
using Newtonsoft.Json;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("DronePilot", "k1lly0u", "1.0.7")]
[Description("Allow players with permission to fly drones")]
class DronePilot : RustPlugin
{
#region Fields
private Hash<ulong, double> _cooldownTimes = new Hash<ulong, double>();
private const string USE_PERMISSION = "dronepilot.use";
private const string CREATE_PERMISSION = "dronepilot.create";
private const string IGNORE_COOLDOWN_PERMISSION = "dronepilot.nocooldown";
private const string IGNORE_COST_PERMISSION = "dronepilot.nocost";
private const float DRONE_DISABLE_HEALTH_FRACTION = 0.1f;
#endregion
#region Oxide Hooks
private void Loaded()
{
permission.RegisterPermission(USE_PERMISSION, this);
permission.RegisterPermission(CREATE_PERMISSION, this);
permission.RegisterPermission(IGNORE_COOLDOWN_PERMISSION, this);
permission.RegisterPermission(IGNORE_COST_PERMISSION, this);
GetMessage = Message;
}
protected override void LoadDefaultMessages() => lang.RegisterMessages(Messages, this);
private object CanBuild(Planner planner, Construction prefab)
{
BasePlayer player = planner?.GetOwnerPlayer();
if (player == null)
{
return null;
}
if (prefab.fullName == "assets/prefabs/deployable/drone/drone.deployed.prefab")
{
if (player.GetMountedVehicle() != null || player.GetParentEntity() != null)
{
player.ChatMessage(Message("Error.HasMountedOrParented", player.userID));
return false;
}
}
return null;
}
private void OnEntityBuilt(Planner planner, GameObject gameObject)
{
BasePlayer player = planner?.GetOwnerPlayer();
if (player == null)
return;
Drone drone = gameObject.ToBaseEntity() as Drone;
if (drone != null)
{
if (permission.UserHasPermission(player.UserIDString, USE_PERMISSION))
{
if (drone.healthFraction < DRONE_DISABLE_HEALTH_FRACTION)
{
player.ChatMessage(Message("Notification.TooMuchDamage", player.userID));
return;
}
DroneController droneController = gameObject.AddComponent<DroneController>();
droneController.StartPiloting(player);
player.ChatMessage(Message("Notification.Controls", player.userID));
}
else player.ChatMessage(Message("Error.NoFlyPermissions", player.userID));
}
}
private void OnEntityTakeDamage(Drone drone, HitInfo hitInfo)
{
DroneController droneController = drone.GetComponent<DroneController>();
if (droneController != null)
{
Rust.DamageType damageType = hitInfo.damageTypes.GetMajorityDamageType();
if (damageType == Rust.DamageType.Generic) // Disable damage from Drone.OnCollisionEnter because its a stupid amount
{
hitInfo.damageTypes.Clear();
hitInfo.HitEntity = null;
hitInfo.HitMaterial = 0;
hitInfo.PointStart = Vector3.zero;
return;
}
if (damageType != Rust.DamageType.Collision) // Dont do anything is its collision damage because we are managing that in DroneController.OnCollisionEnter
{
hitInfo.damageTypes.ScaleAll(Configuration.DamageScale);
BasePlayer player = droneController.Controller;
float healthFraction = (drone.Health() - hitInfo.damageTypes.Total()) / drone.MaxHealth();
if (healthFraction < 0)
{
player.ChatMessage(Message("Notification.Destroyed", player.userID));
droneController._killOnDestroy = true;
UnityEngine.Object.Destroy(droneController);
}
if (healthFraction < DRONE_DISABLE_HEALTH_FRACTION)
{
player.ChatMessage(Message("Notification.TooMuchDamage", player.userID));
UnityEngine.Object.Destroy(droneController);
}
}
}
}
private void OnPlayerWound(BasePlayer player, HitInfo hitInfo)
{
for (int i = 0; i < DroneController._allDroneControllers.Count; i++)
{
DroneController droneController = DroneController._allDroneControllers[i];
if (droneController.Dummy == player)
{
BasePlayer actualPlayer = droneController.Controller;
UnityEngine.Object.Destroy(droneController);
actualPlayer.BecomeWounded(hitInfo);
return;
}
}
}
private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo)
{
for (int i = 0; i < DroneController._allDroneControllers.Count; i++)
{
DroneController droneController = DroneController._allDroneControllers[i];
if (droneController.Dummy == player)
{
droneController.OnPlayerDeath(hitInfo);
return;
}
}
}
private void OnEntityDeath(Drone drone, HitInfo hitInfo)
{
DroneController droneController = drone.GetComponent<DroneController>();
if (droneController != null)
{
BasePlayer player = droneController.Controller;
if (player != null)
player.ChatMessage(Message("Notification.Destroyed", player.userID));
droneController._killOnDestroy = true;
UnityEngine.Object.Destroy(droneController);
}
}
private object CanPickupEntity(BasePlayer player, Drone drone)
{
DroneController droneController = drone.GetComponent<DroneController>();
if (droneController != null && droneController.Controller != null)
return false;
return null;
}
private object CanSpectateTarget(BasePlayer player, string name)
{
DroneController droneController = player.GetMounted()?.GetComponentInParent<DroneController>();
if (droneController)
return false;
return null;
}
private object CanBeTargeted(BasePlayer player)
{
for (int i = 0; i < DroneController._allDroneControllers.Count; i++)
{
DroneController droneController = DroneController._allDroneControllers[i];
if (droneController.Controller.userID == player.userID)
{
return false;
}
}
return null;
}
private object CanBradleyApcTarget(BasePlayer player)
{
for (int i = 0; i < DroneController._allDroneControllers.Count; i++)
{
DroneController droneController = DroneController._allDroneControllers[i];
if (droneController.Controller.userID == player.userID)
{
return false;
}
}
return null;
}
private object CanHelicopterTarget(BasePlayer player)
{
for (int i = 0; i < DroneController._allDroneControllers.Count; i++)
{
DroneController droneController = DroneController._allDroneControllers[i];
if (droneController.Controller.userID == player.userID)
{
return false;
}
}
return null;
}
private void Unload()
{
for (int i = DroneController._allDroneControllers.Count - 1; i >= 0; i--)
UnityEngine.Object.Destroy(DroneController._allDroneControllers[i]);
Configuration = null;
GetMessage = null;
}
#endregion
#region Functions
private static void MoveInventoryTo(PlayerInventory from, PlayerInventory to)
{
for (int i = from.containerBelt.itemList.Count - 1; i >= 0; i--)
{
Item item = from.containerBelt.itemList[i];
item.MoveToContainer(to.containerBelt, item.position, false);
}
for (int i = from.containerMain.itemList.Count - 1; i >= 0; i--)
{
Item item = from.containerMain.itemList[i];
item.MoveToContainer(to.containerMain, item.position, false);
}
for (int i = from.containerWear.itemList.Count - 1; i >= 0; i--)
{
Item item = from.containerWear.itemList[i];
item.MoveToContainer(to.containerWear, item.position, false);
}
}
private string FormatTime(double time)
{
TimeSpan dateDifference = TimeSpan.FromSeconds(time);
int days = dateDifference.Days;
int hours = dateDifference.Hours;
int mins = dateDifference.Minutes;
int secs = dateDifference.Seconds;
if (days > 0)
return string.Format("~{0:00}d:{1:00}h", days, hours);
else if (hours > 0)
return string.Format("~{0:00}h:{1:00}m", hours, mins, secs);
else if (mins > 0)
return string.Format("{0:00}m:{1:00}s", mins, secs);
else return string.Format("{0}s", secs);
}
private object CanTeleport(BasePlayer player)
{
if (player.GetMounted()?.GetComponentInParent<DroneController>() != null)
return Message("Error.NoTP", player.userID);
return null;
}
#endregion
#region Controller
private class DroneController : MonoBehaviour
{
public static List<DroneController> _allDroneControllers = new List<DroneController>();
public Drone Drone { get; private set; }
public BasePlayer Controller { get; private set; }
public BasePlayer Dummy { get; private set; }
public bool IsBeingControlled => Controller != null;
private DroneInputState _currentInput;
private float _lastInputTime;
private double _lastCollision = -1000.0;
private bool _isGrounded;
private Transform _tr;
private Rigidbody _rb;
private BaseMountable _mountPoint;
private float _avgTerrainHeight;
internal bool _killOnDestroy = false;
private void Awake()
{
Drone = GetComponent<Drone>();
Drone.enabled = false;
_tr = Drone.transform;
_rb = Drone.GetComponent<Rigidbody>();
_allDroneControllers.Add(this);
}
private void OnDestroy()
{
if (Controller != null)
{
StopControllingDrone();
RestorePlayer();
}
if (_mountPoint != null && !_mountPoint.IsDestroyed)
_mountPoint.Kill(BaseNetworkable.DestroyMode.None);
_allDroneControllers.Remove(this);
if (_killOnDestroy && Drone != null && !Drone.IsDead())
Drone.Kill();
}
internal void StartPiloting(BasePlayer player)
{
CreateMountPoint();
Controller = player;
CreateDummyPlayer();
Controller.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, true);
Controller.MountObject(_mountPoint);
NetWrite write = Net.sv.StartWrite();
write.PacketID(Network.Message.Type.EntityDestroy);
write.EntityID(player.net.ID);
write.UInt8((byte)BaseNetworkable.DestroyMode.None);
write.Send(new SendInfo(player.net.group.subscribers.Where(x => x.userid != player.userID).ToList()));
Controller.limitNetworking = true;
Controller.syncPosition = false;
}
internal void OnPlayerDeath(HitInfo hitInfo)
{
StopControllingDrone();
Controller.Die(hitInfo);
Controller = null;
Destroy(this);
}
private void StopControllingDrone()
{
Controller.PauseFlyHackDetection(3f);
Controller.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, false);
Controller.limitNetworking = false;
Controller.syncPosition = true;
Controller.DismountObject();
Controller.EnsureDismounted();
}
private void RestorePlayer()
{
Controller.transform.position = Dummy.transform.position;
if (Dummy != null && !Dummy.IsDead())
{
MoveInventoryTo(Dummy.inventory, Controller.inventory);
Dummy.Kill(BaseNetworkable.DestroyMode.None);
}
Controller = null;
}
private void CreateDummyPlayer()
{
Dummy = (BasePlayer)GameManager.server.CreateEntity(Controller.PrefabName, Controller.transform.position, Controller.transform.rotation);
Dummy.enableSaving = false;
Dummy.Spawn();
Dummy.displayName = Controller.displayName;
Dummy.SetFlag(BaseEntity.Flags.Reserved8, true);
Dummy.inventory.Strip();
MoveInventoryTo(Controller.inventory, Dummy.inventory);
}
private void CreateMountPoint()
{
const string CHAIR_PREFAB = "assets/prefabs/deployable/chair/chair.deployed.prefab";
_mountPoint = GameManager.server.CreateEntity(CHAIR_PREFAB, _tr.position) as BaseMountable;
_mountPoint.enableSaving = false;
_mountPoint.skinID = (ulong)1169930802;
_mountPoint.isMobile = true;
_mountPoint.Spawn();
Destroy(_mountPoint.GetComponent<DestroyOnGroundMissing>());
Destroy(_mountPoint.GetComponent<GroundWatch>());
Destroy(_mountPoint.GetComponent<MeshCollider>());
_mountPoint.SetParent(Drone);
_mountPoint.transform.localPosition = new Vector3(0f, -0.5f, -1f);
}
private void UserInput(InputState inputState)
{
_currentInput.Reset();
float forward = (inputState.IsDown(BUTTON.FORWARD) ? 1 : 0) + (inputState.IsDown(BUTTON.BACKWARD) ? -1 : 0);
float sideways = (inputState.IsDown(BUTTON.RIGHT) ? 1 : 0) + (inputState.IsDown(BUTTON.LEFT) ? -1 : 0);
_currentInput.movement = new Vector3(sideways, 0f, forward).normalized;
_currentInput.throttle = ((inputState.IsDown(BUTTON.SPRINT) ? 1 : 0) + (inputState.IsDown(BUTTON.DUCK) ? -1 : 0));
_lastInputTime = UnityEngine.Time.time;
}
private void FixedUpdate()
{
if (Drone.IsDead() || !IsBeingControlled)
return;
float num = Drone.WaterFactor();
if (Drone.killInWater && num > 0.1f)
{
if (num > 0.75f)
{
Controller.ChatMessage(GetMessage("Notification.WaterDamage", Controller.userID));
_killOnDestroy = true;
Destroy(this);
}
Controller.ChatMessage(GetMessage("Notification.WaterLogged", Controller.userID));
Destroy(this);
return;
}
if (Controller.serverInput.WasJustPressed(BUTTON.JUMP))
{
Destroy(this);
return;
}
UserInput(Controller.serverInput);
bool isCollisionDisabled = _lastCollision > 0.0 && TimeEx.currentTimestamp - _lastCollision < (double)Configuration.CollisionDisableTime;
RaycastHit raycastHit;
_isGrounded = (Drone.enableGrounding && _rb.SweepTest(-_tr.up, out raycastHit, Drone.groundTraceDist));
Vector3 localToWorld = _tr.TransformDirection(_currentInput.movement);
Vector3 direction;
float magnitude;
_rb.velocity.WithY(0f).ToDirectionAndMagnitude(out direction, out magnitude);
float leanMagnitude = Mathf.Clamp01(magnitude / Configuration.LeanMaxVelocity);
Vector3 worldLeanVelocity = Mathf.Approximately(localToWorld.sqrMagnitude, 0f) ? (-leanMagnitude * direction) : localToWorld;
Vector3 normalizedLean = (Vector3.up + worldLeanVelocity * Configuration.LeanWeight * leanMagnitude).normalized;
float leanForce = Mathf.Max(Vector3.Dot(normalizedLean, _tr.up), 0f);
if (!isCollisionDisabled || _isGrounded)
{
Vector3 descendVector = (_isGrounded && _currentInput.throttle <= 0f) ? Vector3.zero : (-1f * _tr.up * Physics.gravity.y);
Vector3 movementVector = _isGrounded ? Vector3.zero : (localToWorld * Configuration.MovementAcceleration);
Vector3 ascendVector = _tr.up * _currentInput.throttle * Configuration.AltitudeAcceleration;
Vector3 force = descendVector + movementVector + ascendVector;
_rb.AddForce(force * leanForce, ForceMode.Acceleration);
}
if (!isCollisionDisabled && !_isGrounded)
{
Vector3 worldYaw = _tr.TransformVector(0f, _currentInput.movement.x * Configuration.YawSpeed, 0f);
Vector3 lean = Vector3.Cross(Quaternion.Euler(_rb.angularVelocity * Drone.uprightPrediction) * _tr.up, normalizedLean) * Configuration.UprightSpeed;
float leanMultiplier = (leanForce < Drone.uprightDot) ? 0f : leanForce;
Vector3 force = worldYaw * leanForce + lean * leanMultiplier;
_rb.AddTorque(force * leanForce, ForceMode.Acceleration);
if (!Configuration.AutoHover && _currentInput.throttle == 0 && _currentInput.movement == Vector3.zero)
_rb.AddForce((Physics.gravity * 2f) * Time.fixedDeltaTime, ForceMode.Acceleration);
}
_avgTerrainHeight = Mathf.Lerp(_avgTerrainHeight, TerrainMeta.HeightMap.GetHeight(_tr.position), Time.fixedDeltaTime * 20f);
if (_tr.position.y > _avgTerrainHeight + Configuration.MaximumCruiseHeight)
{
if (_rb.velocity.y > 0)
{
Vector3 velocity = _rb.velocity;
velocity.y = 0;
_rb.velocity = velocity;
}
}
UpdateNetworkGroup();
}
private void OnCollisionEnter(Collision collision)
{
_lastCollision = TimeEx.currentTimestamp;
float magnitude = collision.relativeVelocity.magnitude;
if (magnitude > Configuration.HurtVelocityThreshold)
{
float damageAmount = (magnitude / Configuration.HurtVelocityThreshold) * Configuration.HurtDamagePower;
float healthFraction = (Drone.Health() - damageAmount) / Drone.MaxHealth();
if (healthFraction < 0)
{
Controller.ChatMessage(GetMessage("Notification.Destroyed", Controller.userID));
_killOnDestroy = true;
Destroy(this);
return;
}
else if (healthFraction < DRONE_DISABLE_HEALTH_FRACTION)
{
Controller.ChatMessage(GetMessage("Notification.TooMuchDamage", Controller.userID));
Destroy(this);
return;
}
else Drone.Hurt(damageAmount, Rust.DamageType.Collision);
}
_rb.velocity = Vector3.Reflect(_rb.velocity, collision.contacts[0].normal);
}
private void OnCollisionStay()
{
_lastCollision = TimeEx.currentTimestamp;
}
private void UpdateNetworkGroup()
{
Network.Visibility.Group group = Net.sv.visibility.GetGroup(_tr.position);
if (_mountPoint.net.group != group)
_mountPoint.net.SwitchGroup(group);
if (Controller.net.group != group)
Controller.net.SwitchGroup(group);
}
private struct DroneInputState
{
public Vector3 movement;
public float throttle;
public void Reset()
{
movement = Vector3.zero;
}
}
}
#endregion
#region Chat Commands
[ChatCommand("drone")]
private void GiveDrone(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, CREATE_PERMISSION))
{
player.ChatMessage(Message("Error.NoPermission", player.userID));
return;
}
double nextUse;
if (_cooldownTimes.TryGetValue(player.userID, out nextUse) && nextUse > Time.time)
{
player.ChatMessage(string.Format(Message("Error.Cooldown", player.userID), FormatTime(nextUse - Time.time)));
return;
}
if (Configuration.Cost > 0 && !permission.UserHasPermission(player.UserIDString, IGNORE_COST_PERMISSION))
{
const int SCRAP_ID = -932201673;
if (player.inventory.GetAmount(SCRAP_ID) < Configuration.Cost)
{
player.ChatMessage(string.Format(Message("Error.InsufficientFunds", player.userID), Configuration.Cost));
return;
}
player.inventory.Take(null, SCRAP_ID, Configuration.Cost);
}
if (!permission.UserHasPermission(player.UserIDString, IGNORE_COOLDOWN_PERMISSION))
_cooldownTimes[player.userID] = Time.time + Configuration.CooldownTime;
const string DRONE_ITEM = "drone";
player.GiveItem(ItemManager.CreateByName(DRONE_ITEM), BaseEntity.GiveItemReason.PickedUp);
}
#endregion
#region Config
private static ConfigData Configuration;
private class ConfigData
{
[JsonProperty(PropertyName = "Vertical acceleration speed")]
public float AltitudeAcceleration { get; set; }
[JsonProperty(PropertyName = "Horizontal acceleration speed")]
public float MovementAcceleration { get; set; }
[JsonProperty(PropertyName = "Yaw speed")]
public float YawSpeed { get; set; }
[JsonProperty(PropertyName = "The speed that the drone will return to a levelled rotation after leaning")]
public float UprightSpeed { get; set; }
[JsonProperty(PropertyName = "Lean weight (how much the drone leans in to movement) (0.0 -> 1.0)")]
public float LeanWeight { get; set; }
[JsonProperty(PropertyName = "Lean max velocity (the maximum velocity for full lean)")]
public float LeanMaxVelocity { get; set; }
[JsonProperty(PropertyName = "The impact speed before damage is applied")]
public float HurtVelocityThreshold { get; set; }
[JsonProperty(PropertyName = "The amount of damage to apply from impacts (scales depending on speed)")]
public float HurtDamagePower { get; set; }
[JsonProperty(PropertyName = "The amount of time to disable the drone controls after a collision (seconds)")]
public float CollisionDisableTime { get; set; }
[JsonProperty(PropertyName = "Maximum cruising height above terrain")]
public float MaximumCruiseHeight { get; set; }
[JsonProperty(PropertyName = "Drone should autohover and not be affected by gravity when no user input is detected")]
public bool AutoHover { get; set; }
[JsonProperty(PropertyName = "Drone damage scaler (does not affect collision damage)")]
public float DamageScale { get; set; }
[JsonProperty(PropertyName = "The cooldown time on using the /drone command (seconds)")]
public int CooldownTime { get; set; }
[JsonProperty(PropertyName = "The scrap cost associated with using the /drone command")]
public int Cost { get; set; }
public Core.VersionNumber Version { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
Configuration = Config.ReadObject<ConfigData>();
if (Configuration.Version < Version)
UpdateConfigValues();
Config.WriteObject(Configuration, true);
}
protected override void LoadDefaultConfig() => Configuration = GetBaseConfig();
private ConfigData GetBaseConfig()
{
return new ConfigData
{
AltitudeAcceleration = 20f,
MovementAcceleration = 15f,
YawSpeed = 6f,
UprightSpeed = 2f,
LeanWeight = 0.35f,
LeanMaxVelocity = 6f,
HurtVelocityThreshold = 3f,
HurtDamagePower = 3f,
CollisionDisableTime = 0.25f,
MaximumCruiseHeight = 30f,
AutoHover = false,
DamageScale = 5f,
CooldownTime = 3600,
Cost = 0,
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(Configuration, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
ConfigData baseConfig = GetBaseConfig();
if (Configuration.Version < new Core.VersionNumber(1, 0, 1))
{
Configuration.AutoHover = false;
Configuration.DamageScale = 5f;
Configuration.CooldownTime = 3600;
Configuration.Cost = 0;
}
Configuration.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Localization
public string Message(string key, ulong playerId = 0U) => lang.GetMessage(key, this, playerId != 0U ? playerId.ToString() : null);
private static Func<string, ulong, string> GetMessage;
private readonly Dictionary<string, string> Messages = new Dictionary<string, string>
{
["Notification.TooMuchDamage"] = "This drone is too damaged to fly.Repair it to continue use",
["Notification.WaterDamage"] = "Drones can't swim...n00b",
["Notification.WaterLogged"] = "Your drone is water logged and is unable to fly",
["Notification.Destroyed"] = "Your drone has been destroyed",
["Notification.Controls"] = "<size=18><color=#ffa500>COBALT DRONE</color></size><size=14> - <color=#ce422b>Controls</color></size>\nForward/BackwardLeft/Right - Movement\nSprint/Duck - Ascend/Descend\nJump - Exit controls",
["Error.HasMountedOrParented"] = "You can't place drone while mounted or parented",
["Error.NoFlyPermissions"] = "You do not have permission to fly drones",
["Error.NoPermission"] = "You do not have permission to use this command",
["Error.NoTP"] = "Teleportation is disabled while flying drones",
["Error.Cooldown"] = "You have a cooldown period of <color=#ce422b>{0}</color> remaining before you can use this ocmmand again",
["Error.InsufficientFunds"] = "This command costs <color=#ce422b>{0} scrap</color> to use"
};
#endregion
}
}