using Facepunch; using Facepunch.Extend; using Network.Visibility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System.Collections.Generic; using System.Linq; namespace Oxide.Plugins { [Info("Custom Network Range", "WhiteThunder", "1.0.0")] [Description("Customizes the network range (draw distance) of entities to players.")] internal class CustomNetworkRange : CovalencePlugin { #region Fields private NetworkVisibilityGrid _grid; private Configuration _pluginConfig; private Configuration _vanillaConfig; #endregion #region Hooks private void OnServerInitialized() { _grid = Network.Net.sv?.visibility?.provider as NetworkVisibilityGrid; if (_grid == null) { LogError("This server doesn't appear to be using a NetworkVisibilityGrid. This plugin is disabled."); return; } _vanillaConfig = new Configuration() { VisibilityRadiusFar = _grid.visibilityRadiusFar, VisibilityRadiusNear = _grid.visibilityRadiusNear, }; ApplyConfig(_grid, _pluginConfig); } private void Unload() { if (_vanillaConfig == null) return; ApplyConfig(_grid, _vanillaConfig); } #endregion #region Helper Methods private void ApplyConfig(NetworkVisibilityGrid grid, Configuration config) { var changed = false; if (grid.visibilityRadiusFar != config.VisibilityRadiusFar) { Puts($"Updating visibilityRadiusFar from {grid.visibilityRadiusFar} to {config.VisibilityRadiusFar}"); grid.visibilityRadiusFar = config.VisibilityRadiusFar; changed = true; } if (grid.visibilityRadiusNear != config.VisibilityRadiusNear) { Puts($"Updating visibilityRadiusNear from {grid.visibilityRadiusNear} to {config.VisibilityRadiusNear}"); grid.visibilityRadiusNear = config.VisibilityRadiusNear; changed = true; } if (!changed) return; foreach (var player in BasePlayer.activePlayerList) { if (player != null && player.net != null && !player.IsReceivingSnapshot && !player.isCallingUpdateNetworkGroup) { player.isCallingUpdateNetworkGroup = true; player.Invoke(() => { player.UpdateNetworkGroup(); UpdateSubscriptions(player.net); }, UnityEngine.Random.Range(0f, 5f)); } } } // TODO: Replace with vanilla call. private void UpdateSubscriptions(Network.Networkable net) { if (net.subscriber == null) return; List obj = Pool.GetList(); List obj2 = Pool.GetList(); List obj3 = Pool.GetList(); net.sv.visibility.GetVisibleFromFar(net.group, obj3); AddVisibleFromNear(net, net.secondaryGroup, obj3); net.subscriber.subscribed.Compare(obj3, obj, obj2, null); for (int i = 0; i < obj2.Count; i++) { Group group = obj2[i]; net.subscriber.Unsubscribe(group); if (net.handler != null) { net.handler.OnNetworkGroupLeave(group); } } for (int j = 0; j < obj.Count; j++) { Group group2 = obj[j]; net.subscriber.Subscribe(group2); if (net.handler != null) { net.handler.OnNetworkGroupEnter(group2); } } Pool.FreeList(ref obj); Pool.FreeList(ref obj2); Pool.FreeList(ref obj3); } // TODO: Replace with vanilla call. private void AddVisibleFromNear(Network.Networkable net, Group additionalGroup, List groupsVisible) { if (additionalGroup == null) { return; } List obj = Pool.GetList(); net.sv.visibility.GetVisibleFromNear(additionalGroup, obj); for (int i = 0; i < obj.Count; i++) { Group item = obj[i]; if (!groupsVisible.Contains(item)) { groupsVisible.Add(item); } } Pool.FreeList(ref obj); } #endregion #region Configuration private class Configuration : SerializableConfiguration { [JsonProperty("VisibilityRadiusFar")] public int VisibilityRadiusFar = 8; [JsonProperty("VisibilityRadiusNear")] public int VisibilityRadiusNear = 4; } private Configuration GetDefaultConfig() => new Configuration(); #endregion #region Configuration Boilerplate private class SerializableConfiguration { public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary; } 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() .ToDictionary(prop => prop.Name, prop => ToObject(prop.Value)); case JTokenType.Array: return token.Select(ToObject).ToList(); default: return ((JValue)token).Value; } } } private bool MaybeUpdateConfig(SerializableConfiguration config) { var currentWithDefaults = config.ToDictionary(); var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value); return MaybeUpdateConfigDict(currentWithDefaults, currentRaw); } private bool MaybeUpdateConfigDict(Dictionary currentWithDefaults, Dictionary currentRaw) { bool changed = false; foreach (var key in currentWithDefaults.Keys) { object currentRawValue; if (currentRaw.TryGetValue(key, out currentRawValue)) { var defaultDictValue = currentWithDefaults[key] as Dictionary; var currentDictValue = currentRawValue as Dictionary; if (defaultDictValue != null) { 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() => _pluginConfig = GetDefaultConfig(); protected override void LoadConfig() { base.LoadConfig(); try { _pluginConfig = Config.ReadObject(); if (_pluginConfig == null) { throw new JsonException(); } if (MaybeUpdateConfig(_pluginConfig)) { LogWarning("Configuration appears to be outdated; updating and saving"); SaveConfig(); } } catch { LogWarning($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { Log($"Configuration changes saved to {Name}.json"); Config.WriteObject(_pluginConfig, true); } #endregion } }