C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Linq;
using Network;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Better Drone Collision", "WhiteThunder", "1.1.0")]
[Description("Overhauls drone collision damage so it's more intuitive.")]
internal class BetterDroneCollision : CovalencePlugin {
#region Fields
private const float ReplacementHurtVelocityThreshold = float.MaxValue;
[PluginReference]
private readonly Plugin DroneSettings;
private Configuration _config;
private float? _vanillaHurtVelocityThreshold;
#endregion
#region Hooks
private void Init() {
Unsubscribe(nameof(OnEntitySpawned));
}
private void OnServerInitialized() {
foreach (var entity in BaseNetworkable.serverEntities) {
var drone = entity as Drone;
if (drone == null) continue;
OnEntitySpawned(drone);
}
Subscribe(nameof(OnEntitySpawned));
}
private void Unload() {
foreach (var entity in BaseNetworkable.serverEntities) {
var drone = entity as Drone;
if (drone == null || !IsDroneEligible(drone)) continue;
ResetDrone(drone);
}
}
private void OnEntitySpawned(Drone drone) {
if (!IsDroneEligible(drone)) return;
_vanillaHurtVelocityThreshold ??= drone.hurtVelocityThreshold;
var drone2 = drone;
NextTick(() => {
if (drone2 != null) {
TryReplaceDroneCollision(drone2);
}
}
);
}
#endregion
#region Helper Methods
private static bool DroneCollisionReplaceWasBlocked(Drone drone) {
return Interface.CallHook("OnDroneCollisionReplace", drone) is false;
}
private static bool IsDroneEligible(Drone drone) {
return drone.skinID == 0 && drone is not DeliveryDrone;
}
private bool TryReplaceDroneCollision(Drone drone) {
if (DroneCollisionReplaceWasBlocked(drone)) return false;
drone.hurtVelocityThreshold = ReplacementHurtVelocityThreshold;
DroneCollisionReplacer.AddToDrone(this, drone);
return true;
}
private void ResetDrone(Drone drone) {
if (drone.hurtVelocityThreshold == ReplacementHurtVelocityThreshold && _vanillaHurtVelocityThreshold != null) {
drone.hurtVelocityThreshold = (float)_vanillaHurtVelocityThreshold;
}
DroneCollisionReplacer.RemoveFromDrone(drone);
}
#endregion
#region Collision Replacer
private class DroneCollisionReplacer : FacepunchBehaviour {
public static void AddToDrone(BetterDroneCollision plugin, Drone drone) {
var component = drone.gameObject.AddComponent<DroneCollisionReplacer>();
component._plugin = plugin;
component._drone = drone;
}
public static void RemoveFromDrone(Drone drone) {
Destroy(drone.gameObject.GetComponent<DroneCollisionReplacer>());
}
private BetterDroneCollision _plugin;
private Drone _drone;
private float _nextDamageTime;
private Configuration _config => _plugin._config;
private void OnCollisionEnter(Collision collision) {
if (collision == null || collision.gameObject == null) return;
if (Time.time < _nextDamageTime) return;
var magnitude = collision.relativeVelocity.magnitude;
if (magnitude < _config.MinCollisionVelocity) return;
// Avoid damage when landing.if (Vector3.Dot(collision.relativeVelocity.normalized, _drone.transform.up) > 0.5f)
return;
var otherEntity = collision.gameObject.ToBaseEntity();
if (otherEntity is TimedExplosive) return;
if (otherEntity is DroppedItem) {
for (var i = 0; i < collision.contactCount; i++) {
var contact = collision.GetContact(i);
Physics.IgnoreCollision(contact.thisCollider, contact.otherCollider);
}
// If the drone just had a collision, assume it was triggered by the Dropped Item and re-enable the drone.if (Math.Abs(_drone.lastCollision - TimeEx.currentTimestamp) < 0.001f)
{
_drone.lastCollision = 0;
}
return;
}
var damage = magnitude * _config.CollisionDamageMultiplier;
// If DroneSettings is not loaded, it's probably safe to assume that drones are using default protection properties.// Default protection properties make a drone immune to collision damage, so bypass protection.// Without this bypass, using this plugin standalone would make drones immune to collision which is not desirable.var useProtection = _plugin.DroneSettings != null;
_drone.Hurt(damage, DamageType.Collision, useProtection : useProtection);
Interface.CallHook("OnDroneCollisionImpact", _drone, collision);
_nextDamageTime = Time.time + _config.MinTimeBetweenImpacts;
}
}
#endregion
#region Configuration
[JsonObject(MemberSerialization.OptIn)]
private class Configuration : BaseConfiguration {
[JsonProperty("MinCollisionVelocity")]
public float MinCollisionVelocity = 3;
[JsonProperty("MinTimeBetweenImpacts")]
public float MinTimeBetweenImpacts = 0.25f;
[JsonProperty("CollisionDamageMultiplier")]
public float CollisionDamageMultiplier = 1;
}
private Configuration GetDefaultConfig() => new();
#region Configuration Helpers
private class BaseConfiguration {
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(BaseConfiguration 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) {
var changed = false;
foreach (var key in currentWithDefaults.Keys) {
if (currentRaw.TryGetValue(key, out var currentRawValue)) {
var currentDictValue = currentRawValue as Dictionary<string, object>;
if (currentWithDefaults[key] is Dictionary<string, object> defaultDictValue) {
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;
}
protected override void LoadDefaultConfig() => _config = GetDefaultConfig();
protected override void LoadConfig() {
base.LoadConfig();
try {
_config = Config.ReadObject<Configuration>();
if (_config == null) {
throw new JsonException();
}
if (MaybeUpdateConfig(_config)) {
LogWarning("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch (Exception e) {
LogError(e.Message);
LogWarning($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig() {
Log($"Configuration changes saved to {Name}.json");
Config.WriteObject(_config, true);
}
#endregion
#endregion
}
}