using System; using System.Collections.Generic; using System.Diagnostics; using ConVar; using Newtonsoft.Json; using UnityEngine; using System.Linq; using Debug = UnityEngine.Debug; namespace Oxide.Plugins { [Info("Heli Network Fix", "ViolationHandler", "0.0.1")] [Description("Fixes networking issues while speeding in helicopters.")] public class HeliNetworkFix : RustPlugin { private HashSet componentList = new HashSet(); private Configuration config; #region Configuration private class Configuration { [JsonProperty("The minimum speed required for this to take effect. (Increasing this will make it so the helicopter needs to be faster to trigger this effect)")] public double speedMinimum = 20; [JsonProperty("Distance forward to render.")] public float renderDistance = 3.75f; [JsonProperty("The minimum amount of time before attempting to load the area in front of the player.")] public double minimumTimeSince = 1; [JsonProperty("The value required to load the area in front of the player. Determined by speed.")] public double minimumSpeedMultipliedValue = 37.5; private string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } protected override void LoadDefaultConfig() => config = new Configuration(); protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { throw new JsonException(); } if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys)) { Puts("Configuration appears to be outdated; updating and saving."); SaveConfig(); } } catch { Puts($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { Puts($"Configuration changes saved to {Name}.json"); Config.WriteObject(config, true); } #endregion Configuration private void Init() { LoadConfig(); } private void Unload() { foreach (NetworkFix fix in componentList) { if (fix == null) continue; // if its somehow null fix.onUnload = true; fix.OnDestroy(); } componentList = null; } #region Hooks private void OnEntityDismounted(BaseMountable entity, BasePlayer player) { if (entity == null || player == null) return; BaseVehicle vehicle = entity.GetParentEntity() as BaseVehicle; if (vehicle == null || vehicle is not PlayerHelicopter) return; NextTick(() => {RemoveComponent(vehicle, player, true);}); } private void OnEntityMounted(BaseMountable entity, BasePlayer player) { if (entity == null || player == null) return; BaseVehicle vehicle = entity.GetParentEntity() as BaseVehicle; if (vehicle == null || vehicle is not PlayerHelicopter) return; AddComponent(vehicle, player); } #endregion Hooks #region Helper Methods private bool AddComponent(BaseVehicle vehicle, BasePlayer player) { NetworkFix fix = vehicle.GetComponent(); if (fix != null) { fix.allPassengers.Add(player); return false; } fix = vehicle.gameObject.AddComponent(); fix.vehicle = vehicle; fix.speedMinimum = config.speedMinimum; fix.renderDistance = config.renderDistance; fix.minimumTimeSince = config.minimumTimeSince; fix.minimumSpeedMultipliedValue = config.minimumSpeedMultipliedValue; fix._instance = this; fix.allPassengers.Add(player); componentList.Add(fix); return true; } private bool RemoveComponent(BaseVehicle vehicle, BasePlayer player, bool hook = false) { if (vehicle == null) { ResetSecondaryNetworkGroup(player.net); return false; } NetworkFix fix = vehicle.GetComponent(); if (fix == null) return false; if(hook) { if (vehicle.AnyMounted()) { if(!IsPlayerMountedInVehicle(player, vehicle)) ResetSecondaryNetworkGroup(player.net); return false; } componentList.Remove(fix); UnityEngine.Object.Destroy(fix); return true; } componentList.Remove(fix); UnityEngine.Object.Destroy(fix); return true; } public bool IsPlayerMountedInVehicle(BasePlayer player, BaseVehicle vehicle) { if (vehicle.mountPoints?.Count <= 0) return false; foreach (BaseVehicle.MountPointInfo mountPoint in vehicle.mountPoints) { if (player == mountPoint.mountable?.GetMounted()) return true; } return false; } public bool ResetSecondaryNetworkGroup(Network.Networkable net) { if (net == null) return false; net.SwitchSecondaryGroup(null); net.OnSubscriptionChange(); return true; } #endregion Helper Methods #region Custom Class public class NetworkFix : FacepunchBehaviour { public BasePlayer player; private Network.Visibility.Group group; public BaseVehicle vehicle; public HeliNetworkFix _instance; public HashSet allPassengers = new HashSet(); private Vector3 projectedPosition = Vector3.zero; public bool onUnload = false; private float lastCalled; public float SinceCalled; public double minimumTimeSince = 1; public double minimumSpeedMultipliedValue = 37.5; public float renderDistance = 4; public float speed; public double speedMinimum; public void Awake() { lastCalled = UnityEngine.Time.realtimeSinceStartup; } public void FixedUpdate() { if (vehicle == null || !vehicle.IsOn()) return; SinceCalled = UnityEngine.Time.realtimeSinceStartup - lastCalled; speed = Vector3.Dot(vehicle.GetLocalVelocity(), vehicle.transform.forward); if (SinceCalled > 5 && speed < speedMinimum) { foreach (BaseVehicle.MountPointInfo mountPoint in vehicle.mountPoints) { player = mountPoint.mountable.GetMounted(); if ((object)player == null) continue; ResetSecondaryNetworkGroup(player.net); } foreach (BaseEntity child in vehicle.children) { player = child as BasePlayer; if ((object)player == null) continue; ResetSecondaryNetworkGroup(player.net); } SinceCalled = 0; return; } if (speed < speedMinimum || SinceCalled < minimumTimeSince || speed * SinceCalled < minimumSpeedMultipliedValue) return; Vector3 position = Vector3.zero; foreach (BaseVehicle.MountPointInfo mountPoint in vehicle.mountPoints) { player = mountPoint.mountable.GetMounted(); if ((object)player == null) continue; if(position == Vector3.zero) position = player.transform.position; if(projectedPosition == Vector3.zero) projectedPosition = position + player.eyes.HeadForward() * (speed * renderDistance); projectedPosition.y = position.y-5; if (group == null) group = player.net.sv.visibility.GetGroup(projectedPosition); if (group.networkables.Contains(player.net)) continue; player.net.SwitchSecondaryGroup(group); player.net.OnSubscriptionChange(); } foreach (BaseEntity child in vehicle.children) { player = child as BasePlayer; if ((object)player == null) continue; allPassengers.Add(player); if (projectedPosition == Vector3.zero || group.networkables.Contains(player.net)) continue; player.net.SwitchSecondaryGroup(group); player.net.OnSubscriptionChange(); } projectedPosition = Vector3.zero; group = null; lastCalled = UnityEngine.Time.realtimeSinceStartup; } public bool ResetSecondaryNetworkGroup(Network.Networkable net) { if (net == null) return false; net.SwitchSecondaryGroup(null); net.OnSubscriptionChange(); return true; } public void OnDestroy() { vehicle = null; group = null; if(allPassengers != null) { foreach (BasePlayer foundPlayer in allPassengers) { ResetSecondaryNetworkGroup(foundPlayer?.net); } } allPassengers = null; player = null; if(!onUnload) _instance.componentList.Remove(this); Destroy(this); } } #endregion Custom Class } }