using System; using System.Collections; using System.Collections.Generic; using System.Linq; using RemoteTech.Modules; using RemoteTech.RangeModel; using RemoteTech.SimpleTypes; using UnityEngine; namespace RemoteTech { ///

/// Class managing the satellites network. /// Acts as a list of vessels in one or more networks. /// public partial class NetworkManager : IEnumerable { public event Action> OnLinkAdd = delegate { }; public event Action> OnLinkRemove = delegate { }; public Dictionary Planets { get; private set; } public Dictionary GroundStations { get; private set; } public Dictionary>> Graph { get; private set; } public int Count { get { return RTCore.Instance.Satellites.Count + GroundStations.Count; } } public static Guid ActiveVesselGuid = new Guid(RTSettings.Instance.ActiveVesselGuid); public ISatellite this[Guid guid] { get { Vessel activeVessel = (FlightGlobals.ActiveVessel == null && HighLogic.LoadedScene == GameScenes.TRACKSTATION ? MapView.MapCamera.target.vessel : FlightGlobals.ActiveVessel); ISatellite vesselSatellite = RTCore.Instance.Satellites[guid]; ISatellite activeSatellite = (guid == ActiveVesselGuid ? RTCore.Instance.Satellites[activeVessel] : null); ISatellite groundSatellite = (GroundStations.ContainsKey(guid) ? GroundStations[guid] : null); return vesselSatellite ?? activeSatellite ?? groundSatellite; } } public List> this[ISatellite sat] { get { if (sat == null) return new List>(); return mConnectionCache.ContainsKey(sat) ? mConnectionCache[sat] : new List>(); } } private const int REFRESH_TICKS = 50; private int mTick; private int mTickIndex; private Dictionary>> mConnectionCache = new Dictionary>>(); public NetworkManager() { Graph = new Dictionary>>(); // Load all planets into a dictionary; Planets = new Dictionary(); foreach (CelestialBody cb in FlightGlobals.Bodies) { Planets[cb.Guid()] = cb; } // Load all ground stations into a dictionary; GroundStations = new Dictionary(); foreach (ISatellite sat in RTSettings.Instance.GroundStations) { try { GroundStations.Add(sat.Guid, sat); OnSatelliteRegister(sat); } catch (Exception e) // Already exists. { RTLog.Notify("A ground station cannot be loaded: " + e.Message, RTLogLevel.LVL1); } } RTCore.Instance.Satellites.OnRegister += OnSatelliteRegister; RTCore.Instance.Satellites.OnUnregister += OnSatelliteUnregister; RTCore.Instance.OnPhysicsUpdate += OnPhysicsUpdate; } public void Dispose() { if (RTCore.Instance != null) { RTCore.Instance.OnPhysicsUpdate -= OnPhysicsUpdate; RTCore.Instance.Satellites.OnRegister -= OnSatelliteRegister; RTCore.Instance.Satellites.OnUnregister -= OnSatelliteUnregister; } } public void FindPath(ISatellite start, IEnumerable commandStations) { var paths = new List>(); foreach (ISatellite root in commandStations.Concat(GroundStations.Values).Where(r => r != start)) { paths.Add(NetworkPathfinder.Solve(start, root, FindNeighbors, RangeModelExtensions.DistanceTo, RangeModelExtensions.DistanceTo)); } mConnectionCache[start] = paths.Where(p => p.Exists).ToList(); mConnectionCache[start].Sort((a, b) => a.Length.CompareTo(b.Length)); start.OnConnectionRefresh(this[start]); } public IEnumerable> FindNeighbors(ISatellite s) { if (!s.Powered) return Enumerable.Empty>(); if (RTSettings.Instance.SignalRelayEnabled) { return Graph[s.Guid].Where(l => l.Target.Powered && l.Target.CanRelaySignal); } else { return Graph[s.Guid].Where(l => l.Target.Powered); } } private void UpdateGraph(ISatellite a) { var result = this.Select(b => GetLink(a, b)).Where(link => link != null).ToList(); // Send events for removed edges foreach (var link in Graph[a.Guid].Except(result)) { OnLinkRemove(a, link); } Graph[a.Guid].Clear(); // Input new edges foreach (var link in result) { Graph[a.Guid].Add(link); OnLinkAdd(a, link); } } public static NetworkLink GetLink(ISatellite sat_a, ISatellite sat_b) { if (sat_a == null || sat_b == null || sat_a == sat_b) return null; if (sat_a.IsInRadioBlackout || sat_b.IsInRadioBlackout) return null; bool los = sat_a.HasLineOfSightWith(sat_b) || RTSettings.Instance.IgnoreLineOfSight; if (!los) return null; switch (RTSettings.Instance.RangeModelType) { case RangeModel.RangeModel.Additive: // NathanKell return RangeModelRoot.GetLink(sat_a, sat_b); default: // Stock range model return RangeModelStandard.GetLink(sat_a, sat_b); } } public void OnPhysicsUpdate() { var count = RTCore.Instance.Satellites.Count; if (count == 0) return; int baseline = (count / REFRESH_TICKS); int takeCount = baseline + (((mTick++ % REFRESH_TICKS) < (count - baseline * REFRESH_TICKS)) ? 1 : 0); IEnumerable commandStations = RTCore.Instance.Satellites.FindCommandStations(); foreach (VesselSatellite s in RTCore.Instance.Satellites.Concat(RTCore.Instance.Satellites).Skip(mTickIndex).Take(takeCount)) { UpdateGraph(s); //("{0} [ E: {1} ]", s.ToString(), Graph[s.Guid].ToDebugString()); // amend this optimisation due to inconsistent connectivity on non-active vessels (eg showing no connection when 3rd-party mods query) //if (s.SignalProcessor.VesselLoaded || HighLogic.LoadedScene == GameScenes.TRACKSTATION || RTCore.Instance.Renderer.ShowMultiPath) if (HighLogic.LoadedScene == GameScenes.TRACKSTATION || HighLogic.LoadedScene == GameScenes.FLIGHT || (HighLogic.LoadedScene == GameScenes.SPACECENTER && API.API.enabledInSPC)) { FindPath(s, commandStations); } } mTickIndex += takeCount; mTickIndex = mTickIndex % RTCore.Instance.Satellites.Count; } private void OnSatelliteUnregister(ISatellite s) { RTLog.Notify("NetworkManager: SatelliteUnregister({0})", s); Graph.Remove(s.Guid); foreach (var list in Graph.Values) { list.RemoveAll(l => l.Target == s); } mConnectionCache.Remove(s); } private void OnSatelliteRegister(ISatellite s) { RTLog.Notify("NetworkManager: SatelliteRegister({0})", s); Graph[s.Guid] = new List>(); } public IEnumerator GetEnumerator() { return RTCore.Instance.Satellites.Cast().Concat(GroundStations.Values).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// Gets the position of a RemoteTech target from its id /// The absolute position or null if is neither /// a satellite nor a celestial body. /// The id of the satellite or celestial body whose position is /// desired. May be the active vessel Guid. /// /// The program state is unchanged in the event of an exception. internal Vector3d? GetPositionFromGuid(Guid targetable) { ISatellite targetSat = this[targetable]; if (targetSat != null) { return targetSat.Position; } if (Planets.ContainsKey(targetable)) { return Planets[targetable].position; } return null; } } public sealed class MissionControlSatellite : ISatellite, IPersistenceLoad { /* Config Node parameters */ [Persistent] private String Guid = new Guid("5105f5a9d62841c6ad4b21154e8fc488").ToString(); [Persistent] private String Name = "Mission Control"; [Persistent] private double Latitude = -0.1313315f; [Persistent] private double Longitude = -74.59484f; [Persistent] private double Height = 75.0f; [Persistent] private int Body = 1; [Persistent] private Color MarkColor = new Color(0.996078f, 0, 0, 1); [Persistent(collectionIndex = "ANTENNA")] private MissionControlAntenna[] Antennas = { new MissionControlAntenna() }; private bool AntennaActivated = true; bool ISatellite.Powered { get { return PowerShutdownFlag ? false : this.AntennaActivated; } } bool ISatellite.Visible { get { return true; } } String ISatellite.Name { get { return Name; } set { Name = value; } } Guid ISatellite.Guid { get { return mGuid; } } Vector3d ISatellite.Position { get { return FlightGlobals.Bodies[Body].GetWorldSurfacePosition(Latitude, Longitude, Height); } } bool ISatellite.IsCommandStation { get { return true; } } bool ISatellite.HasLocalControl { get { return false; } } bool ISatellite.isVessel { get { return false; } } Vessel ISatellite.parentVessel { get { return null; } } CelestialBody ISatellite.Body { get { return FlightGlobals.Bodies[Body]; } } Color ISatellite.MarkColor { get { return MarkColor; } } IEnumerable ISatellite.Antennas { get { return Antennas; } } bool ISatellite.CanRelaySignal { get { return true; } } //not sure if should relay signal. Mission Control can "do" everything isnt it? public Guid mGuid { get; private set; } public IEnumerable MissionControlAntennas { get { return Antennas; } } public bool IsInRadioBlackout { get; set; } // could be EMP public bool PowerShutdownFlag { get; set; } // flag for third-party realism mods void ISatellite.OnConnectionRefresh(List> route) { } public MissionControlSatellite() { this.mGuid = new Guid(Guid); } public void reloadUpgradeableAntennas(int techlvl = 0) { foreach (var antenna in this.Antennas) { antenna.reloadUpgradeableAntennas(techlvl); } } /* * Simple getter + setter. * For being able to add groundstations. */ public void SetDetails(String name, double lat, double longi, double height, int body) { this.Name = name; this.Latitude = lat; this.Longitude = longi; this.Height = height; this.Body = body; this.mGuid = System.Guid.NewGuid (); this.Guid = this.mGuid.ToString (); } public String GetDetails() { return String.Format ("name:{0}, lat={1}, long={2}, height={3}, body={4}", this.Name, this.Latitude, this.Longitude, this.Height, this.Body); } public String GetName() { return this.Name; } public CelestialBody GetBody() { return FlightGlobals.Bodies[this.Body]; } public void SetBodyIndex(int index) { this.Body = index; } void IPersistenceLoad.PersistenceLoad() { foreach (var antenna in Antennas) { antenna.Parent = this; } mGuid = new Guid(Guid); } public override String ToString() { return Name; } /// /// Used currently for debug purposes only. This method can be used to shut down the mission control /// /// true=Missioncontrol on, false=MissionControl off public void togglePower(bool powerswitch) { this.AntennaActivated = powerswitch; } } }