From 1f22f8ab6ad952065d24f75087f4b54f3a2ac53d Mon Sep 17 00:00:00 2001 From: clusterfack Date: Sat, 3 Feb 2018 22:35:42 -0600 Subject: [PATCH] Power System (Complete) (#25) * Power Commit 1 * Commit 2 * Powernet Part2, All components essentially complete * Commit 4 * Commit 5 * Commit 6 Creates prototypes * Finishes powernet code alpha Adds prototypes and logic for powernet updating and regeneration * Adds onremove functionality to all components Without these bits of logic nothing makes any sense! * And this * Fixes a lot of bugs * Fix powernet thinking devices are duplicates * Fix bug and add comments * woop woop thats the sound of the police --- Content.Client/EntryPoint.cs | 6 + Content.Server/Content.Server.csproj | 68 +-- Content.Server/EntryPoint.cs | 7 + .../Components/Power/PowerDevice.cs | 294 +++++++++++++ .../Power/PowerGeneratorComponent.cs | 105 +++++ .../Components/Power/PowerNodeComponent.cs | 109 +++++ .../Power/PowerProviderComponent.cs | 201 +++++++++ .../Components/Power/PowerStorageComponent.cs | 201 +++++++++ .../Power/PowerTransferComponent.cs | 129 ++++++ .../GameObjects/Components/Power/Powernet.cs | 402 ++++++++++++++++++ .../GameObjects/EntitySystems/PowerSystem.cs | 47 ++ Resources/Prototypes/Entities/Power.yml | 105 ++++- Resources/textures/Objects/generator.png | Bin 0 -> 452 bytes Resources/textures/Objects/provider.png | Bin 0 -> 195 bytes Resources/textures/Objects/storage.png | Bin 0 -> 869 bytes Resources/textures/Objects/wiredmachine.png | Bin 0 -> 742 bytes .../textures/Objects/wirelessmachine.png | Bin 0 -> 324 bytes 17 files changed, 1642 insertions(+), 32 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Power/PowerDevice.cs create mode 100644 Content.Server/GameObjects/Components/Power/PowerGeneratorComponent.cs create mode 100644 Content.Server/GameObjects/Components/Power/PowerNodeComponent.cs create mode 100644 Content.Server/GameObjects/Components/Power/PowerProviderComponent.cs create mode 100644 Content.Server/GameObjects/Components/Power/PowerStorageComponent.cs create mode 100644 Content.Server/GameObjects/Components/Power/PowerTransferComponent.cs create mode 100644 Content.Server/GameObjects/Components/Power/Powernet.cs create mode 100644 Content.Server/GameObjects/EntitySystems/PowerSystem.cs create mode 100644 Resources/textures/Objects/generator.png create mode 100644 Resources/textures/Objects/provider.png create mode 100644 Resources/textures/Objects/storage.png create mode 100644 Resources/textures/Objects/wiredmachine.png create mode 100644 Resources/textures/Objects/wirelessmachine.png diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index a05012f210..9ddf27ee63 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -18,6 +18,12 @@ namespace Content.Client factory.RegisterIgnore("Damageable"); factory.RegisterIgnore("Destructible"); factory.RegisterIgnore("Temperature"); + factory.RegisterIgnore("PowerTransfer"); + factory.RegisterIgnore("PowerNode"); + factory.RegisterIgnore("PowerProvider"); + factory.RegisterIgnore("PowerDevice"); + factory.RegisterIgnore("PowerStorage"); + factory.RegisterIgnore("PowerGenerator"); factory.Register(); factory.RegisterReference(); diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 8d58127e11..7ca6c53904 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -1,6 +1,6 @@  - + Debug AnyCPU @@ -39,14 +39,14 @@ x64 - - - - - - - - + + + + + + + + $(SolutionDir)packages\YamlDotNet.4.2.1\lib\net35\YamlDotNet.dll @@ -55,22 +55,30 @@ - + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -92,13 +100,13 @@ SS14.Shared - - - + + + - - - - + + + + - + \ No newline at end of file diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 1463a8508c..a9d5ca592d 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -1,5 +1,6 @@  using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.Power; using Content.Server.Interfaces.GameObjects; using SS14.Server; using SS14.Server.Interfaces; @@ -59,6 +60,12 @@ namespace Content.Server factory.Register(); factory.Register(); factory.Register(); + factory.Register(); + factory.Register(); + factory.Register(); + factory.Register(); + factory.Register(); + factory.Register(); } /// diff --git a/Content.Server/GameObjects/Components/Power/PowerDevice.cs b/Content.Server/GameObjects/Components/Power/PowerDevice.cs new file mode 100644 index 0000000000..d923c43995 --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/PowerDevice.cs @@ -0,0 +1,294 @@ +using SS14.Server.GameObjects; +using SS14.Shared.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Utility; +using System.Collections.Generic; +using System.Linq; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Component that requires power to function + /// + public class PowerDeviceComponent : Component + { + public override string Name => "PowerDevice"; + + /// + /// The method of draw we will try to use to place our load set via component parameter, defaults to using power providers + /// + public virtual DrawTypes Drawtype { get; protected set; } = DrawTypes.Provider; + + /// + /// The power draw method we are currently connected to and using + /// + public DrawTypes Connected { get; protected set; } = DrawTypes.None; + + public bool _powered = false; + /// + /// Status indicator variable for powered + /// + public virtual bool Powered + { + get => _powered; + set => SetPowered(value); + } + + /// + /// Priority for powernet draw, lower will draw first, defined in powernet.cs + /// + public virtual Powernet.Priority Priority { get; protected set; } = Powernet.Priority.Medium; + + + private float _load = 100; //arbitrary magic number to start + /// + /// Power load from this entity + /// + public float Load + { + get => _load; + set { UpdateLoad(value); } + } + + /// + /// All the power providers that we are within range of + /// + public List AvailableProviders = new List(); + + + private PowerProviderComponent _provider; + /// + /// A power provider that will handle our load, if we are linked to any + /// + public PowerProviderComponent Provider + { + get => _provider; + set { + Connected = DrawTypes.Provider; + if (_provider != null) + { + _provider.RemoveDevice(this); + } + + if(value != null) + { + _provider = value; + _provider.AddDevice(this); + } + else + { + Connected = DrawTypes.None; + } + } + } + + public override void OnAdd(IEntity owner) + { + base.OnAdd(owner); + + if (Drawtype == DrawTypes.Both || Drawtype == DrawTypes.Node) + { + if (!owner.TryGetComponent(out PowerNodeComponent node)) + { + var factory = IoCManager.Resolve(); + node = factory.GetComponent(); + owner.AddComponent(node); + } + node.OnPowernetConnect += PowernetConnect; + node.OnPowernetDisconnect += PowernetDisconnect; + node.OnPowernetRegenerate += PowernetRegenerate; + } + } + + public override void OnRemove() + { + if (Owner.TryGetComponent(out PowerNodeComponent node)) + { + if(node.Parent != null) + { + node.Parent.RemoveDevice(this); + } + + node.OnPowernetConnect -= PowernetConnect; + node.OnPowernetDisconnect -= PowernetDisconnect; + node.OnPowernetRegenerate -= PowernetRegenerate; + } + + base.OnRemove(); + } + + public override void LoadParameters(YamlMappingNode mapping) + { + if (mapping.TryGetNode("Drawtype", out YamlNode node)) + { + Drawtype = node.AsEnum(); + } + if (mapping.TryGetNode("Load", out node)) + { + Load = node.AsFloat(); + } + if (mapping.TryGetNode("Priority", out node)) + { + Priority = node.AsEnum(); + } + } + + private void UpdateLoad(float value) + { + var oldLoad = _load; + _load = value; + if(Connected == DrawTypes.Node) + { + var node = Owner.GetComponent(); + node.Parent.UpdateDevice(this, oldLoad); + } + else if(Connected == DrawTypes.Provider) + { + Provider.UpdateDevice(this, oldLoad); + } + } + + /// + /// Changes behavior when receiving a command to become powered or depowered + /// + /// + public virtual void SetPowered(bool value) + { + //Let them set us to true + if (value == true) + { + _powered = true; + return; + } + + //A powernet has decided we will not be powered this tick, lets try to power ourselves + if (value == false && Owner.TryGetComponent(out PowerStorageComponent storage)) + { + if (storage.CanDeductCharge(Load)) + { + storage.DeductCharge(Load); + _powered = true; + return; + } + } + + //For some reason above we could not power ourselves, we depower + _powered = false; + return; + } + + /// + /// Register a new power provider as a possible connection to this device + /// + /// + public void AddProvider(PowerProviderComponent provider) + { + AvailableProviders.Add(provider); + + if(Connected != DrawTypes.Node) + { + ConnectToBestProvider(); + } + } + + /// + /// Find the nearest registered power provider and connect to it + /// + private void ConnectToBestProvider() + { + //Any values we can connect to or are we already connected to a node, cancel! + if (!AvailableProviders.Any() || Connected == DrawTypes.Node) + return; + + //Get the starting value for our loop + var position = Owner.GetComponent().WorldPosition; + var bestprovider = AvailableProviders[0]; + + //If we are already connected to a power provider we need to do a loop to find the nearest one, otherwise skip it and use first entry + if (Connected == DrawTypes.Provider) + { + var bestdistance = (bestprovider.Owner.GetComponent().WorldPosition - position).LengthSquared; + + foreach (var availprovider in AvailableProviders) + { + //Find distance to new provider + var distance = (availprovider.Owner.GetComponent().WorldPosition - position).LengthSquared; + + //If new provider distance is shorter it becomes new best possible provider + if (distance < bestdistance) + { + bestdistance = distance; + bestprovider = availprovider; + } + } + } + + if(Provider != bestprovider) + Provider = bestprovider; + } + + /// + /// Remove a power provider from being a possible connection to this device + /// + /// + public void RemoveProvider(PowerProviderComponent provider) + { + if (!AvailableProviders.Contains(provider)) + return; + + AvailableProviders.Remove(provider); + + if (Connected != DrawTypes.Node) + { + ConnectToBestProvider(); + } + } + + /// + /// Node has become anchored to a powernet + /// + /// + /// + private void PowernetConnect(object sender, PowernetEventArgs eventarg) + { + //This sets connected = none so it must be first + Provider = null; + + eventarg.Powernet.AddDevice(this); + Connected = DrawTypes.Node; + } + + /// + /// Powernet wire was remove so we need to regenerate the powernet + /// + /// + /// + private void PowernetRegenerate(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddDevice(this); + } + + /// + /// Node has become unanchored from a powernet + /// + /// + /// + private void PowernetDisconnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.RemoveDevice(this); + Connected = DrawTypes.None; + + ConnectToBestProvider(); + } + } + + public enum DrawTypes + { + None = 0, + Node = 1, + Provider = 2, + Both = 3 + } +} diff --git a/Content.Server/GameObjects/Components/Power/PowerGeneratorComponent.cs b/Content.Server/GameObjects/Components/Power/PowerGeneratorComponent.cs new file mode 100644 index 0000000000..92d5490108 --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/PowerGeneratorComponent.cs @@ -0,0 +1,105 @@ +using SS14.Shared.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Log; +using SS14.Shared.Utility; +using System; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Component that creates power and supplies it to the powernet + /// + public class PowerGeneratorComponent : Component + { + public override string Name => "PowerGenerator"; + + /// + /// Power supply from this entity + /// + private float _supply = 1000; //arbitrary initial magic number to start + public float Supply + { + get => _supply; + set { UpdateSupply(value); } + } + + public override void LoadParameters(YamlMappingNode mapping) + { + if (mapping.TryGetNode("Supply", out YamlNode node)) + { + Supply = node.AsFloat(); + } + } + + public override void OnAdd(IEntity owner) + { + base.OnAdd(owner); + + if (!owner.TryGetComponent(out PowerNodeComponent node)) + { + var factory = IoCManager.Resolve(); + node = factory.GetComponent(); + owner.AddComponent(node); + } + node.OnPowernetConnect += PowernetConnect; + node.OnPowernetDisconnect += PowernetDisconnect; + node.OnPowernetRegenerate += PowernetRegenerate; + } + + public override void OnRemove() + { + if (Owner.TryGetComponent(out PowerNodeComponent node)) + { + if (node.Parent != null) + { + node.Parent.RemoveGenerator(this); + } + + node.OnPowernetConnect -= PowernetConnect; + node.OnPowernetDisconnect -= PowernetDisconnect; + node.OnPowernetRegenerate -= PowernetRegenerate; + } + + base.OnRemove(); + } + + private void UpdateSupply(float value) + { + _supply = value; + var node = Owner.GetComponent(); + node.Parent.UpdateGenerator(this); + } + + /// + /// Node has become anchored to a powernet + /// + /// + /// + private void PowernetConnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddGenerator(this); + } + + /// + /// Node has had its powernet regenerated + /// + /// + /// + private void PowernetRegenerate(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddGenerator(this); + } + + /// + /// Node has become unanchored from a powernet + /// + /// + /// + private void PowernetDisconnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.RemoveGenerator(this); + } + } +} diff --git a/Content.Server/GameObjects/Components/Power/PowerNodeComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNodeComponent.cs new file mode 100644 index 0000000000..a549c84d6a --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/PowerNodeComponent.cs @@ -0,0 +1,109 @@ +using SS14.Server.GameObjects; +using SS14.Server.Interfaces.GameObjects; +using SS14.Shared.GameObjects; +using SS14.Shared.IoC; +using System; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Component that connects to the powernet + /// + public class PowerNodeComponent : Component + { + public override string Name => "PowerNode"; + + /// + /// The powernet this node is connected to + /// + public Powernet Parent; + + /// + /// An event handling when this node connects to a powernet + /// + public event EventHandler OnPowernetConnect; + + /// + /// An event handling when this node disconnects from a powernet + /// + public event EventHandler OnPowernetDisconnect; + + /// + /// An event that registers us to a regenerating powernet + /// + public event EventHandler OnPowernetRegenerate; + + public override void Initialize() + { + TryCreatePowernetConnection(); + } + + public override void OnRemove() + { + DisconnectFromPowernet(); + + base.OnRemove(); + } + + /// + /// Find a nearby wire which will have a powernet and connect ourselves to its powernet + /// + public void TryCreatePowernetConnection() + { + var _emanager = IoCManager.Resolve(); + var position = Owner.GetComponent().WorldPosition; + var wires = _emanager.GetEntitiesIntersecting(Owner) + .Where(x => x.HasComponent()) + .OrderByDescending(x => (x.GetComponent().WorldPosition - position).Length); + var choose = wires.FirstOrDefault(); + if(choose != null) + ConnectToPowernet(choose.GetComponent().Parent); + } + + /// + /// Triggers event telling power components that we connected to a powernet + /// + /// + public void ConnectToPowernet(Powernet toconnect) + { + Parent = toconnect; + Parent.Nodelist.Add(this); + OnPowernetConnect?.Invoke(this, new PowernetEventArgs(Parent)); + } + + /// + /// Triggers event telling power components that we haven't disconnected but have readded ourselves to a regenerated powernet + /// + /// + public void RegeneratePowernet(Powernet toconnect) + { + //This removes the device from things that will be powernet disconnected when dirty powernet is killed + Parent.Nodelist.Remove(this); + + Parent = toconnect; + Parent.Nodelist.Add(this); + OnPowernetRegenerate?.Invoke(this, new PowernetEventArgs(Parent)); + } + + /// + /// Triggers event telling power components we have exited any powernets + /// + public void DisconnectFromPowernet() + { + Parent.Nodelist.Remove(this); + OnPowernetDisconnect?.Invoke(this, new PowernetEventArgs(Parent)); + Parent = null; + } + } + + public class PowernetEventArgs : EventArgs + { + public PowernetEventArgs(Powernet powernet) + { + Powernet = powernet; + } + + public Powernet Powernet { get; } + } +} diff --git a/Content.Server/GameObjects/Components/Power/PowerProviderComponent.cs b/Content.Server/GameObjects/Components/Power/PowerProviderComponent.cs new file mode 100644 index 0000000000..8445481de2 --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/PowerProviderComponent.cs @@ -0,0 +1,201 @@ +using SS14.Server.GameObjects; +using SS14.Server.Interfaces.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Log; +using SS14.Shared.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Component that wirelessly connects and powers devices, connects to powernet via node and can be combined with internal storage component + /// + public class PowerProviderComponent : PowerDeviceComponent + { + public override string Name => "PowerProvider"; + + /// + public override DrawTypes Drawtype { get; protected set; } = DrawTypes.Node; + + /// + /// Variable that determines the range that the power provider will try to supply power to + /// + public int PowerRange { get; private set; } = 0; + + /// + /// List storing all the power devices that we are currently providing power to + /// + public SortedSet Deviceloadlist = new SortedSet(new Powernet.DevicePriorityCompare()); + + public List DepoweredDevices = new List(); + + public override Powernet.Priority Priority { get; protected set; } = Powernet.Priority.Provider; + + public override void LoadParameters(YamlMappingNode mapping) + { + if (mapping.TryGetNode("Range", out YamlNode node)) + { + PowerRange = node.AsInt(); + } + if (mapping.TryGetNode("Priority", out node)) + { + Priority = node.AsEnum(); + } + } + + /// + public override void SetPowered(bool value) + { + //Let them set us true, we must now power all the devices that rely on us for energy + if (value == true) + { + PowerAllDevices(); + return; + } + + //A powernet has decided we will not be powered this tick, lets try to power ourselves + if (value == false && Owner.TryGetComponent(out PowerStorageComponent storage)) + { + //Can the storage cover powering all our devices and us? If so power all + if (storage.CanDeductCharge(Load)) + { + storage.DeductCharge(Load); + _powered = true; + return; + } + //Does the storage even have any power to give us? If so power as much as we can + else if (storage.RequestAllCharge() != 0) + { + var depowervalue = storage.RequestAllCharge() - Load; + _powered = true; + //See code in powernet for same functionality + foreach (var device in Deviceloadlist) + { + device.Powered = false; + DepoweredDevices.Add(device); + depowervalue -= device.Load; + if (depowervalue < 0) + break; + } + return; + } + //Storage doesn't have anything, depower everything + else if(storage.RequestAllCharge() == 0) + { + DepowerAllDevices(); + return; + } + } + + //For some reason above we could not power ourselves, we depower ourselves and all devices + DepowerAllDevices(); + return; + } + + private void PowerAllDevices() + { + _powered = true; + foreach (var device in DepoweredDevices) + { + device.Powered = true; + } + DepoweredDevices.Clear(); + } + + private void DepowerAllDevices() + { + _powered = false; + foreach (var device in DepoweredDevices) + { + device.Powered = false; + } + } + + private void PowernetConnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddDevice(this); + Connected = DrawTypes.Node; + + //Find devices within range to take under our control + var _emanager = IoCManager.Resolve(); + var position = Owner.GetComponent().WorldPosition; + var entities = _emanager.GetEntitiesInRange(Owner, PowerRange) + .Where(x => x.HasComponent()); + + + foreach (var entity in entities) + { + var device = entity.GetComponent(); + + //Make sure the device can accept power providers to give it power + if (device.Drawtype == DrawTypes.Provider || device.Drawtype == DrawTypes.Both) + { + device.AddProvider(this); + } + } + } + + private void PowernetRegenerate(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddDevice(this); + } + + private void PowernetDisconnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.RemoveDevice(this); + Connected = DrawTypes.None; + + //We don't want to make the devices under us think we're still a valid provider if we have no powernet to connect to + foreach (var device in Deviceloadlist) + { + device.RemoveProvider(this); + } + } + + /// + /// Register a continuous load from a device connected to the powernet + /// + public void AddDevice(PowerDeviceComponent device) + { + Deviceloadlist.Add(device); + Load += device.Load; + if (!device.Powered) + DepoweredDevices.Add(device); + } + + /// + /// Update one of the loads from a deviceconnected to the powernet + /// + public void UpdateDevice(PowerDeviceComponent device, float oldLoad) + { + if (Deviceloadlist.Contains(device)) + { + Load -= oldLoad; + Load += device.Load; + } + } + + /// + /// Remove a continuous load from a device connected to the powernet + /// + public void RemoveDevice(PowerDeviceComponent device) + { + if (Deviceloadlist.Contains(device)) + { + Load -= device.Load; + Deviceloadlist.Remove(device); + if (DepoweredDevices.Contains(device)) + DepoweredDevices.Remove(device); + } + else + { + var name = device.Owner.Prototype.Name; + Logger.Log(String.Format("We tried to remove a device twice from the same {0} somehow, prototype {1}", Name, name)); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Power/PowerStorageComponent.cs b/Content.Server/GameObjects/Components/Power/PowerStorageComponent.cs new file mode 100644 index 0000000000..184fc079f7 --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/PowerStorageComponent.cs @@ -0,0 +1,201 @@ +using SS14.Shared.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Utility; +using System; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Feeds energy from the powernet and may have the ability to supply back into it + /// + public class PowerStorageComponent : Component + { + public override string Name => "PowerStorage"; + + /// + /// Maximum amount of energy the internal battery can store + /// + public float Capacity { get; private set; } = 10000; //arbitrary value replace + + /// + /// Energy the battery is currently storing + /// + public float Charge { get; private set; } = 0; + + /// + /// Rate at which energy will be taken to charge internal battery + /// + public float ChargeRate { get; private set; } = 1000; + + /// + /// Rate at which energy will be distributed to the powernet if needed + /// + public float DistributionRate { get; private set; } = 1000; + + private bool _chargepowernet = false; + + /// + /// Do we distribute power into the powernet from our stores if the powernet requires it? + /// + public bool ChargePowernet + { + get => _chargepowernet; + set + { + _chargepowernet = value; + if (Owner.TryGetComponent(out PowerNodeComponent node)) + { + if (node.Parent != null) + node.Parent.UpdateStorageType(this); + } + } + } + + + public override void LoadParameters(YamlMappingNode mapping) + { + if (mapping.TryGetNode("Capacity", out YamlNode node)) + { + Capacity = node.AsFloat(); + } + if (mapping.TryGetNode("Charge", out node)) + { + Charge = node.AsFloat(); + } + if (mapping.TryGetNode("ChargeRate", out node)) + { + ChargeRate = node.AsFloat(); + } + if (mapping.TryGetNode("DistributionRate", out node)) + { + DistributionRate = node.AsFloat(); + } + if (mapping.TryGetNode("ChargePowernet", out node)) + { + _chargepowernet = node.AsBool(); + } + } + + public override void OnAdd(IEntity owner) + { + base.OnAdd(owner); + + if (!owner.TryGetComponent(out PowerNodeComponent node)) + { + var factory = IoCManager.Resolve(); + node = factory.GetComponent(); + owner.AddComponent(node); + } + node.OnPowernetConnect += PowernetConnect; + node.OnPowernetDisconnect += PowernetDisconnect; + node.OnPowernetRegenerate += PowernetRegenerate; + } + + public override void OnRemove() + { + if (Owner.TryGetComponent(out PowerNodeComponent node)) + { + if (node.Parent != null) + { + node.Parent.RemovePowerStorage(this); + } + + node.OnPowernetConnect -= PowernetConnect; + node.OnPowernetDisconnect -= PowernetDisconnect; + node.OnPowernetRegenerate -= PowernetRegenerate; + } + + base.OnRemove(); + } + + /// + /// Checks if the storage can supply the amount of charge directly requested + /// + public bool CanDeductCharge(float todeduct) + { + if (Charge > todeduct) + return true; + return false; + } + + /// + /// Deducts the requested charge from the energy storage + /// + public void DeductCharge(float todeduct) + { + Charge = Math.Min(0, Charge - todeduct); + } + + /// + /// Returns all possible charge available from the energy storage + /// + public float RequestAllCharge() + { + return Math.Min(ChargeRate, Capacity - Charge); + } + + /// + /// Returns the charge available from the energy storage + /// + public float RequestCharge() + { + return Math.Min(ChargeRate, Capacity - Charge); + } + + /// + /// Returns the charge available from the energy storage + /// + public float AvailableCharge() + { + return Math.Min(DistributionRate, Charge); + } + + /// + /// Gives the storage one full tick of charging its energy storage + /// + public void ChargePowerTick() + { + Charge = Math.Max(Charge + ChargeRate, Capacity); + } + + /// + /// Takes from the storage one full tick of energy + /// + public void RetrievePassiveStorage() + { + Charge = Math.Min(Charge - DistributionRate, 0); + } + + /// + /// Node has become anchored to a powernet + /// + /// + /// + private void PowernetConnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddPowerStorage(this); + } + + /// + /// Node has had its powernet regenerated + /// + /// + /// + private void PowernetRegenerate(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.AddPowerStorage(this); + } + + /// + /// Node has become unanchored from a powernet + /// + /// + /// + private void PowernetDisconnect(object sender, PowernetEventArgs eventarg) + { + eventarg.Powernet.RemovePowerStorage(this); + } + } +} diff --git a/Content.Server/GameObjects/Components/Power/PowerTransferComponent.cs b/Content.Server/GameObjects/Components/Power/PowerTransferComponent.cs new file mode 100644 index 0000000000..d1f155e3ca --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/PowerTransferComponent.cs @@ -0,0 +1,129 @@ +using SS14.Server.GameObjects; +using SS14.Server.Interfaces.GameObjects; +using SS14.Shared.GameObjects; +using SS14.Shared.IoC; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Component to transfer power to nearby components, can create powernets and connect to nodes + /// + public class PowerTransferComponent : Component + { + public override string Name => "PowerTransfer"; + + /// + /// The powernet this component is connected to + /// + public Powernet Parent; + + public bool Regenerating { get; set; } = false; + + public override void Initialize() + { + if(Parent == null) + { + SpreadPowernet(); + } + } + + public override void OnRemove() + { + DisconnectFromPowernet(); + + base.OnRemove(); + } + + /// + /// Searches for local powernets to connect to, otherwise creates its own, and spreads powernet to nearby entities + /// + public void SpreadPowernet() + { + var _emanager = IoCManager.Resolve(); + var position = Owner.GetComponent().WorldPosition; + var wires = _emanager.GetEntitiesInRange(Owner, 0.1f) //arbitrarily low, just scrape things //wip + .Where(x => x.HasComponent()); + + //we have no parent so lets find a partner we can join his powernet + if(Parent == null || Regenerating) + { + foreach (var wire in wires) + { + var ptc = wire.GetComponent(); + if (ptc.CanConnectTo()) + { + ConnectToPowernet(ptc.Parent); + break; + } + } + + //we couldn't find a partner so none must have spread yet, lets make our own powernet to spread + if (Parent == null || Regenerating) + { + var powernew = new Powernet(); + ConnectToPowernet(powernew); + } + } + + //Find nodes intersecting us and if not already assigned to a powernet assign them to us + var nodes = _emanager.GetEntitiesIntersecting(Owner) + .Where(x => x.HasComponent()) + .Select(x => x.GetComponent()); + + foreach(var node in nodes) + { + if(node.Parent == null) + { + node.ConnectToPowernet(Parent); + } + else if(node.Parent.Dirty) + { + node.RegeneratePowernet(Parent); + } + } + + //spread powernet to nearby wires which haven't got one yet, and tell them to spread as well + foreach (var wire in wires) + { + var ptc = wire.GetComponent(); + if (ptc.Parent == null || Regenerating) + { + ptc.ConnectToPowernet(Parent); + SpreadPowernet(); + } + else if(ptc.Parent != Parent && !ptc.Parent.Dirty) + { + Parent.MergePowernets(ptc.Parent); + } + } + } + + /// + /// Called when connecting to a new powernet, either on creation or on regeneration + /// + /// + public void ConnectToPowernet(Powernet toconnect) + { + Parent = toconnect; + Parent.Wirelist.Add(this); + Regenerating = false; + } + + /// + /// Called when we are removed and telling the powernet that it is now dirty and must regenerate + /// + public void DisconnectFromPowernet() + { + Parent.Wirelist.Remove(this); + Parent.Dirty = true; + Parent = null; + } + + + public bool CanConnectTo() + { + return Parent != null && Parent.Dirty == false && !Regenerating; + } + } +} diff --git a/Content.Server/GameObjects/Components/Power/Powernet.cs b/Content.Server/GameObjects/Components/Power/Powernet.cs new file mode 100644 index 0000000000..1272106a59 --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/Powernet.cs @@ -0,0 +1,402 @@ +using Content.Shared.GameObjects.EntitySystems; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Log; +using System; +using System.Collections.Generic; + +namespace Content.Server.GameObjects.Components.Power +{ + /// + /// Master class for group of powertransfercomponents, takes in and distributes power via nodes + /// + public class Powernet + { + public Powernet() + { + var EntitySystemManager = IoCManager.Resolve(); + EntitySystemManager.GetEntitySystem().Powernets.Add(this); + } + + /// + /// The entities that make up the powernet's physical location and allow powernet connection + /// + public List Wirelist { get; set; } = new List(); + + /// + /// Entities that connect directly to the powernet through PTC above to add power or add power load + /// + public List Nodelist { get; set; } = new List(); + + /// + /// Subset of nodelist that adds a continuous power supply to the network + /// + public Dictionary Generatorlist { get; set; } = new Dictionary(); + + /// + /// Subset of nodelist that draw power, stores information on current continuous powernet load + /// + public SortedSet Deviceloadlist { get; set; } = new SortedSet(new DevicePriorityCompare()); + + /// + /// Comparer that keeps the device dictionary sorted by powernet priority + /// + public class DevicePriorityCompare : IComparer + { + public int Compare(PowerDeviceComponent x, PowerDeviceComponent y) + { + int compare = y.Priority.CompareTo(x.Priority); + + //If the comparer returns 0 sortedset will believe it is a duplicate and return 0, so return 1 instead + if (compare == 0 && !x.Equals(y)) + { + return 1; + } + return compare; + } + } + + /// + /// Priority that a device will receive power if powernet cannot supply every device + /// + public enum Priority + { + Necessary, + High, + Medium, + Low, + Provider, + Unnecessary + } + + /// + /// All the devices that have been depowered by this powernet or depowered prior to being absorted into this powernet + /// + public List DepoweredDevices { get; set; } = new List(); + + /// + /// A list of the energy storage components that will feed the powernet if necessary, and if there is enough power feed itself + /// + public List PowerStorageSupplierlist { get; set; } = new List(); + + /// + /// A list of energy storage components that will never feed the powernet, will try to draw energy to feed themselves if possible + /// + public List PowerStorageConsumerlist { get; set; } = new List(); + + /// + /// Static counter of all continuous load placed from devices on this power network + /// + public float Load { get; private set; } = 0; + + /// + /// Static counter of all continiuous supply from generators on this power network + /// + public float Supply { get; private set; } = 0; + + /// + /// Variable that causes powernet to be regenerated from its wires during the next update cycle + /// + public bool Dirty { get; set; } = false; + + public void Update(float frametime) + { + float activesupply = Supply; + float activeload = Load; + + float storagedemand = 0; + + foreach (var supply in PowerStorageConsumerlist) + { + storagedemand += supply.RequestCharge(); + } + + float passivesupply = 0; + float passivedemand = 0; + + foreach (var supply in PowerStorageSupplierlist) + { + passivesupply += supply.AvailableCharge(); + passivedemand += supply.RequestCharge(); + } + + + //If we have enough power to feed all load and storage demand, then feed everything + if (activesupply > activeload + storagedemand + passivedemand) + { + PowerAllDevices(); + ChargeActiveStorage(); + ChargePassiveStorage(); + } + //We don't have enough power for the storage powernet suppliers, ignore powering them + else if (activesupply > activeload + storagedemand) + { + PowerAllDevices(); + ChargeActiveStorage(); + } + //We require the storage powernet suppliers to power the remaining storage components and device load + else if (activesupply + passivesupply > activeload + storagedemand) + { + PowerAllDevices(); + ChargeActiveStorage(); + RetrievePassiveStorage(); + } + //We cant afford to fund the storage components, so lets try to power the basic load using our supply and storage supply + else if (activesupply + passivesupply > activeload) + { + PowerAllDevices(); + RetrievePassiveStorage(); + } + //We cant even cover the basic device load, start disabling devices in order of priority until the remaining load is lowered enough to be met + else if (activesupply + passivesupply < activeload) + { + PowerAllDevices(); //This merely makes our inevitable betrayal all the sweeter + RetrievePassiveStorage(); + + var depowervalue = activeload - (activesupply + passivesupply); + + //Providers use same method to recreate functionality + foreach(var device in Deviceloadlist) + { + device.Powered = false; + DepoweredDevices.Add(device); + depowervalue -= device.Load; + if (depowervalue < 0) + break; + } + } + } + + private void PowerAllDevices() + { + foreach(var device in DepoweredDevices) + { + device.Powered = true; + } + DepoweredDevices.Clear(); + } + + private void ChargeActiveStorage() + { + foreach (var storage in PowerStorageConsumerlist) + { + storage.ChargePowerTick(); + } + } + + private void ChargePassiveStorage() + { + foreach (var storage in PowerStorageSupplierlist) + { + storage.ChargePowerTick(); + } + } + + private void RetrievePassiveStorage() + { + foreach (var storage in PowerStorageSupplierlist) + { + storage.ChargePowerTick(); + } + } + + /// + /// Kills a powernet after it is marked dirty and its component have already been regenerated by the powernet system + /// + public void DirtyKill() + { + Wirelist.Clear(); + while(Nodelist.Count != 0) + { + Nodelist[0].DisconnectFromPowernet(); + } + Generatorlist.Clear(); + Deviceloadlist.Clear(); + DepoweredDevices.Clear(); + PowerStorageSupplierlist.Clear(); + PowerStorageConsumerlist.Clear(); + + RemoveFromSystem(); + } + + /// + /// Combines two powernets when they connect via powertransfer components + /// + public void MergePowernets(Powernet toMerge) + { + //TODO: load balance reconciliation between powernets on merge tick here + + foreach (var wire in toMerge.Wirelist) + { + wire.Parent = this; + } + Wirelist.AddRange(toMerge.Wirelist); + toMerge.Wirelist.Clear(); + + foreach (var node in toMerge.Nodelist) + { + node.Parent = this; + } + Nodelist.AddRange(toMerge.Nodelist); + toMerge.Nodelist.Clear(); + + foreach (var generator in toMerge.Generatorlist) + { + Generatorlist.Add(generator.Key, generator.Value); + } + toMerge.Generatorlist.Clear(); + + foreach (var device in toMerge.Deviceloadlist) + { + Deviceloadlist.Add(device); + } + toMerge.Deviceloadlist.Clear(); + + DepoweredDevices.AddRange(toMerge.DepoweredDevices); + toMerge.DepoweredDevices.Clear(); + + PowerStorageSupplierlist.AddRange(toMerge.PowerStorageSupplierlist); + toMerge.PowerStorageSupplierlist.Clear(); + + PowerStorageConsumerlist.AddRange(toMerge.PowerStorageConsumerlist); + toMerge.PowerStorageConsumerlist.Clear(); + + toMerge.RemoveFromSystem(); + } + + /// + /// Removes reference from the powernets list on the powernet system + /// + private void RemoveFromSystem() + { + var EntitySystemManager = IoCManager.Resolve(); + EntitySystemManager.GetEntitySystem().Powernets.Remove(this); + } + + #region Registration + + /// + /// Register a continuous load from a device connected to the powernet + /// + public void AddDevice(PowerDeviceComponent device) + { + Deviceloadlist.Add(device); + Load += device.Load; + if (!device.Powered) + DepoweredDevices.Add(device); + } + + /// + /// Update one of the loads from a deviceconnected to the powernet + /// + public void UpdateDevice(PowerDeviceComponent device, float oldLoad) + { + if(Deviceloadlist.Contains(device)) + { + Load -= oldLoad; + Load += device.Load; + } + } + + /// + /// Remove a continuous load from a device connected to the powernet + /// + public void RemoveDevice(PowerDeviceComponent device) + { + if(Deviceloadlist.Contains(device)) + { + Load -= device.Load; + Deviceloadlist.Remove(device); + if (DepoweredDevices.Contains(device)) + DepoweredDevices.Remove(device); + } + else + { + var name = device.Owner.Prototype.Name; + Logger.Log(String.Format("We tried to remove a device twice from the same powernet somehow, prototype {0}", name)); + } + } + + /// + /// Register a power supply from a generator connected to the powernet + /// + public void AddGenerator(PowerGeneratorComponent generator) + { + Generatorlist.Add(generator, generator.Supply); + Supply += generator.Supply; + } + + /// + /// Update the value supplied from a generator connected to the powernet + /// + public void UpdateGenerator(PowerGeneratorComponent generator) + { + if (Generatorlist.ContainsKey(generator)) + { + Supply -= Generatorlist[generator]; + Generatorlist[generator] = generator.Supply; + Supply += generator.Supply; + } + } + + /// + /// Remove a power supply from a generator connected to the powernet + /// + public void RemoveGenerator(PowerGeneratorComponent generator) + { + if (Generatorlist.ContainsKey(generator)) + { + Supply -= Generatorlist[generator]; + Generatorlist.Remove(generator); + } + else + { + var name = generator.Owner.Prototype.Name; + Logger.Log(String.Format("We tried to remove a device twice from the same power somehow, prototype {1}", name)); + } + } + + /// + /// Register a power supply from a generator connected to the powernet + /// + public void AddPowerStorage(PowerStorageComponent storage) + { + if(storage.ChargePowernet) + PowerStorageSupplierlist.Add(storage); + else + PowerStorageConsumerlist.Add(storage); + } + + //How do I even call this? TODO: fix + public void UpdateStorageType(PowerStorageComponent storage) + { + //If our chargepowernet settings change we need to tell the powernet of this new setting and remove traces of our old setting + if (PowerStorageSupplierlist.Contains(storage)) + PowerStorageSupplierlist.Remove(storage); + if (PowerStorageConsumerlist.Contains(storage)) + PowerStorageConsumerlist.Remove(storage); + + //Apply new setting + if (storage.ChargePowernet) + PowerStorageSupplierlist.Add(storage); + else + PowerStorageConsumerlist.Add(storage); + } + + /// + /// Remove a power supply from a generator connected to the powernet + /// + public void RemovePowerStorage(PowerStorageComponent storage) + { + if (PowerStorageSupplierlist.Contains(storage)) + { + PowerStorageSupplierlist.Remove(storage); + } + if (PowerStorageConsumerlist.Contains(storage)) + { + PowerStorageSupplierlist.Remove(storage); + } + } + #endregion Registration + } +} diff --git a/Content.Server/GameObjects/EntitySystems/PowerSystem.cs b/Content.Server/GameObjects/EntitySystems/PowerSystem.cs new file mode 100644 index 0000000000..f7af4d77e7 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/PowerSystem.cs @@ -0,0 +1,47 @@ +using Content.Server.GameObjects.Components.Power; +using SS14.Shared.GameObjects.System; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared.GameObjects.EntitySystems +{ + public class PowerSystem : EntitySystem + { + public List Powernets = new List(); + + public override void Update(float frametime) + { + for (int i = 0; i < Powernets.Count; i++) + { + var powernet = Powernets[i]; + if (powernet.Dirty) + { + //Tell all the wires of this net to be prepared to create/join new powernets + foreach (var wire in powernet.Wirelist) + { + wire.Regenerating = true; + } + + foreach (var wire in powernet.Wirelist) + { + //Only a few wires should pass this if check since each will create and take all the others into its powernet + if (wire.Regenerating) + wire.SpreadPowernet(); + } + + //At this point all wires will have found/joined new powernet, all capable nodes will have joined them as well and removed themselves from nodelist + powernet.DirtyKill(); + i--; + } + } + + foreach(var powernet in Powernets) + { + powernet.Update(frametime); + } + } + } +} diff --git a/Resources/Prototypes/Entities/Power.yml b/Resources/Prototypes/Entities/Power.yml index c1c294f5c9..313cca842c 100644 --- a/Resources/Prototypes/Entities/Power.yml +++ b/Resources/Prototypes/Entities/Power.yml @@ -4,14 +4,15 @@ components: - type: Transform - type: Clickable + - type: BoundingBox - type: Sprite drawdepth: FloorPlaceable color: Red sprites: - eightdirwire - - type: Icon icon: eightdirwire + - type: PowerTransfer snap: - Wire @@ -22,4 +23,104 @@ name: BlueWire components: - type: Sprite - color: Blue \ No newline at end of file + color: Blue + +- type: entity + id: Generator + name: Generator + components: + - type: Transform + - type: Clickable + - type: BoundingBox + - type: Sprite + drawdepth: FloorPlaceable + sprites: + - generator + - type: Icon + icon: generator + - type: PowerGenerator + +- type: entity + id: WPPnobattery + name: WPPnobattery + components: + - type: Transform + - type: Clickable + - type: BoundingBox + - type: Sprite + drawdepth: FloorPlaceable + sprites: + - provider + - type: Icon + icon: provider + - type: PowerProvider + Range: 5 + Priority: Provider + +- type: entity + parent: WPPnobattery + id: WPP + name: WPP + components: + - type: PowerStorage + Capacity: 1000 + Charge: 1000 + ChargeRate: 200 + ChargePowernet: false + + +- type: entity + id: SMES + name: SMES + components: + - type: Transform + - type: Clickable + - type: BoundingBox + - type: Sprite + drawdepth: FloorPlaceable + sprites: + - storage + - type: Icon + icon: storage + - type: PowerStorage + Capacity: 3000 + Charge: 1000 + ChargeRate: 200 + DistributionRate: 400 + ChargePowernet: true + +- type: entity + id: WiredMachine + name: WiredMachine + components: + - type: Transform + - type: Clickable + - type: BoundingBox + - type: Sprite + drawdepth: FloorPlaceable + sprites: + - wiredmachine + - type: Icon + icon: wiredmachine + - type: PowerDevice + Drawtype: Node + Load: 100 + Priority: High + +- type: entity + id: WirelessMachine + name: WirelessMachine + components: + - type: Transform + - type: Clickable + - type: BoundingBox + - type: Sprite + drawdepth: FloorPlaceable + sprites: + - wirelessmachine + - type: Icon + icon: wirelessmachine + - type: PowerDevice + Drawtype: Both + Load: 200 + Priority: Low \ No newline at end of file diff --git a/Resources/textures/Objects/generator.png b/Resources/textures/Objects/generator.png new file mode 100644 index 0000000000000000000000000000000000000000..b81c082d7bb77fe7bc25ca3e1cef663d327c17ec GIT binary patch literal 452 zcmV;#0XzPQP)@~D=RE4EKE#HPEJllLql|Q zbSEb#OG`^;W@a}xH##~xZ*OlTBqV8RX)rJ_V`F0`CMGT}E?!<43U|Vg=`Lf5sQ=91H0lM~tHY0000VS)*Z&O-K&GLg;e!Vc-o1PG zpMhch`t=NI|A8PtTlODNjhCl(PmIq8~dI=9$ z#0^-q4SbzArZ77yb~(7aS1&Z&#pSHh*!8WCPlS<2RDvlXdm@WZPvf~FXCVV#R>u_4 l%!CGkfF`CQr?xx>hSj^}zqm!7Sr4>e-B;-UF2NLRM!H%cT4J`%4Ls=L#`tLKI!s1G&_m=S7p# zNWZ_oXA-Zkum5Vhy}gZuvMG|jsZ=ryGzYoCq`FX04q?kn@NM+)7yUohV3dhIC z0MzUC&;n7SlarH}LRp9$a8bw^D~iJD=_xWFfD)XYQ5p^dP186yH~=Q)78Us`L}G&Y z=H`a?_jg{aRb{m_97Yzj+ig@;rBE!C(MDtJOm016X~nR+Un#6|-P(Z!ad~>+6ed zw@b6xTqtYHvgmX=6bc1YRgK~;ZGk|cP@vgtQY;o15+LRXWMTn?WnJ4^i@yCwpRKjE z*qBG(k9}uifeioA|J61NnOGpB(U@fX@ZkT-I1QOuAV`-%<`yI?@bdBk!1lv70Ddnn zFmnsiDqt7}w5LwV)`DaO48sUBpRzbJw;)*oVZp<8XhF~a&lbr0UhDwFFlITBtp&+# zCoGt@9p_pgI_8}PmSxSzF%HYJ*x1;Zc@}(pd`tw0dt9&An|ct9mwjC})2pzMn*=8iHD1e zi-UuMu&}UOTU(5ZiYh89m6ergYictyGnST?iHV5mm$Q45 z>~?DEKOuz55TfhB2$_VKAp_KXc-R|;VVW8uBCBcH&zRAO5}1v}twd-pF`tahDuIQ? zB|B~{uh>hA3rc{lrqY?Ll}V>osTx?z<<|?v^?Ytk129&C@&;@&rU5u(K(~M~t_CE* z0k?tc`kh@$_jtvjG^v&y*%t?gymFL1KGBG>%Z2J8&z_!XMcH=2T0Yl`s*Od}lmLuH zslO)F8M`QNTrgJG03%UUifU;7n4u_MfySk}Z?@#?lvgMKX|)vn)itycal6<_-rQa{ zCwo`idG~FUGth|=ulq1bxWe+ z9{TwBkdTl!(*YC|6gfFLW@2J-Zf+eN9WgO6Ws*6n=yE6k0004WQchCkh&o3`UDinf1!5Zcg9-Iqhx<(ALHM+z9~^4qpgRfB`3g7`KVpb`-Ip z?036FCOmy)3^Xc6 z0uQ{({oP6!=X}VAnlN{hDNjwPn^(iFHXh6S_TAGi7k>YTPsaaWZXJRwy{x*;>i+@< WC=#UQFsh9J0000