Power Rework (#863)

Co-authored-by: py01 <pyronetics01@gmail.com>
This commit is contained in:
py01
2020-06-28 09:23:26 -06:00
committed by GitHub
parent ffe25de723
commit 23cc6b1d4e
154 changed files with 11253 additions and 3913 deletions

View File

@@ -61,12 +61,6 @@ namespace Content.Client
"Interactable",
"Destructible",
"Temperature",
"PowerTransfer",
"PowerNode",
"PowerProvider",
"PowerDevice",
"PowerStorage",
"PowerGenerator",
"Explosive",
"OnUseTimerTrigger",
"ToolboxElectricalFill",
@@ -92,7 +86,6 @@ namespace Content.Client
"Storeable",
"Dice",
"Construction",
"Apc",
"Door",
"PoweredLight",
"Smes",
@@ -104,8 +97,8 @@ namespace Content.Client
"Ammo",
"HitscanWeaponCapacitor",
"PowerCell",
"WeaponCapacitorCharger",
"PowerCellCharger",
"WeaponCapacitorCharger",
"AiController",
"Computer",
"AsteroidRock",
@@ -175,7 +168,17 @@ namespace Content.Client
"UnarmedCombat",
"TimedSpawner",
"Buckle",
"Strap"
"Strap",
"NodeContainer",
"PowerSupplier",
"PowerConsumer",
"Battery",
"BatteryStorage",
"BatteryDischarger",
"Apc",
"PowerProvider",
"PowerReceiver",
"Wire",
};
foreach (var ignoreName in registerIgnore)

View File

@@ -1,36 +0,0 @@
using Content.Shared.GameObjects.Components.Power;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Players;
namespace Content.Client.GameObjects.Components.Power
{
[RegisterComponent]
public class PowerDebugTool : SharedPowerDebugTool
{
SS14Window LastWindow;
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case OpenDataWindowMsg msg:
if (LastWindow != null && !LastWindow.Disposed)
{
LastWindow.Dispose();
}
LastWindow = new SS14Window()
{
Title = "Power Debug Tool",
};
LastWindow.Contents.AddChild(new Label() { Text = msg.Data });
LastWindow.Open();
break;
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Client.GameObjects.Components.Gravity;
using Content.Server.GameObjects.Components.Gravity;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using NUnit.Framework;
using Robust.Shared.Interfaces.GameObjects;
@@ -40,11 +41,11 @@ namespace Content.IntegrationTests.Tests
generator = entityMan.SpawnEntity("GravityGenerator", new GridCoordinates(new Vector2(0, 0), grid2.Index));
Assert.That(generator.HasComponent<GravityGeneratorComponent>());
Assert.That(generator.HasComponent<PowerDeviceComponent>());
Assert.That(generator.HasComponent<PowerReceiverComponent>());
var generatorComponent = generator.GetComponent<GravityGeneratorComponent>();
var powerComponent = generator.GetComponent<PowerDeviceComponent>();
var powerComponent = generator.GetComponent<PowerReceiverComponent>();
Assert.AreEqual(generatorComponent.Status, GravityGeneratorStatus.Unpowered);
powerComponent.ExternalPowered = true;
powerComponent.NeedsPower = false;
});
server.RunTicks(1);

View File

@@ -1,4 +1,6 @@
using Content.Server.Interfaces;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.Interfaces;
using Content.Server.AI.WorldState;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
@@ -75,7 +77,8 @@ namespace Content.Server
logManager.GetSawmill("Storage").Level = LogLevel.Info;
IoCManager.Resolve<IServerPreferencesManager>().StartInit();
IoCManager.Resolve<INodeGroupFactory>().Initialize();
IoCManager.Resolve<INodeFactory>().Initialize();
}
public override void PostInit()

View File

@@ -1,5 +1,5 @@
using System.Linq;
using Content.Server.GameObjects.Components.Power;
using System.Linq;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
@@ -26,7 +26,7 @@ namespace Content.Server.GameObjects.Components.BarSign
private string _currentSign;
private PowerDeviceComponent _power;
private PowerReceiverComponent _power;
private SpriteComponent _sprite;
[ViewVariables(VVAccess.ReadWrite)]
@@ -80,7 +80,7 @@ namespace Content.Server.GameObjects.Components.BarSign
{
base.Initialize();
_power = Owner.GetComponent<PowerDeviceComponent>();
_power = Owner.GetComponent<PowerReceiverComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
_power.OnPowerStateChanged += PowerOnOnPowerStateChanged;
@@ -88,6 +88,12 @@ namespace Content.Server.GameObjects.Components.BarSign
UpdateSignInfo();
}
public override void OnRemove()
{
_power.OnPowerStateChanged -= PowerOnOnPowerStateChanged;
base.OnRemove();
}
private void PowerOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
{
UpdateSignInfo();

View File

@@ -1,5 +1,6 @@
using Content.Server.Cargo;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Cargo;
using Content.Shared.Prototypes.Cargo;
@@ -65,8 +66,8 @@ namespace Content.Server.GameObjects.Components.Cargo
private bool _requestOnly = false;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private CargoConsoleSystem _cargoConsoleSystem;
public override void Initialize()
@@ -76,7 +77,7 @@ namespace Content.Server.GameObjects.Components.Cargo
Orders = Owner.GetComponent<CargoOrderDatabaseComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CargoConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_cargoConsoleSystem = EntitySystem.Get<CargoConsoleSystem>();
BankAccount = _cargoConsoleSystem.StationAccount;
}

View File

@@ -22,6 +22,7 @@ using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
namespace Content.Server.GameObjects.Components.Chemistry
{
@@ -51,9 +52,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ViewVariables]
private SolutionComponent Solution => _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
///implementing PowerDeviceComponent
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
/// <summary>
@@ -80,7 +80,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
InitializeFromPrototype();
UpdateUserInterface();

View File

@@ -1,3 +1,4 @@
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
@@ -21,8 +22,8 @@ namespace Content.Server.GameObjects.Components.Command
#pragma warning restore 649
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem<RoundEndSystem>();
public override void Initialize()
@@ -31,7 +32,7 @@ namespace Content.Server.GameObjects.Components.Command
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CommunicationsConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface;

View File

@@ -1,4 +1,4 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Shared.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
@@ -12,18 +12,28 @@ namespace Content.Server.GameObjects.Components
{
base.Initialize();
if (Owner.TryGetComponent(out PowerDeviceComponent powerDevice))
if (Owner.TryGetComponent(out PowerReceiverComponent powerReceiver))
{
powerDevice.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
powerReceiver.OnPowerStateChanged += PowerReceiverOnOnPowerStateChanged;
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(ComputerVisuals.Powered, powerDevice.Powered);
appearance.SetData(ComputerVisuals.Powered, powerReceiver.Powered);
}
}
}
private void PowerDeviceOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
public override void OnRemove()
{
if (Owner.TryGetComponent(out PowerReceiverComponent powerReceiver))
{
powerReceiver.OnPowerStateChanged -= PowerReceiverOnOnPowerStateChanged;
}
base.OnRemove();
}
private void PowerReceiverOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{

View File

@@ -1,7 +1,8 @@
using System;
using System.Threading;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Server.GameObjects.Components.VendingMachines;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
@@ -31,7 +32,7 @@ namespace Content.Server.GameObjects.Components.Doors
/// </summary>
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
private WiresComponent _wires;
private CancellationTokenSource _powerWiresPulsedTimerCancel;
@@ -82,7 +83,7 @@ namespace Content.Server.GameObjects.Components.Doors
private void UpdatePowerCutStatus()
{
_powerDevice.IsPowerCut = PowerWiresPulsed ||
_powerReceiver.PowerDisabled = PowerWiresPulsed ||
_wires.IsWireCut(Wires.MainPower) ||
_wires.IsWireCut(Wires.BackupPower);
}
@@ -100,10 +101,20 @@ namespace Content.Server.GameObjects.Components.Doors
public override void Initialize()
{
base.Initialize();
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_wires = Owner.GetComponent<WiresComponent>();
_powerDevice.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
_powerReceiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DoorVisuals.Powered, _powerReceiver.Powered);
}
}
public override void OnRemove()
{
_powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged;
base.OnRemove();
}
private void PowerDeviceOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
@@ -217,7 +228,7 @@ namespace Content.Server.GameObjects.Components.Doors
private bool IsPowered()
{
return _powerDevice.Powered;
return _powerReceiver.Powered;
}
public bool InteractUsing(InteractUsingEventArgs eventArgs)

View File

@@ -1,5 +1,6 @@
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
@@ -27,7 +28,7 @@ namespace Content.Server.GameObjects.Components.Gravity
{
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
private SpriteComponent _sprite;
@@ -37,7 +38,7 @@ namespace Content.Server.GameObjects.Components.Gravity
private GravityGeneratorStatus _status;
public bool Powered => _powerDevice.Powered;
public bool Powered => _powerReceiver.Powered;
public bool SwitchedOn => _switchedOn;
@@ -74,7 +75,7 @@ namespace Content.Server.GameObjects.Components.Gravity
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GravityGeneratorUiKey.Key);
_userInterface.OnReceiveMessage += HandleUIMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
_switchedOn = true;
_intact = true;

View File

@@ -1,8 +1,6 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems;
@@ -18,6 +16,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System;
namespace Content.Server.GameObjects.Components.Interactable
{
@@ -39,13 +38,13 @@ namespace Content.Server.GameObjects.Components.Interactable
private ClothingComponent _clothingComponent;
[ViewVariables]
private PowerCellComponent Cell
private BatteryComponent Cell
{
get
{
if (_cellContainer.ContainedEntity == null) return null;
_cellContainer.ContainedEntity.TryGetComponent(out PowerCellComponent cell);
_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent cell);
return cell;
}
}
@@ -58,7 +57,7 @@ namespace Content.Server.GameObjects.Components.Interactable
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.HasComponent<PowerCellComponent>()) return false;
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
if (Cell != null) return false;
@@ -100,7 +99,8 @@ namespace Content.Server.GameObjects.Components.Interactable
_spriteComponent = Owner.GetComponent<SpriteComponent>();
Owner.TryGetComponent(out _clothingComponent);
_cellContainer =
ContainerManagerComponent.Ensure<ContainerSlot>("flashlight_cell_container", Owner, out var existed);
ContainerManagerComponent.Ensure<ContainerSlot>("flashlight_cell_container", Owner, out _);
Dirty();
}
/// <summary>
@@ -160,7 +160,7 @@ namespace Content.Server.GameObjects.Components.Interactable
// To prevent having to worry about frame time in here.
// Let's just say you need a whole second of charge before you can turn it on.
// Simple enough.
if (cell.AvailableCharge(1) < Wattage)
if (Wattage > cell.CurrentCharge)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/machines/button.ogg", Owner);
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell..."));
@@ -189,7 +189,7 @@ namespace Content.Server.GameObjects.Components.Interactable
if (!Activated) return;
var cell = Cell;
if (cell == null || !cell.TryDeductWattage(Wattage, frameTime)) TurnOff();
if (cell == null || !cell.TryUseCharge(Wattage * frameTime)) TurnOff();
Dirty();
}
@@ -229,14 +229,14 @@ namespace Content.Server.GameObjects.Components.Interactable
return new HandheldLightComponentState(null);
}
if (Cell.AvailableCharge(1) < Wattage)
if (Wattage > Cell.CurrentCharge)
{
// Practically zero.
// This is so the item status works correctly.
return new HandheldLightComponentState(0);
}
return new HandheldLightComponentState(Cell.Charge / Cell.Capacity);
return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge);
}
[Verb]

View File

@@ -81,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
{
if (random.Prob(0.3f))
{
Spawn("CableStack");
Spawn("ApcExtensionCableStack");
}
}
}

View File

@@ -30,11 +30,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
Spawn("Screwdriver");
Spawn("Crowbar");
Spawn("Wirecutter");
Spawn("CableStack");
Spawn("CableStack");
// 5% chance for a pair of fancy insulated gloves, else just a third cable coil.
Spawn(random.Prob(0.05f) ? "GlovesYellow" : "CableStack");
Spawn("ApcExtensionCableStack");
Spawn("MVWireStack");
Spawn(random.Prob(0.05f) ? "GlovesYellow" : "HVWireStack");
}
}
}

View File

@@ -30,7 +30,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
Spawn("Wirecutter");
Spawn("Welder");
Spawn("Multitool");
Spawn("CableStack");
Spawn("ApcExtensionCableStack");
}
}
}

View File

@@ -26,6 +26,7 @@ using Content.Server.Interfaces.Chat;
using Content.Server.BodySystem;
using Content.Shared.BodySystem;
using Robust.Shared.GameObjects.Systems;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
namespace Content.Server.GameObjects.Components.Kitchen
{
@@ -63,7 +64,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
private uint _currentCookTimerTime = 1;
#endregion
private bool _powered => _powerDevice.Powered;
private bool _powered => _powerReceiver.Powered;
private bool _hasContents => _solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0;
private bool _uiDirty = true;
private bool _lostPower = false;
@@ -72,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => _uiDirty = true;
private AudioSystem _audioSystem;
private AppearanceComponent _appearance;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
private BoundUserInterface _userInterface;
private Container _storage;
@@ -95,7 +96,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
_storage = ContainerManagerComponent.Ensure<Container>("microwave_entity_container", Owner, out var existed);
_appearance = Owner.GetComponent<AppearanceComponent>();
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_audioSystem = EntitySystem.Get<AudioSystem>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MicrowaveUiKey.Key);

View File

@@ -14,6 +14,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
namespace Content.Server.GameObjects.Components.Medical
{
@@ -27,9 +28,8 @@ namespace Content.Server.GameObjects.Components.Medical
private readonly Vector2 _ejectOffset = new Vector2(-0.5f, 0f);
public bool IsOccupied => _bodyContainer.ContainedEntity != null;
///implementing PowerDeviceComponent
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
public override void Initialize()
{
@@ -38,7 +38,7 @@ namespace Content.Server.GameObjects.Components.Medical
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MedicalScannerUiKey.Key);
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
UpdateUserInterface();
}

View File

@@ -0,0 +1,73 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
namespace Content.Server.GameObjects.Components.NodeContainer
{
/// <summary>
/// Creates and maintains a set of <see cref="Node"/>s.
/// </summary>
[RegisterComponent]
public class NodeContainerComponent : Component
{
public override string Name => "NodeContainer";
[ViewVariables]
public IReadOnlyList<Node> Nodes => _nodes;
private List<Node> _nodes = new List<Node>();
#pragma warning disable 649
[Dependency] private readonly INodeFactory _nodeFactory;
#pragma warning restore 649
/// <summary>
/// A set of <see cref="NodeGroupID"/>s and <see cref="Node"/> implementation names
/// to be created and held in this container.
/// </summary>
[ViewVariables]
private Dictionary<NodeGroupID, List<string>> _nodeTypes;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _nodeTypes, "nodeTypes", new Dictionary<NodeGroupID, List<string>> { });
}
protected override void Startup()
{
base.Startup();
foreach (var nodeType in _nodeTypes)
{
var nodeGroupID = nodeType.Key;
foreach (var nodeName in nodeType.Value)
{
_nodes.Add(MakeNewNode(nodeName, nodeGroupID, Owner));
}
}
foreach (var node in _nodes)
{
node.OnContainerInitialize();
}
}
public override void OnRemove()
{
foreach (var node in _nodes)
{
node.OnContainerRemove();
}
_nodes = null;
base.OnRemove();
}
private Node MakeNewNode(string nodeName, NodeGroupID groupID, IEntity owner)
{
return _nodeFactory.MakeNode(nodeName, groupID, owner);
}
}
}

View File

@@ -0,0 +1,122 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
public interface IApcNet
{
void AddApc(ApcComponent apc);
void RemoveApc(ApcComponent apc);
void AddPowerProvider(PowerProviderComponent provider);
void RemovePowerProvider(PowerProviderComponent provider);
void UpdatePowerProviderReceivers(PowerProviderComponent provider);
void Update(float frameTime);
}
[NodeGroup(NodeGroupID.Apc)]
public class ApcNetNodeGroup : BaseNetConnectorNodeGroup<BaseApcNetComponent, IApcNet>, IApcNet
{
[ViewVariables]
private readonly Dictionary<ApcComponent, BatteryComponent> _apcBatteries = new Dictionary<ApcComponent, BatteryComponent>();
[ViewVariables]
private readonly Dictionary<PowerProviderComponent, List<PowerReceiverComponent>> _providerReceivers = new Dictionary<PowerProviderComponent, List<PowerReceiverComponent>>();
//Debug property
[ViewVariables]
private int TotalReceivers => _providerReceivers.SelectMany(kvp => kvp.Value).Count();
private IEnumerable<BatteryComponent> AvailableBatteries => _apcBatteries.Where(kvp => kvp.Key.MainBreakerEnabled).Select(kvp => kvp.Value);
public static readonly IApcNet NullNet = new NullApcNet();
#region IApcNet Methods
protected override void SetNetConnectorNet(BaseApcNetComponent netConnectorComponent)
{
netConnectorComponent.Net = this;
}
public void AddApc(ApcComponent apc)
{
_apcBatteries.Add(apc, apc.Battery);
}
public void RemoveApc(ApcComponent apc)
{
_apcBatteries.Remove(apc);
if (!_apcBatteries.Any())
{
foreach (var receiver in _providerReceivers.SelectMany(kvp => kvp.Value))
{
receiver.HasApcPower = false;
}
}
}
public void AddPowerProvider(PowerProviderComponent provider)
{
_providerReceivers.Add(provider, provider.LinkedReceivers.ToList());
}
public void RemovePowerProvider(PowerProviderComponent provider)
{
_providerReceivers.Remove(provider);
}
public void UpdatePowerProviderReceivers(PowerProviderComponent provider)
{
Debug.Assert(_providerReceivers.ContainsKey(provider));
_providerReceivers[provider] = provider.LinkedReceivers.ToList();
}
public void Update(float frameTime)
{
var totalCharge = 0.0;
var totalMaxCharge = 0;
foreach (var battery in AvailableBatteries)
{
totalCharge += battery.CurrentCharge;
totalMaxCharge += battery.MaxCharge;
}
var availablePowerFraction = totalCharge / totalMaxCharge;
foreach (var receiver in _providerReceivers.SelectMany(kvp => kvp.Value))
{
receiver.HasApcPower = TryUsePower(receiver.Load * frameTime);
}
}
private bool TryUsePower(float neededCharge)
{
foreach (var battery in AvailableBatteries)
{
if (battery.TryUseCharge(neededCharge)) //simplification - all power needed must come from one battery
{
return true;
}
}
return false;
}
#endregion
private class NullApcNet : IApcNet
{
public void AddApc(ApcComponent apc) { }
public void AddPowerProvider(PowerProviderComponent provider) { }
public void RemoveApc(ApcComponent apc) { }
public void RemovePowerProvider(PowerProviderComponent provider) { }
public void UpdatePowerProviderReceivers(PowerProviderComponent provider) { }
public void Update(float frameTime) { }
}
}
}

View File

@@ -0,0 +1,36 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using System.Collections.Generic;
using System.Linq;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
public abstract class BaseNetConnectorNodeGroup<TNetConnector, TNetType> : BaseNodeGroup where TNetConnector : BaseNetConnectorComponent<TNetType>
{
private readonly Dictionary<Node, List<TNetConnector>> _netConnectorComponents = new Dictionary<Node, List<TNetConnector>>();
protected override void OnAddNode(Node node)
{
var newNetConnectorComponents = node.Owner
.GetAllComponents<TNetConnector>()
.Where(powerComp => (NodeGroupID) powerComp.Voltage == node.NodeGroupID)
.ToList();
_netConnectorComponents.Add(node, newNetConnectorComponents);
foreach (var netConnectorComponent in newNetConnectorComponents)
{
SetNetConnectorNet(netConnectorComponent);
}
}
protected abstract void SetNetConnectorNet(TNetConnector netConnectorComponent);
protected override void OnRemoveNode(Node node)
{
foreach (var netConnectorComponent in _netConnectorComponents[node])
{
netConnectorComponent.ClearNet();
}
_netConnectorComponents.Remove(node);
}
}
}

View File

@@ -0,0 +1,121 @@
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
/// <summary>
/// Maintains a collection of <see cref="Node"/>s, and performs operations requiring a list of
/// all connected <see cref="Node"/>s.
/// </summary>
public interface INodeGroup
{
public IReadOnlyList<Node> Nodes { get; }
void AddNode(Node node);
void RemoveNode(Node node);
void CombineGroup(INodeGroup newGroup);
void BeforeCombine();
void AfterCombine();
void BeforeRemakeSpread();
void AfterRemakeSpread();
}
[NodeGroup(NodeGroupID.Default)]
public class BaseNodeGroup : INodeGroup
{
[ViewVariables]
public IReadOnlyList<Node> Nodes => _nodes;
private readonly List<Node> _nodes = new List<Node>();
[ViewVariables]
public int NodeCount => Nodes.Count;
public static readonly INodeGroup NullGroup = new NullNodeGroup();
public void AddNode(Node node)
{
_nodes.Add(node);
OnAddNode(node);
}
public void RemoveNode(Node node)
{
_nodes.Remove(node);
OnRemoveNode(node);
RemakeGroup();
}
public void CombineGroup(INodeGroup newGroup)
{
if (newGroup.Nodes.Count < Nodes.Count)
{
newGroup.CombineGroup(this);
return;
}
BeforeCombine();
newGroup.BeforeCombine();
foreach (var node in Nodes)
{
node.NodeGroup = newGroup;
}
AfterCombine();
newGroup.AfterCombine();
}
/// <summary>
/// Causes all <see cref="Node"/>s to remake their groups. Called when a <see cref="Node"/> is removed
/// and may have split a group in two, so multiple new groups may need to be formed.
/// </summary>
private void RemakeGroup()
{
BeforeRemake();
foreach (var node in Nodes)
{
node.ClearNodeGroup();
}
foreach (var node in Nodes)
{
if (node.TryAssignGroupIfNeeded())
{
node.StartSpreadingGroup();
}
}
}
protected virtual void OnAddNode(Node node) { }
protected virtual void OnRemoveNode(Node node) { }
protected virtual void BeforeRemake() { }
protected virtual void AfterRemake() { }
public virtual void BeforeCombine() { }
public virtual void AfterCombine() { }
public virtual void BeforeRemakeSpread() { }
public virtual void AfterRemakeSpread() { }
private class NullNodeGroup : INodeGroup
{
public IReadOnlyList<Node> Nodes => _nodes;
private readonly List<Node> _nodes = new List<Node>();
public void AddNode(Node node) { }
public void CombineGroup(INodeGroup newGroup) { }
public void RemoveNode(Node node) { }
public void BeforeCombine() { }
public void AfterCombine() { }
public void BeforeRemakeSpread() { }
public void AfterRemakeSpread() { }
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
/// <summary>
/// Associates a <see cref="INodeGroup"/> implementation with a <see cref="NodeGroupID"/>.
/// This is used to gurantee all <see cref="INode"/>s of the same <see cref="INode.NodeGroupID"/>
/// have the same type of <see cref="INodeGroup"/>. Used by <see cref="INodeGroupFactory"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class NodeGroupAttribute : Attribute
{
public NodeGroupID[] NodeGroupIDs { get; }
public NodeGroupAttribute(params NodeGroupID[] nodeGroupTypes)
{
NodeGroupIDs = nodeGroupTypes;
}
}
}

View File

@@ -0,0 +1,65 @@
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
public interface INodeGroupFactory
{
/// <summary>
/// Performs reflection to associate <see cref="INodeGroup"/> implementations with the
/// string specified in their <see cref="NodeGroupAttribute"/>.
/// </summary>
void Initialize();
/// <summary>
/// Returns a new <see cref="INodeGroup"/> instance.
/// </summary>
INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType);
}
public class NodeGroupFactory : INodeGroupFactory
{
private readonly Dictionary<NodeGroupID, Type> _groupTypes = new Dictionary<NodeGroupID, Type>();
#pragma warning disable 649
[Dependency] private readonly IReflectionManager _reflectionManager;
[Dependency] private readonly IDynamicTypeFactory _typeFactory;
#pragma warning restore 649
public void Initialize()
{
var nodeGroupTypes = _reflectionManager.GetAllChildren<BaseNodeGroup>();
foreach (var nodeGroupType in nodeGroupTypes)
{
var att = nodeGroupType.GetCustomAttribute<NodeGroupAttribute>();
if (att != null)
{
foreach (var groupID in att.NodeGroupIDs)
{
_groupTypes.Add(groupID, nodeGroupType);
}
}
}
}
public INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType)
{
if (_groupTypes.TryGetValue(nodeGroupType, out var type))
{
return _typeFactory.CreateInstance<INodeGroup>(type);
}
throw new ArgumentException($"{nodeGroupType} did not have an associated {nameof(INodeGroup)}.");
}
}
public enum NodeGroupID
{
Default,
HVPower,
MVPower,
Apc,
}
}

View File

@@ -0,0 +1,192 @@
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups
{
public interface IPowerNet
{
void AddSupplier(PowerSupplierComponent supplier);
void RemoveSupplier(PowerSupplierComponent supplier);
void UpdateSupplierSupply(PowerSupplierComponent supplier, int oldSupplyRate, int newSupplyRate);
void AddConsumer(PowerConsumerComponent consumer);
void RemoveConsumer(PowerConsumerComponent consumer);
void UpdateConsumerDraw(PowerConsumerComponent consumer, int oldDrawRate, int newDrawRate);
void UpdateConsumerPriority(PowerConsumerComponent consumer, Priority oldPriority, Priority newPriority);
}
[NodeGroup(NodeGroupID.HVPower, NodeGroupID.MVPower)]
public class PowerNetNodeGroup : BaseNetConnectorNodeGroup<BasePowerNetComponent, IPowerNet>, IPowerNet
{
[ViewVariables]
private readonly List<PowerSupplierComponent> _suppliers = new List<PowerSupplierComponent>();
[ViewVariables]
private int _totalSupply = 0;
[ViewVariables]
private readonly Dictionary<Priority, List<PowerConsumerComponent>> _consumersByPriority = new Dictionary<Priority, List<PowerConsumerComponent>>();
[ViewVariables]
private readonly Dictionary<Priority, int> _drawByPriority = new Dictionary<Priority, int>();
[ViewVariables]
private bool _supressPowerRecalculation = false;
public static readonly IPowerNet NullNet = new NullPowerNet();
public PowerNetNodeGroup()
{
foreach(Priority priority in Enum.GetValues(typeof(Priority)))
{
_consumersByPriority.Add(priority, new List<PowerConsumerComponent>());
_drawByPriority.Add(priority, 0);
}
}
#region BaseNodeGroup Overrides
protected override void SetNetConnectorNet(BasePowerNetComponent netConnectorComponent)
{
netConnectorComponent.Net = this;
}
public override void BeforeCombine()
{
_supressPowerRecalculation = true;
}
public override void AfterCombine()
{
_supressPowerRecalculation = false;
UpdateConsumerReceivedPower();
}
protected override void BeforeRemake()
{
_supressPowerRecalculation = true;
}
public override void BeforeRemakeSpread()
{
_supressPowerRecalculation = true;
}
public override void AfterRemakeSpread()
{
_supressPowerRecalculation = false;
UpdateConsumerReceivedPower();
}
#endregion
#region IPowerNet Methods
public void AddSupplier(PowerSupplierComponent supplier)
{
_suppliers.Add(supplier);
_totalSupply += supplier.SupplyRate;
UpdateConsumerReceivedPower();
}
public void RemoveSupplier(PowerSupplierComponent supplier)
{
Debug.Assert(_suppliers.Contains(supplier));
_suppliers.Remove(supplier);
_totalSupply -= supplier.SupplyRate;
UpdateConsumerReceivedPower();
}
public void UpdateSupplierSupply(PowerSupplierComponent supplier, int oldSupplyRate, int newSupplyRate)
{
Debug.Assert(_suppliers.Contains(supplier));
_totalSupply -= oldSupplyRate;
_totalSupply += newSupplyRate;
UpdateConsumerReceivedPower();
}
public void AddConsumer(PowerConsumerComponent consumer)
{
_consumersByPriority[consumer.Priority].Add(consumer);
_drawByPriority[consumer.Priority] += consumer.DrawRate;
UpdateConsumerReceivedPower();
}
public void RemoveConsumer(PowerConsumerComponent consumer)
{
Debug.Assert(_consumersByPriority[consumer.Priority].Contains(consumer));
_consumersByPriority[consumer.Priority].Add(consumer);
_drawByPriority[consumer.Priority] -= consumer.DrawRate;
UpdateConsumerReceivedPower();
}
public void UpdateConsumerDraw(PowerConsumerComponent consumer, int oldDrawRate, int newDrawRate)
{
Debug.Assert(_consumersByPriority[consumer.Priority].Contains(consumer));
_drawByPriority[consumer.Priority] -= oldDrawRate;
_drawByPriority[consumer.Priority] += newDrawRate;
UpdateConsumerReceivedPower();
}
public void UpdateConsumerPriority(PowerConsumerComponent consumer, Priority oldPriority, Priority newPriority)
{
Debug.Assert(_consumersByPriority[oldPriority].Contains(consumer));
_consumersByPriority[oldPriority].Remove(consumer);
_drawByPriority[oldPriority] -= consumer.DrawRate;
_consumersByPriority[newPriority].Add(consumer);
_drawByPriority[newPriority] += consumer.DrawRate;
UpdateConsumerReceivedPower();
}
private void UpdateConsumerReceivedPower()
{
if (_supressPowerRecalculation)
{
return;
}
var remainingSupply = _totalSupply;
foreach (Priority priority in Enum.GetValues(typeof(Priority)))
{
var categoryPowerDemand = _drawByPriority[priority];
if (remainingSupply - categoryPowerDemand >= 0) //can fully power all in category
{
remainingSupply -= categoryPowerDemand;
foreach (var consumer in _consumersByPriority[priority])
{
consumer.ReceivedPower = consumer.DrawRate;
}
}
else if (remainingSupply - categoryPowerDemand < 0) //cannot fully power all, split power
{
var availiablePowerFraction = (float) remainingSupply / categoryPowerDemand;
remainingSupply = 0;
foreach (var consumer in _consumersByPriority[priority])
{
consumer.ReceivedPower = (int) (consumer.DrawRate * availiablePowerFraction); //give each consumer a fraction of what they requested (rounded down to nearest int)
}
}
}
}
#endregion
private class NullPowerNet : IPowerNet
{
public void AddConsumer(PowerConsumerComponent consumer) { }
public void AddSupplier(PowerSupplierComponent supplier) { }
public void UpdateSupplierSupply(PowerSupplierComponent supplier, int oldSupplyRate, int newSupplyRate) { }
public void RemoveConsumer(PowerConsumerComponent consumer) { }
public void RemoveSupplier(PowerSupplierComponent supplier) { }
public void UpdateConsumerDraw(PowerConsumerComponent consumer, int oldDrawRate, int newDrawRate) { }
public void UpdateConsumerPriority(PowerConsumerComponent consumer, Priority oldPriority, Priority newPriority) { }
}
}
}

View File

@@ -0,0 +1,25 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects.Components.Transform;
using System.Collections.Generic;
using System.Linq;
namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
{
/// <summary>
/// A <see cref="Node"/> that can reach other <see cref="AdjacentNode"/>s that are directly adjacent to it.
/// </summary>
[Node("AdjacentNode")]
public class AdjacentNode : Node
{
protected override IEnumerable<Node> GetReachableNodes()
{
return Owner.GetComponent<SnapGridComponent>()
.GetCardinalNeighborCells()
.SelectMany(sgc => sgc.GetLocal())
.Select(entity => entity.TryGetComponent<NodeContainerComponent>(out var container) ? container : null)
.Where(container => container != null)
.SelectMany(container => container.Nodes)
.Where(node => node != null && node != this);
}
}
}

View File

@@ -0,0 +1,131 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
{
/// <summary>
/// Organizes themselves into distinct <see cref="INodeGroup"/>s with other <see cref="Node"/>s
/// that they can "reach" and have the same <see cref="Node.NodeGroupID"/>.
/// </summary>
public abstract class Node
{
/// <summary>
/// An ID used as a criteria for combining into groups. Determines which <see cref="INodeGroup"/>
/// implementation is used as a group, detailed in <see cref="INodeGroupFactory"/>.
/// </summary>
[ViewVariables]
public NodeGroupID NodeGroupID { get; private set; }
[ViewVariables]
public INodeGroup NodeGroup { get => _nodeGroup; set => SetNodeGroup(value); }
private INodeGroup _nodeGroup = BaseNodeGroup.NullGroup;
[ViewVariables]
public IEntity Owner { get; private set; }
private bool _needsGroup = true;
private bool _deleting = false;
#pragma warning disable 649
[Dependency] private readonly INodeGroupFactory _nodeGroupFactory;
#pragma warning restore 649
public void Initialize(NodeGroupID nodeGroupID, IEntity owner)
{
NodeGroupID = nodeGroupID;
Owner = owner;
}
public void OnContainerInitialize()
{
TryAssignGroupIfNeeded();
CombineGroupWithReachable();
}
public void OnContainerRemove()
{
_deleting = true;
NodeGroup.RemoveNode(this);
}
public bool TryAssignGroupIfNeeded()
{
if (!_needsGroup)
{
return false;
}
NodeGroup = GetReachableCompatibleGroups().FirstOrDefault() ?? MakeNewGroup();
return true;
}
public void StartSpreadingGroup()
{
NodeGroup.BeforeRemakeSpread();
SpreadGroup();
NodeGroup.AfterRemakeSpread();
}
public void SpreadGroup()
{
Debug.Assert(!_needsGroup);
foreach (var node in GetReachableCompatibleNodes().Where(node => node._needsGroup == true))
{
node.NodeGroup = NodeGroup;
node.SpreadGroup();
}
}
public void ClearNodeGroup()
{
_nodeGroup = BaseNodeGroup.NullGroup;
_needsGroup = true;
}
/// <summary>
/// How this node will attempt to find other reachable <see cref="Node"/>s to group with.
/// Returns a set of <see cref="Node"/>s to consider grouping with. Should not return this current <see cref="Node"/>.
/// </summary>
protected abstract IEnumerable<Node> GetReachableNodes();
private IEnumerable<Node> GetReachableCompatibleNodes()
{
return GetReachableNodes().Where(node => node.NodeGroupID == NodeGroupID)
.Where(node => node._deleting == false);
}
private IEnumerable<INodeGroup> GetReachableCompatibleGroups()
{
return GetReachableCompatibleNodes().Where(node => node._needsGroup == false)
.Select(node => node.NodeGroup)
.Where(group => group != NodeGroup);
}
private void CombineGroupWithReachable()
{
Debug.Assert(!_needsGroup);
foreach (var group in GetReachableCompatibleGroups())
{
NodeGroup.CombineGroup(group);
}
}
private void SetNodeGroup(INodeGroup newGroup)
{
_nodeGroup = newGroup;
NodeGroup.AddNode(this);
_needsGroup = false;
}
private INodeGroup MakeNewGroup()
{
return _nodeGroupFactory.MakeNodeGroup(NodeGroupID);
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
{
/// <summary>
/// Associates a <see cref="Node"/> implementation with a string. This is used
/// to specify an <see cref="Node"/>'s strategy in yaml. Used by <see cref="INodeFactory"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class
NodeAttribute : Attribute
{
public string Name { get; }
public NodeAttribute(string name)
{
Name = name;
}
}
}

View File

@@ -0,0 +1,58 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
{
public interface INodeFactory
{
/// <summary>
/// Performs reflection to associate <see cref="Node"/> implementations with the
/// string specified in their <see cref="NodeAttribute"/>.
/// </summary>
void Initialize();
/// <summary>
/// Returns a new <see cref="Node"/> instance.
/// </summary>
Node MakeNode(string nodeName, NodeGroupID groupID, IEntity owner);
}
public class NodeFactory : INodeFactory
{
private readonly Dictionary<string, Type> _groupTypes = new Dictionary<string, Type>();
#pragma warning disable 649
[Dependency] private readonly IReflectionManager _reflectionManager;
[Dependency] private readonly IDynamicTypeFactory _typeFactory;
#pragma warning restore 649
public void Initialize()
{
var nodeTypes = _reflectionManager.GetAllChildren<Node>();
foreach (var nodeType in nodeTypes)
{
var att = nodeType.GetCustomAttribute<NodeAttribute>();
if (att != null)
{
_groupTypes.Add(att.Name, nodeType);
}
}
}
public Node MakeNode(string nodeName, NodeGroupID groupID, IEntity owner)
{
if (_groupTypes.TryGetValue(nodeName, out var type))
{
var newNode = _typeFactory.CreateInstance<Node>(type);
newNode.Initialize(groupID, owner);
return newNode;
}
throw new ArgumentException($"{nodeName} did not have an associated {nameof(Node)}.");
}
}
}

View File

@@ -1,127 +0,0 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.Components.Power
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public sealed class ApcComponent : SharedApcComponent, IActivate
{
PowerStorageComponent Storage;
AppearanceComponent Appearance;
private PowerProviderComponent _provider;
ApcChargeState LastChargeState;
private float _lastCharge = 0f;
private ApcExternalPowerState _lastExternalPowerState;
private BoundUserInterface _userInterface;
private bool _uiDirty = true;
public override void Initialize()
{
base.Initialize();
Storage = Owner.GetComponent<PowerStorageComponent>();
Appearance = Owner.GetComponent<AppearanceComponent>();
_provider = Owner.GetComponent<PowerProviderComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(ApcUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
var obj = serverMsg.Message;
if (obj is ApcToggleMainBreakerMessage)
{
_provider.MainBreaker = !_provider.MainBreaker;
_uiDirty = true;
_clickSound();
}
}
public void OnUpdate()
{
var newState = CalcChargeState();
if (newState != LastChargeState)
{
LastChargeState = newState;
Appearance.SetData(ApcVisuals.ChargeState, newState);
}
var newCharge = Storage.Charge;
if (newCharge != _lastCharge)
{
_lastCharge = newCharge;
_uiDirty = true;
}
var extPowerState = CalcExtPowerState();
if (extPowerState != _lastExternalPowerState)
{
_lastExternalPowerState = extPowerState;
_uiDirty = true;
}
if (_uiDirty)
{
_userInterface.SetState(new ApcBoundInterfaceState(_provider.MainBreaker, extPowerState,
newCharge / Storage.Capacity));
_uiDirty = false;
}
}
private ApcChargeState CalcChargeState()
{
var storageCharge = Storage.GetChargeState();
switch (storageCharge)
{
case ChargeState.Discharging:
return ApcChargeState.Lack;
case ChargeState.Charging:
return ApcChargeState.Charging;
default:
// Still.
return Storage.Full ? ApcChargeState.Full : ApcChargeState.Lack;
}
}
private ApcExternalPowerState CalcExtPowerState()
{
if (!Owner.TryGetComponent(out PowerNodeComponent node) || node.Parent == null)
{
return ApcExternalPowerState.None;
}
var net = node.Parent;
if (net.LastTotalAvailable <= 0)
{
return ApcExternalPowerState.None;
}
return net.Lack > 0 ? ApcExternalPowerState.Low : ApcExternalPowerState.Good;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
{
return;
}
_userInterface.Open(actor.playerSession);
}
private void _clickSound()
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/machines/machine_switch.ogg", Owner,AudioParams.Default.WithVolume(-2f));
}
}
}

View File

@@ -0,0 +1,161 @@
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.ViewVariables;
using System;
using Robust.Shared.IoC;
using Robust.Shared.Interfaces.Timing;
namespace Content.Server.GameObjects.Components.Power.ApcNetComponents
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class ApcComponent : BaseApcNetComponent, IActivate
{
public override string Name => "Apc";
[ViewVariables]
public BatteryComponent Battery { get; private set; }
public bool MainBreakerEnabled { get; private set; } = true;
private BoundUserInterface _userInterface;
private AppearanceComponent _appearance;
private ApcChargeState _lastChargeState;
private TimeSpan _lastChargeStateChange;
private ApcExternalPowerState _lastExternalPowerState;
private TimeSpan _lastExternalPowerStateChange;
private float _lastCharge = 0f;
private bool _uiDirty = true;
private const float HighPowerThreshold = 0.9f;
private const int VisualsChangeDelay = 1;
#pragma warning disable 649
[Dependency] private readonly IGameTiming _gameTiming;
#pragma warning restore 649
public override void Initialize()
{
base.Initialize();
Battery = Owner.GetComponent<BatteryComponent>();
_appearance = Owner.GetComponent<AppearanceComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(ApcUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
Update();
}
protected override void AddSelfToNet(IApcNet apcNet)
{
apcNet.AddApc(this);
}
protected override void RemoveSelfFromNet(IApcNet apcNet)
{
apcNet.RemoveApc(this);
}
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
if (serverMsg.Message is ApcToggleMainBreakerMessage)
{
MainBreakerEnabled = !MainBreakerEnabled;
_uiDirty = true;
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
}
}
public void Update()
{
var newState = CalcChargeState();
if (newState != _lastChargeState && _lastChargeStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastChargeState = newState;
_lastChargeStateChange = _gameTiming.CurTime;
_appearance.SetData(ApcVisuals.ChargeState, newState);
}
var newCharge = Battery.CurrentCharge;
if (newCharge != _lastCharge)
{
_lastCharge = newCharge;
_uiDirty = true;
}
var extPowerState = CalcExtPowerState();
if (extPowerState != _lastExternalPowerState && _lastExternalPowerStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastExternalPowerState = extPowerState;
_lastExternalPowerStateChange = _gameTiming.CurTime;
_uiDirty = true;
}
if (_uiDirty)
{
_userInterface.SetState(new ApcBoundInterfaceState(MainBreakerEnabled, extPowerState, newCharge / Battery.MaxCharge));
_uiDirty = false;
}
}
private ApcChargeState CalcChargeState()
{
var chargeFraction = Battery.CurrentCharge / Battery.MaxCharge;
if (chargeFraction > HighPowerThreshold)
{
return ApcChargeState.Full;
}
var consumer = Owner.GetComponent<PowerConsumerComponent>();
if (consumer.DrawRate == consumer.ReceivedPower)
{
return ApcChargeState.Charging;
}
else
{
return ApcChargeState.Lack;
}
}
private ApcExternalPowerState CalcExtPowerState()
{
if (!Owner.TryGetComponent(out BatteryStorageComponent batteryStorage))
{
return ApcExternalPowerState.None;
}
var consumer = batteryStorage.Consumer;
if (consumer.ReceivedPower == 0 && consumer.DrawRate != 0)
{
return ApcExternalPowerState.None;
}
else if (consumer.ReceivedPower < consumer.DrawRate)
{
return ApcExternalPowerState.Low;
}
else
{
return ApcExternalPowerState.Good;
}
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
{
return;
}
_userInterface.Open(actor.playerSession);
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
namespace Content.Server.GameObjects.Components.Power.ApcNetComponents
{
public abstract class BaseApcNetComponent : BaseNetConnectorComponent<IApcNet>
{
protected override IApcNet NullNet => ApcNetNodeGroup.NullNet;
}
}

View File

@@ -0,0 +1,131 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Content.Server.GameObjects.Components.Power.ApcNetComponents
{
/// <summary>
/// Relays <see cref="PowerReceiverComponent"/>s in an area to a <see cref="IApcNet"/> so they can receive power.
/// </summary>
public interface IPowerProvider
{
void AddReceiver(PowerReceiverComponent receiver);
void RemoveReceiver(PowerReceiverComponent receiver);
}
[RegisterComponent]
public class PowerProviderComponent : BaseApcNetComponent, IPowerProvider
{
public override string Name => "PowerProvider";
/// <summary>
/// The max distance this can transmit power to <see cref="PowerReceiverComponent"/>s from.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int PowerTransferRange { get => _powerTransferRange; set => SetPowerTransferRange(value); }
private int _powerTransferRange;
[ViewVariables]
public IReadOnlyList<PowerReceiverComponent> LinkedReceivers => _linkedReceivers;
private List<PowerReceiverComponent> _linkedReceivers = new List<PowerReceiverComponent>();
/// <summary>
/// If <see cref="PowerReceiverComponent"/>s should consider connecting to this.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool Connectable { get; private set; } = true;
public static readonly IPowerProvider NullProvider = new NullPowerProvider();
public void AddReceiver(PowerReceiverComponent receiver)
{
_linkedReceivers.Add(receiver);
Net.UpdatePowerProviderReceivers(this);
}
public void RemoveReceiver(PowerReceiverComponent receiver)
{
_linkedReceivers.Remove(receiver);
Net.UpdatePowerProviderReceivers(this);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _powerTransferRange, "powerTransferRange", 3);
}
protected override void Startup()
{
base.Startup();
foreach (var receiver in FindAvailableReceivers())
{
receiver.Provider = this;
}
}
public override void OnRemove()
{
Connectable = false;
var receivers = _linkedReceivers.ToArray();
foreach (var receiver in receivers)
{
receiver.ClearProvider();
}
_linkedReceivers = new List<PowerReceiverComponent>();
Net.UpdatePowerProviderReceivers(this);
foreach (var receiver in receivers)
{
receiver.TryFindAndSetProvider();
}
base.OnRemove();
}
private List<PowerReceiverComponent> FindAvailableReceivers()
{
var mapManager = IoCManager.Resolve<IMapManager>();
var nearbyEntities = IoCManager.Resolve<IServerEntityManager>()
.GetEntitiesInRange(Owner, PowerTransferRange);
return nearbyEntities.Select(entity => entity.TryGetComponent<PowerReceiverComponent>(out var receiver) ? receiver : null)
.Where(receiver => receiver != null)
.Where(receiver => receiver.NeedsProvider)
.Where(receiver => receiver.Owner.Transform.GridPosition.Distance(mapManager, Owner.Transform.GridPosition) < Math.Min(PowerTransferRange, receiver.PowerReceptionRange))
.ToList();
}
protected override void AddSelfToNet(IApcNet apcNet)
{
apcNet.AddPowerProvider(this);
}
protected override void RemoveSelfFromNet(IApcNet apcNet)
{
apcNet.RemovePowerProvider(this);
}
private void SetPowerTransferRange(int newPowerTransferRange)
{
foreach (var receiver in _linkedReceivers)
{
receiver.ClearProvider();
}
_powerTransferRange = newPowerTransferRange;
_linkedReceivers = FindAvailableReceivers();
Net.UpdatePowerProviderReceivers(this);
}
private class NullPowerProvider : IPowerProvider
{
public void AddReceiver(PowerReceiverComponent receiver) { }
public void RemoveReceiver(PowerReceiverComponent receiver) { }
}
}
}

View File

@@ -0,0 +1,198 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
namespace Content.Server.GameObjects.Components.Power.ApcNetComponents
{
/// <summary>
/// Attempts to link with a nearby <see cref="IPowerProvider"/>s so that it can receive power from a <see cref="IApcNet"/>.
/// </summary>
[RegisterComponent]
public class PowerReceiverComponent : Component
{
public override string Name => "PowerReceiver";
public event EventHandler<PowerStateEventArgs> OnPowerStateChanged;
[ViewVariables]
public bool Powered => (HasApcPower || !NeedsPower) && !PowerDisabled;
[ViewVariables]
public bool HasApcPower { get => _hasApcPower; set => SetHasApcPower(value); }
private bool _hasApcPower;
/// <summary>
/// The max distance from a <see cref="PowerProviderComponent"/> that this can receive power from.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int PowerReceptionRange { get => _powerReceptionRange; set => SetPowerReceptionRange(value); }
private int _powerReceptionRange;
[ViewVariables]
public IPowerProvider Provider { get => _provider; set => SetProvider(value); }
private IPowerProvider _provider = PowerProviderComponent.NullProvider;
[ViewVariables]
public bool NeedsProvider { get; private set; } = true;
/// <summary>
/// Amount of charge this needs from an APC per second to function.
/// </summary>
[ViewVariables]
public int Load { get => _load; set => SetLoad(value); }
private int _load;
/// <summary>
/// When true, causes this to appear powered even if not receiving power from an Apc.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool NeedsPower { get => _needsPower; set => SetNeedsPower(value); }
private bool _needsPower;
/// <summary>
/// When true, causes this to never appear powered.
/// </summary>
public bool PowerDisabled { get => _powerDisabled; set => SetPowerDisabled(value); }
private bool _powerDisabled;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _powerReceptionRange, "powerReceptionRange", 3);
serializer.DataField(ref _load, "powerLoad", 5);
serializer.DataField(ref _needsPower, "needsPower", true);
serializer.DataField(ref _powerDisabled, "powerDisabled", false);
}
protected override void Startup()
{
base.Startup();
if (NeedsProvider)
{
TryFindAndSetProvider();
}
}
public override void OnRemove()
{
_provider.RemoveReceiver(this);
base.OnRemove();
}
public void TryFindAndSetProvider()
{
if (TryFindAvailableProvider(out var provider))
{
Provider = provider;
}
}
private bool TryFindAvailableProvider(out IPowerProvider foundProvider)
{
var nearbyEntities = IoCManager.Resolve<IServerEntityManager>()
.GetEntitiesInRange(Owner, PowerReceptionRange);
var mapManager = IoCManager.Resolve<IMapManager>();
foreach (var entity in nearbyEntities)
{
if (entity.TryGetComponent<PowerProviderComponent>(out var provider))
{
if (provider.Connectable)
{
var distanceToProvider = provider.Owner.Transform.GridPosition.Distance(mapManager, Owner.Transform.GridPosition);
if (distanceToProvider < Math.Min(PowerReceptionRange, provider.PowerTransferRange))
{
foundProvider = provider;
return true;
}
}
}
}
foundProvider = default;
return false;
}
public void ClearProvider()
{
_provider.RemoveReceiver(this);
_provider = PowerProviderComponent.NullProvider;
NeedsProvider = true;
HasApcPower = false;
}
private void SetProvider(IPowerProvider newProvider)
{
_provider.RemoveReceiver(this);
_provider = newProvider;
newProvider.AddReceiver(this);
NeedsProvider = false;
}
private void SetHasApcPower(bool newHasApcPower)
{
var oldPowered = Powered;
_hasApcPower = newHasApcPower;
if (oldPowered != Powered)
{
OnNewPowerState();
}
}
private void SetPowerReceptionRange(int newPowerReceptionRange)
{
ClearProvider();
_powerReceptionRange = newPowerReceptionRange;
TryFindAndSetProvider();
}
private void SetLoad(int newLoad)
{
_load = newLoad;
}
private void SetNeedsPower(bool newNeedsPower)
{
var oldPowered = Powered;
_needsPower = newNeedsPower;
if (oldPowered != Powered)
{
OnNewPowerState();
}
}
private void SetPowerDisabled(bool newPowerDisabled)
{
var oldPowered = Powered;
_powerDisabled = newPowerDisabled;
if (oldPowered != Powered)
{
OnNewPowerState();
}
}
private void OnNewPowerState()
{
OnPowerStateChanged?.Invoke(this, new PowerStateEventArgs(Powered));
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(PowerDeviceVisuals.Powered, Powered);
}
}
}
public class PowerStateEventArgs : EventArgs
{
public readonly bool Powered;
public PowerStateEventArgs(bool powered)
{
Powered = powered;
}
}
}

View File

@@ -0,0 +1,282 @@
using System;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.Chargers
{
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))]
public abstract class BaseCharger : Component, IActivate, IInteractUsing
{
[ViewVariables]
private BatteryComponent _heldBattery;
[ViewVariables]
private ContainerSlot _container;
[ViewVariables]
private PowerReceiverComponent _powerReceiver;
[ViewVariables]
private CellChargerStatus _status;
private AppearanceComponent _appearanceComponent;
[ViewVariables]
private int _chargeRate;
[ViewVariables]
private float _transferEfficiency;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _chargeRate, "chargeRate", 100);
serializer.DataField(ref _transferEfficiency, "transferEfficiency", 0.85f);
}
public override void Initialize()
{
base.Initialize();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_container = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-powerCellContainer", Owner);
_appearanceComponent = Owner.GetComponent<AppearanceComponent>();
// Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty
_powerReceiver.OnPowerStateChanged += PowerUpdate;
}
public override void OnRemove()
{
_powerReceiver.OnPowerStateChanged -= PowerUpdate;
base.OnRemove();
}
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
var result = TryInsertItem(eventArgs.Using);
if (!result)
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.User.PopupMessage(Owner, localizationManager.GetString("Unable to insert capacitor"));
}
return result;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
RemoveItem(eventArgs.User);
}
/// <summary>
/// This will remove the item directly into the user's hand / floor
/// </summary>
/// <param name="user"></param>
private void RemoveItem(IEntity user)
{
var heldItem = _container.ContainedEntity;
if (heldItem == null)
{
return;
}
_container.Remove(heldItem);
_heldBattery = null;
if (user.TryGetComponent(out HandsComponent handsComponent))
{
handsComponent.PutInHandOrDrop(heldItem.GetComponent<ItemComponent>());
}
if (heldItem.TryGetComponent(out ServerBatteryBarrelComponent batteryBarrelComponent))
{
batteryBarrelComponent.UpdateAppearance();
}
UpdateStatus();
}
private void PowerUpdate(object sender, PowerStateEventArgs eventArgs)
{
UpdateStatus();
}
[Verb]
private sealed class InsertVerb : Verb<BaseCharger>
{
protected override void GetData(IEntity user, BaseCharger component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity != null || handsComponent.GetActiveHand == null)
{
data.Visibility = VerbVisibility.Disabled;
data.Text = "Insert";
return;
}
data.Text = $"Insert {handsComponent.GetActiveHand.Owner.Name}";
}
protected override void Activate(IEntity user, BaseCharger component)
{
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
return;
}
if (handsComponent.GetActiveHand == null)
{
return;
}
var userItem = handsComponent.GetActiveHand.Owner;
handsComponent.Drop(userItem);
component.TryInsertItem(userItem);
}
}
[Verb]
private sealed class EjectVerb : Verb<BaseCharger>
{
protected override void GetData(IEntity user, BaseCharger component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity == null)
{
data.Text = "Eject";
data.Visibility = VerbVisibility.Disabled;
return;
}
data.Text = $"Eject {component._container.ContainedEntity.Name}";
}
protected override void Activate(IEntity user, BaseCharger component)
{
component.RemoveItem(user);
}
}
private CellChargerStatus GetStatus()
{
if (!_powerReceiver.Powered)
{
return CellChargerStatus.Off;
}
if (_container.ContainedEntity == null)
{
return CellChargerStatus.Empty;
}
if (_heldBattery != null && Math.Abs(_heldBattery.MaxCharge - _heldBattery.CurrentCharge) < 0.01)
{
return CellChargerStatus.Charged;
}
return CellChargerStatus.Charging;
}
private bool TryInsertItem(IEntity entity)
{
if (!IsEntityCompatible(entity) || _container.ContainedEntity != null)
{
return false;
}
if (!_container.Insert(entity))
{
return false;
}
_heldBattery = GetBatteryFrom(entity);
UpdateStatus();
return true;
}
/// <summary>
/// If the supplied entity should fit into the charger.
/// </summary>
protected abstract bool IsEntityCompatible(IEntity entity);
protected abstract BatteryComponent GetBatteryFrom(IEntity entity);
private void UpdateStatus()
{
// Not called UpdateAppearance just because it messes with the load
var status = GetStatus();
if (_status == status)
{
return;
}
_status = status;
switch (_status)
{
// Update load just in case
case CellChargerStatus.Off:
_powerReceiver.Load = 0;
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Off);
break;
case CellChargerStatus.Empty:
_powerReceiver.Load = 0;
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Empty); ;
break;
case CellChargerStatus.Charging:
_powerReceiver.Load = (int) (_chargeRate / _transferEfficiency);
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Charging);
break;
case CellChargerStatus.Charged:
_powerReceiver.Load = 0;
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Charged);
break;
default:
throw new ArgumentOutOfRangeException();
}
_appearanceComponent?.SetData(CellVisual.Occupied, _container.ContainedEntity != null);
}
public void OnUpdate(float frameTime) //todo: make single system for this
{
if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged || _container.ContainedEntity == null)
{
return;
}
TransferPower(frameTime);
}
private void TransferPower(float frameTime)
{
if (!_powerReceiver.Powered)
{
return;
}
_heldBattery.CurrentCharge += _chargeRate * frameTime;
// Just so the sprite won't be set to 99.99999% visibility
if (_heldBattery.MaxCharge - _heldBattery.CurrentCharge < 0.01)
{
_heldBattery.CurrentCharge = _heldBattery.MaxCharge;
}
UpdateStatus();
}
}
}

View File

@@ -68,8 +68,8 @@ namespace Content.Server.GameObjects.Components.Power
private int _burningTemperature;
public int BurningTemperature => _burningTemperature;
private float _powerUse;
public float PowerUse => _powerUse;
private int _powerUse;
public int PowerUse => _powerUse;
/// <summary>
/// The current state of the light bulb. Invokes the OnLightBulbStateChange event when set.

View File

@@ -0,0 +1,27 @@
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.Components.Power.Chargers
{
/// <summary>
/// Recharges an entity with a <see cref="BatteryComponent"/>.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(BaseCharger))]
public sealed class PowerCellChargerComponent : BaseCharger
{
public override string Name => "PowerCellCharger";
protected override bool IsEntityCompatible(IEntity entity)
{
return entity.HasComponent<BatteryComponent>();
}
protected override BatteryComponent GetBatteryFrom(IEntity entity)
{
return entity.GetComponent<BatteryComponent>();
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
@@ -148,12 +149,12 @@ namespace Content.Server.GameObjects.Components.Power
/// </summary>
public void UpdateLight()
{
var device = Owner.GetComponent<PowerDeviceComponent>();
var powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
var sprite = Owner.GetComponent<SpriteComponent>();
var light = Owner.GetComponent<PointLightComponent>();
if (LightBulb == null) // No light bulb.
{
device.Load = 0;
powerReceiver.Load = 0;
sprite.LayerSetState(0, "empty");
light.Enabled = false;
return;
@@ -162,8 +163,8 @@ namespace Content.Server.GameObjects.Components.Power
switch (LightBulb.State)
{
case LightBulbState.Normal:
device.Load = LightBulb.PowerUse;
if (device.Powered)
powerReceiver.Load = LightBulb.PowerUse;
if (powerReceiver.Powered)
{
sprite.LayerSetState(0, "on");
light.Enabled = true;
@@ -182,12 +183,12 @@ namespace Content.Server.GameObjects.Components.Power
}
break;
case LightBulbState.Broken:
device.Load = 0;
powerReceiver.Load = 0;
sprite.LayerSetState(0, "broken");
light.Enabled = false;
break;
case LightBulbState.Burned:
device.Load = 0;
powerReceiver.Load = 0;
sprite.LayerSetState(0, "burned");
light.Enabled = false;
break;
@@ -198,8 +199,7 @@ namespace Content.Server.GameObjects.Components.Power
{
base.Initialize();
var device = Owner.GetComponent<PowerDeviceComponent>();
device.OnPowerStateChanged += UpdateLight;
Owner.GetComponent<PowerReceiverComponent>().OnPowerStateChanged += UpdateLight;
_lightBulbContainer = ContainerManagerComponent.Ensure<ContainerSlot>("light_bulb", Owner, out var existed);
@@ -216,5 +216,11 @@ namespace Content.Server.GameObjects.Components.Power
}
}
}
public override void OnRemove()
{
Owner.GetComponent<PowerReceiverComponent>().OnPowerStateChanged -= UpdateLight;
base.OnRemove();
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.Components.Power.Chargers
{
/// <summary>
/// Recharges the battery in a <see cref="ServerBatteryBarrelComponent"/>.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(BaseCharger))]
public sealed class WeaponCapacitorChargerComponent : BaseCharger
{
public override string Name => "WeaponCapacitorCharger";
protected override bool IsEntityCompatible(IEntity entity)
{
return entity.HasComponent<ServerBatteryBarrelComponent>();
}
protected override BatteryComponent GetBatteryFrom(IEntity entity)
{
return entity.GetComponent<ServerBatteryBarrelComponent>().PowerCell;
}
}
}

View File

@@ -0,0 +1,113 @@
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Power
{
public abstract class BaseNetConnectorComponent<TNetType> : Component
{
[ViewVariables(VVAccess.ReadWrite)]
public Voltage Voltage { get => _voltage; set => SetVoltage(value); }
private Voltage _voltage;
[ViewVariables]
public TNetType Net { get => _net; set => SetNet(value); }
private TNetType _net;
protected abstract TNetType NullNet { get; }
[ViewVariables]
private bool _needsNet = true;
public override void OnAdd()
{
base.OnAdd();
_net = NullNet;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _voltage, "voltage", Voltage.High);
}
public override void Initialize()
{
base.Initialize();
if (_needsNet)
{
TryFindAndSetNet();
}
}
public override void OnRemove()
{
ClearNet();
base.OnRemove();
}
public void TryFindAndSetNet()
{
if (TryFindNet(out var net))
{
Net = net;
}
}
public void ClearNet()
{
RemoveSelfFromNet(_net);
_net = NullNet;
_needsNet = true;
}
protected abstract void AddSelfToNet(TNetType net);
protected abstract void RemoveSelfFromNet(TNetType net);
private bool TryFindNet(out TNetType foundNet)
{
if (Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
var compatibleNet = container.Nodes
.Where(node => node.NodeGroupID == (NodeGroupID) Voltage)
.Select(node => node.NodeGroup)
.OfType<TNetType>()
.FirstOrDefault();
if (compatibleNet != null)
{
foundNet = compatibleNet;
return true;
}
}
foundNet = default;
return false;
}
private void SetNet(TNetType newNet)
{
RemoveSelfFromNet(_net);
AddSelfToNet(newNet);
_net = newNet;
_needsNet = false;
}
private void SetVoltage(Voltage newVoltage)
{
ClearNet();
_voltage = newVoltage;
TryFindAndSetNet();
}
}
public enum Voltage
{
High = NodeGroupID.HVPower,
Medium = NodeGroupID.MVPower,
Apc = NodeGroupID.Apc,
}
}

View File

@@ -0,0 +1,115 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
namespace Content.Server.GameObjects.Components.Power
{
[RegisterComponent]
public class BatteryComponent : Component
{
public override string Name => "Battery";
[ViewVariables(VVAccess.ReadWrite)]
public int MaxCharge { get => _maxCharge; set => SetMaxCharge(value); }
private int _maxCharge;
[ViewVariables(VVAccess.ReadWrite)]
public float CurrentCharge { get => _currentCharge; set => SetCurrentCharge(value); }
private float _currentCharge;
[ViewVariables]
public BatteryState BatteryState { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxCharge, "maxCharge", 1000);
serializer.DataField(ref _currentCharge, "startingCharge", 500);
}
public override void Initialize()
{
base.Initialize();
UpdateStorageState();
}
/// <summary>
/// If sufficient charge is avaiable on the battery, use it. Otherwise, don't.
/// </summary>
public bool TryUseCharge(float chargeToUse)
{
if (chargeToUse >= CurrentCharge)
{
return false;
}
else
{
CurrentCharge -= chargeToUse;
return true;
}
}
public float UseCharge(float toDeduct)
{
var chargeChangedBy = Math.Min(CurrentCharge, toDeduct);
CurrentCharge -= chargeChangedBy;
return chargeChangedBy;
}
public void FillFrom(BatteryComponent battery)
{
var powerDeficit = MaxCharge - CurrentCharge;
if (battery.TryUseCharge(powerDeficit))
{
CurrentCharge += powerDeficit;
}
else
{
CurrentCharge += battery.CurrentCharge;
battery.CurrentCharge = 0;
}
}
protected virtual void OnChargeChanged() { }
private void UpdateStorageState()
{
if (CurrentCharge == MaxCharge)
{
BatteryState = BatteryState.Full;
}
else if (CurrentCharge == 0)
{
BatteryState = BatteryState.Empty;
}
else
{
BatteryState = BatteryState.PartlyFull;
}
}
private void SetMaxCharge(int newMax)
{
_maxCharge = Math.Max(newMax, 0);
_currentCharge = Math.Min( _currentCharge, MaxCharge);
UpdateStorageState();
OnChargeChanged();
}
private void SetCurrentCharge(float newChargeAmount)
{
_currentCharge = FloatMath.Clamp(newChargeAmount, 0, MaxCharge);
UpdateStorageState();
OnChargeChanged();
}
}
public enum BatteryState
{
Full,
PartlyFull,
Empty
}
}

View File

@@ -1,144 +0,0 @@
using System;
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.Chargers
{
public abstract class BaseCharger : Component
{
protected IEntity _heldItem;
protected ContainerSlot _container;
protected PowerDeviceComponent _powerDevice;
public CellChargerStatus Status => _status;
protected CellChargerStatus _status;
protected AppearanceComponent _appearanceComponent;
public abstract double CellChargePercent { get; }
// Powered items have their own charge rates, this is just a way to have chargers with different rates as well
public float TransferRatio => _transferRatio;
[ViewVariables]
protected float _transferRatio;
public float TransferEfficiency => _transferEfficiency;
[ViewVariables]
protected float _transferEfficiency;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _transferRatio, "transfer_ratio", 0.1f);
serializer.DataField(ref _transferEfficiency, "transfer_efficiency", 0.85f);
}
public override void Initialize()
{
base.Initialize();
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
if (_powerDevice == null)
{
var exc = new InvalidOperationException("Chargers requires a PowerDevice to function");
Logger.FatalS("charger", exc.Message);
throw exc;
}
_container =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-powerCellContainer", Owner);
_appearanceComponent = Owner.GetComponent<AppearanceComponent>();
// Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty
_powerDevice.OnPowerStateChanged += PowerUpdate;
}
/// <summary>
/// This will remove the item directly into the user's hand / floor
/// </summary>
/// <param name="user"></param>
public void RemoveItem(IEntity user)
{
var heldItem = _container.ContainedEntity;
if (heldItem == null)
{
return;
}
_container.Remove(heldItem);
if (user.TryGetComponent(out HandsComponent handsComponent))
{
handsComponent.PutInHandOrDrop(heldItem.GetComponent<ItemComponent>());
}
if (heldItem.TryGetComponent(out ServerBatteryBarrelComponent batteryBarrelComponent))
{
batteryBarrelComponent.UpdateAppearance();
}
UpdateStatus();
}
protected void PowerUpdate(object sender, PowerStateEventArgs eventArgs)
{
UpdateStatus();
}
protected abstract CellChargerStatus GetStatus();
protected abstract void TransferPower(float frameTime);
protected void UpdateStatus()
{
// Not called UpdateAppearance just because it messes with the load
var status = GetStatus();
if (_status == status)
{
return;
}
_status = status;
switch (_status)
{
// Update load just in case
case CellChargerStatus.Off:
_powerDevice.Load = 0;
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Off);
break;
case CellChargerStatus.Empty:
_powerDevice.Load = 0;
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Empty); ;
break;
case CellChargerStatus.Charging:
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Charging);
break;
case CellChargerStatus.Charged:
_powerDevice.Load = 0;
_appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Charged);
break;
default:
throw new ArgumentOutOfRangeException();
}
_appearanceComponent?.SetData(CellVisual.Occupied, _container.ContainedEntity != null);
}
public void OnUpdate(float frameTime)
{
if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged ||
_container.ContainedEntity == null)
{
return;
}
TransferPower(frameTime);
}
}
}

View File

@@ -1,193 +0,0 @@
using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.GameObjects.Components.Power.Chargers
{
/// <summary>
/// This is used for the standalone cell rechargers (e.g. from a flashlight)
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))]
public sealed class PowerCellChargerComponent : BaseCharger, IActivate, IInteractUsing
{
public override string Name => "PowerCellCharger";
public override double CellChargePercent => _container.ContainedEntity != null ?
_container.ContainedEntity.GetComponent<PowerCellComponent>().Charge /
_container.ContainedEntity.GetComponent<PowerCellComponent>().Capacity * 100 : 0.0f;
public override void Initialize()
{
base.Initialize();
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_container =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-powerCellContainer", Owner);
_appearanceComponent = Owner.GetComponent<AppearanceComponent>();
// Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty
_powerDevice.OnPowerStateChanged += PowerUpdate;
}
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
var result = TryInsertItem(eventArgs.Using);
if (result)
{
return true;
}
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.User.PopupMessage(Owner, localizationManager.GetString("Unable to insert capacitor"));
return false;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
RemoveItem(eventArgs.User);
}
[Verb]
private sealed class InsertVerb : Verb<PowerCellChargerComponent>
{
protected override void GetData(IEntity user, PowerCellChargerComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity != null || handsComponent.GetActiveHand == null)
{
data.Visibility = VerbVisibility.Disabled;
data.Text = "Insert";
return;
}
data.Text = $"Insert {handsComponent.GetActiveHand.Owner.Name}";
}
protected override void Activate(IEntity user, PowerCellChargerComponent component)
{
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
return;
}
if (handsComponent.GetActiveHand == null)
{
return;
}
var userItem = handsComponent.GetActiveHand.Owner;
handsComponent.Drop(userItem);
component.TryInsertItem(userItem);
}
}
[Verb]
private sealed class EjectVerb : Verb<PowerCellChargerComponent>
{
protected override void GetData(IEntity user, PowerCellChargerComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity == null)
{
data.Text = "Eject";
data.Visibility = VerbVisibility.Disabled;
return;
}
data.Text = $"Eject {component._container.ContainedEntity.Name}";
}
protected override void Activate(IEntity user, PowerCellChargerComponent component)
{
component.RemoveItem(user);
}
}
public bool TryInsertItem(IEntity entity)
{
if (!entity.HasComponent<PowerCellComponent>() ||
_container.ContainedEntity != null)
{
return false;
}
if (!_container.Insert(entity))
{
return false;
}
UpdateStatus();
return true;
}
protected override CellChargerStatus GetStatus()
{
if (!_powerDevice.Powered)
{
return CellChargerStatus.Off;
}
if (_container.ContainedEntity == null)
{
return CellChargerStatus.Empty;
}
if (_container.ContainedEntity.TryGetComponent(out PowerCellComponent component) &&
Math.Abs(component.Capacity - component.Charge) < 0.01)
{
return CellChargerStatus.Charged;
}
return CellChargerStatus.Charging;
}
protected override void TransferPower(float frameTime)
{
// Two numbers: One for how much power actually goes into the device (chargeAmount) and
// chargeLoss which is how much is drawn from the powernet
var cellComponent = _container.ContainedEntity.GetComponent<PowerCellComponent>();
var chargeLoss = cellComponent.RequestCharge(frameTime) * _transferRatio;
_powerDevice.Load = chargeLoss;
if (!_powerDevice.Powered)
{
// No power: Event should update to Off status
return;
}
var chargeAmount = chargeLoss * _transferEfficiency;
cellComponent.AddCharge(chargeAmount);
// Just so the sprite won't be set to 99.99999% visibility
if (cellComponent.Capacity - cellComponent.Charge < 0.01)
{
cellComponent.Charge = cellComponent.Capacity;
}
UpdateStatus();
}
}
}

View File

@@ -1,183 +0,0 @@
using System;
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.GameObjects.Components.Power.Chargers
{
/// <summary>
/// This is used for the lasergun / flash rechargers
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))]
public sealed class WeaponCapacitorChargerComponent : BaseCharger, IActivate, IInteractUsing
{
public override string Name => "WeaponCapacitorCharger";
public override double CellChargePercent => _container.ContainedEntity != null ?
_container.ContainedEntity.GetComponent<ServerBatteryBarrelComponent>().PowerCell.Charge /
_container.ContainedEntity.GetComponent<ServerBatteryBarrelComponent>().PowerCell.Capacity * 100 : 0.0f;
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
var result = TryInsertItem(eventArgs.Using);
if (!result)
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.User.PopupMessage(Owner, localizationManager.GetString("Unable to insert capacitor"));
}
return result;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
RemoveItem(eventArgs.User);
}
[Verb]
private sealed class InsertVerb : Verb<WeaponCapacitorChargerComponent>
{
protected override void GetData(IEntity user, WeaponCapacitorChargerComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (handsComponent.GetActiveHand == null)
{
data.Visibility = VerbVisibility.Disabled;
data.Text = "Insert";
return;
}
if (component._container.ContainedEntity != null)
{
data.Visibility = VerbVisibility.Disabled;
}
data.Text = $"Insert {handsComponent.GetActiveHand.Owner.Name}";
}
protected override void Activate(IEntity user, WeaponCapacitorChargerComponent component)
{
if (!user.TryGetComponent(out HandsComponent handsComponent))
{
return;
}
if (handsComponent.GetActiveHand == null)
{
return;
}
var userItem = handsComponent.GetActiveHand.Owner;
handsComponent.Drop(userItem);
component.TryInsertItem(userItem);
}
}
[Verb]
private sealed class EjectVerb : Verb<WeaponCapacitorChargerComponent>
{
protected override void GetData(IEntity user, WeaponCapacitorChargerComponent component, VerbData data)
{
if (!ActionBlockerSystem.CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
if (component._container.ContainedEntity == null)
{
data.Visibility = VerbVisibility.Disabled;
data.Text = "Eject";
return;
}
data.Text = $"Eject {component._container.ContainedEntity.Name}";
}
protected override void Activate(IEntity user, WeaponCapacitorChargerComponent component)
{
component.RemoveItem(user);
}
}
public bool TryInsertItem(IEntity entity)
{
if (!entity.HasComponent<ServerBatteryBarrelComponent>() ||
_container.ContainedEntity != null)
{
return false;
}
if (!_container.Insert(entity))
{
return false;
}
UpdateStatus();
return true;
}
protected override CellChargerStatus GetStatus()
{
if (!_powerDevice.Powered)
{
return CellChargerStatus.Off;
}
if (_container.ContainedEntity == null)
{
return CellChargerStatus.Empty;
}
if (_container.ContainedEntity.TryGetComponent(out ServerBatteryBarrelComponent component) &&
Math.Abs(component.PowerCell.Capacity - component.PowerCell.Charge) < 0.01)
{
return CellChargerStatus.Charged;
}
return CellChargerStatus.Charging;
}
protected override void TransferPower(float frameTime)
{
// Two numbers: One for how much power actually goes into the device (chargeAmount) and
// chargeLoss which is how much is drawn from the powernet
var powerCell = _container.ContainedEntity.GetComponent<ServerBatteryBarrelComponent>().PowerCell;
var chargeLoss = powerCell.RequestCharge(frameTime) * _transferRatio;
_powerDevice.Load = chargeLoss;
if (!_powerDevice.Powered)
{
// No power: Event should update to Off status
return;
}
var chargeAmount = chargeLoss * _transferEfficiency;
powerCell.AddCharge(chargeAmount);
// Just so the sprite won't be set to 99.99999% visibility
if (powerCell.Capacity - powerCell.Charge < 0.01)
{
powerCell.Charge = powerCell.Capacity;
}
UpdateStatus();
}
}
}

View File

@@ -1,53 +1,37 @@
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Batteries that have update an <see cref="AppearanceComponent"/> based on their charge percent.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(PowerStorageComponent))]
public class PowerCellComponent : PowerStorageComponent
[ComponentReference(typeof(BatteryComponent))]
public class PowerCellComponent : BatteryComponent
{
public override string Name => "PowerCell";
private AppearanceComponent _appearance;
public override float Charge
{
get => base.Charge;
set
{
base.Charge = value;
_updateAppearance();
}
}
public override void Initialize()
{
base.Initialize();
Owner.TryGetComponent(out _appearance);
_appearance = Owner.GetComponent<AppearanceComponent>();
CurrentCharge = MaxCharge;
UpdateVisuals();
}
public override void DeductCharge(float toDeduct)
protected override void OnChargeChanged()
{
base.DeductCharge(toDeduct);
_updateAppearance();
ChargeChanged();
base.OnChargeChanged();
UpdateVisuals();
}
public override void AddCharge(float charge)
private void UpdateVisuals()
{
base.AddCharge(charge);
_updateAppearance();
ChargeChanged();
}
private void _updateAppearance()
{
_appearance?.SetData(PowerCellVisuals.ChargeLevel, Charge / Capacity);
_appearance?.SetData(PowerCellVisuals.ChargeLevel, CurrentCharge / MaxCharge);
}
}
}

View File

@@ -1,108 +0,0 @@
using System;
using System.Text;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
namespace Content.Server.GameObjects.Components.Power
{
[RegisterComponent]
public class PowerDebugTool : SharedPowerDebugTool, IAfterInteract
{
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null)
{
return;
}
var builder = new StringBuilder();
builder.AppendFormat("Entity: {0} ({1})\n", eventArgs.Target.Name, eventArgs.Target.Uid);
if (eventArgs.Target.TryGetComponent<PowerNodeComponent>(out var node))
{
builder.AppendFormat("Power Node:\n");
if (node.Parent == null)
{
builder.Append(" No Powernet!\n");
}
else
{
var net = node.Parent;
builder.AppendFormat(@" Powernet: {0}
Wires: {1}, Nodes: {2}
Generators: {3}, Loaders: {4},
StorageS: {5}, StorageC: {6},
Load: {7}, Supply: {8},
LAvail: {9}, LDraw: {10},
LDemand: {11}, LDemandWS: {12},
",
net.Uid,
net.NodeList.Count, net.WireList.Count,
net.GeneratorCount, net.DeviceCount,
net.PowerStorageSupplierCount, net.PowerStorageConsumerCount,
net.Load, net.Supply,
net.LastTotalAvailable, net.LastTotalDraw,
net.LastTotalDemand, net.LastTotalDemandWithSuppliers);
}
}
if (eventArgs.Target.TryGetComponent<PowerDeviceComponent>(out var device))
{
builder.AppendFormat(@"Power Device:
Load: {0} W
Priority: {1}
Drawtype: {2}, Connected: {3}
Powered: {4}
", device.Load, device.Priority, device.DrawType, device.Connected, device.Powered);
if (device.Connected == DrawTypes.Provider || device.Connected == DrawTypes.Both)
{
builder.Append(" Providers:\n");
foreach (var provider in device.AvailableProviders)
{
var providerTransform = provider.Owner.GetComponent<ITransformComponent>();
builder.AppendFormat(" {0} ({1}) @ {2}", provider.Owner.Name, provider.Owner.Uid, providerTransform.GridPosition);
if (device.Provider == provider)
{
builder.Append(" (CURRENT)");
}
builder.Append('\n');
}
}
}
if (eventArgs.Target.TryGetComponent<PowerStorageNetComponent>(out var storage))
{
var stateSeconds = (DateTime.Now - storage.LastChargeStateChange).TotalSeconds;
builder.AppendFormat(@"Power Storage:
Capacity: {0}, Charge: {1}, ChargeRate: {2}, DistributionRate: {3}, ChargePowernet: {4}
LastChargeState: {5} ({6}), LastChargeStateChange: {7:0.00} seconds ago.
", storage.Capacity, storage.Charge, storage.ChargeRate, storage.DistributionRate, storage.ChargePowernet, storage.LastChargeState, storage.GetChargeState(), stateSeconds);
}
if (eventArgs.Target.TryGetComponent<PowerTransferComponent>(out var transfer))
{
builder.AppendFormat(@"Power Transfer:
Powernet: {0}
", transfer.Parent.Uid);
}
OpenDataWindowClientSide(eventArgs.User, builder.ToString());
}
private void OpenDataWindowClientSide(IEntity user, string data)
{
if (!user.TryGetComponent<IActorComponent>(out var actor))
{
return;
}
SendNetworkMessage(new OpenDataWindowMsg(data), actor.playerSession.ConnectedClient);
}
}
}

View File

@@ -1,432 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Component that requires power to function
/// </summary>
[RegisterComponent]
public class PowerDeviceComponent : Component, IExamine
{
public override string Name => "PowerDevice";
protected override void Startup()
{
base.Startup();
if (DrawType != DrawTypes.Node)
{
var componentMgr = IoCManager.Resolve<IComponentManager>();
AvailableProviders = componentMgr.GetAllComponents<PowerProviderComponent>().Where(x => x.CanServiceDevice(this)).ToList();
ConnectToBestProvider();
}
}
protected virtual bool SaveLoad => true;
/// <summary>
/// The method of draw we will try to use to place our load set via component parameter, defaults to using power providers
/// </summary>
[ViewVariables]
public DrawTypes DrawType
{
get => _drawType ?? DefaultDrawType;
protected set => _drawType = value;
}
private DrawTypes? _drawType;
protected virtual DrawTypes DefaultDrawType => DrawTypes.Provider;
/// <summary>
/// The power draw method we are currently connected to and using
/// </summary>
[ViewVariables]
public DrawTypes Connected { get; protected set; } = DrawTypes.None;
[ViewVariables]
public bool Powered { get; private set; } = false;
/// <summary>
/// Is an external power source currently available?
/// </summary>
[ViewVariables]
public bool ExternalPowered
{
get => _externalPowered;
set
{
_externalPowered = value;
UpdatePowered();
}
}
private bool _externalPowered = false;
/// <summary>
/// Is an internal power source currently available?
/// </summary>
[ViewVariables]
protected bool InternalPowered
{
get => _internalPowered;
set
{
_internalPowered = value;
UpdatePowered();
}
}
private bool _internalPowered = false;
/// <summary>
/// Priority for powernet draw, lower will draw first, defined in powernet.cs
/// </summary>
[ViewVariables]
public virtual Powernet.Priority Priority
{
get => _priority;
protected set => _priority = value;
}
private Powernet.Priority _priority = Powernet.Priority.Medium;
[ViewVariables(VVAccess.ReadWrite)]
public bool IsPowerCut
{
get => _isPowerCut;
set
{
_isPowerCut = value;
UpdatePowered();
}
}
private float _load = 100; //arbitrary magic number to start
/// <summary>
/// Power load from this entity.
/// In Watts.
/// </summary>
[ViewVariables]
public float Load
{
get => _load;
set => UpdateLoad(value);
}
/// <summary>
/// All the power providers that we are within range of
/// </summary>
public List<PowerProviderComponent> AvailableProviders = new List<PowerProviderComponent>();
private PowerProviderComponent _provider;
private bool _isPowerCut;
/// <summary>
/// A power provider that will handle our load, if we are linked to any
/// </summary>
[ViewVariables]
public PowerProviderComponent Provider
{
get => _provider;
set
{
Connected = DrawTypes.Provider;
if (_provider != null)
{
_provider.RemoveDevice(this);
}
_provider = value;
if (value != null)
{
_provider.AddDevice(this);
}
else
{
Connected = DrawTypes.None;
}
}
}
public event EventHandler<PowerStateEventArgs> OnPowerStateChanged;
public override void OnAdd()
{
base.OnAdd();
if (DrawType == DrawTypes.Node || DrawType == DrawTypes.Both)
{
if (!Owner.TryGetComponent(out PowerNodeComponent node))
{
Owner.AddComponent<PowerNodeComponent>();
node = Owner.GetComponent<PowerNodeComponent>();
}
node.OnPowernetConnect += PowernetConnect;
node.OnPowernetDisconnect += PowernetDisconnect;
node.OnPowernetRegenerate += PowernetRegenerate;
}
}
/// <inheritdoc />
protected override void Shutdown()
{
if (Owner.TryGetComponent(out PowerNodeComponent node))
{
if (node.Parent != null && node.Parent.HasDevice(this))
{
node.Parent.RemoveDevice(this);
}
node.OnPowernetConnect -= PowernetConnect;
node.OnPowernetDisconnect -= PowernetDisconnect;
node.OnPowernetRegenerate -= PowernetRegenerate;
}
Connected = DrawTypes.None;
if (Provider != null)
{
Provider = null;
}
base.Shutdown();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
var drawType = DrawType;
serializer.DataField(ref drawType, "drawtype", DefaultDrawType);
DrawType = drawType;
serializer.DataField(ref _priority, "priority", Powernet.Priority.Medium);
if (SaveLoad)
{
serializer.DataField(ref _load, "load", 100);
}
}
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
if (!Powered && inDetailsRange)
{
message.AddMarkup(loc.GetString("The device is [color=orange]not powered[/color]."));
}
}
private void UpdateLoad(float value)
{
var oldLoad = _load;
_load = value;
if (Connected == DrawTypes.Node)
{
var node = Owner.GetComponent<PowerNodeComponent>();
node.Parent.UpdateDevice(this, oldLoad);
}
else if (Connected == DrawTypes.Provider)
{
Provider.UpdateDevice(this, oldLoad);
}
}
/// <summary>
/// Updates the state of whether or not this device is powered,
/// and fires off events if said state has changed.
/// </summary>
private void UpdatePowered()
{
var oldPowered = Powered;
Powered = !IsPowerCut && (ExternalPowered || InternalPowered);
if (oldPowered != Powered)
{
OnPowerStateChanged?.Invoke(this, new PowerStateEventArgs(Powered));
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(PowerDeviceVisuals.Powered, Powered);
}
}
}
/// <summary>
/// Register a new power provider as a possible connection to this device
/// </summary>
/// <param name="provider"></param>
public void AddProvider(PowerProviderComponent provider)
{
AvailableProviders.Add(provider);
if (Connected != DrawTypes.Node)
{
ConnectToBestProvider();
}
}
/// <summary>
/// Find the nearest registered power provider and connect to it
/// </summary>
private void ConnectToBestProvider()
{
//Any values we can connect to or are we already connected to a node, cancel!
if (!AvailableProviders.Any() || Connected == DrawTypes.Node || Deleted || Owner.Deleted)
return;
//Get the starting value for our loop
var position = Owner.GetComponent<ITransformComponent>().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<ITransformComponent>().WorldPosition - position).LengthSquared;
foreach (var availprovider in AvailableProviders)
{
if (availprovider.Owner.Deleted)
continue;
//Find distance to new provider
var distance = (availprovider.Owner.GetComponent<ITransformComponent>().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;
}
/// <summary>
/// Remove a power provider from being a possible connection to this device
/// </summary>
/// <param name="provider"></param>
public void RemoveProvider(PowerProviderComponent provider)
{
if (!AvailableProviders.Contains(provider))
return;
AvailableProviders.Remove(provider);
if (provider == Provider)
{
Provider = null;
ExternalPowered = false;
}
if (Connected != DrawTypes.Node)
{
ConnectToBestProvider();
}
}
/// <summary>
/// Node has become anchored to a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
protected virtual void PowernetConnect(object sender, PowernetEventArgs eventarg)
{
//This sets connected = none so it must be first
Provider = null;
eventarg.Powernet.AddDevice(this);
Connected = DrawTypes.Node;
}
/// <summary>
/// Powernet wire was remove so we need to regenerate the powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
protected virtual void PowernetRegenerate(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.AddDevice(this);
}
/// <summary>
/// Node has become unanchored from a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
protected virtual void PowernetDisconnect(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.RemoveDevice(this);
Connected = DrawTypes.None;
ConnectToBestProvider();
}
/// <summary>
/// Process mechanism to keep track of internal battery and power status.
/// </summary>
/// <param name="frametime">Time since the last process frame.</param>
internal virtual void ProcessInternalPower(float frametime)
{
if (Owner.TryGetComponent<PowerStorageComponent>(out var storage) && storage.CanDeductCharge(Load))
{
// We still keep InternalPowered correct if connected externally,
// but don't use it.
if (!ExternalPowered)
{
storage.DeductCharge(Load);
}
InternalPowered = true;
}
else
{
InternalPowered = false;
}
}
}
/// <summary>
/// The different methods that a <see cref="PowerDeviceComponent"/> can use to connect to a power network.
/// </summary>
public enum DrawTypes
{
/// <summary>
/// This device cannot be connected to a power network.
/// </summary>
None = 0,
/// <summary>
/// This device can connect to a <see cref=""/>
/// </summary>
Node = 1,
Provider = 2,
Both = 3,
}
public class PowerStateEventArgs : EventArgs
{
public readonly bool Powered;
public PowerStateEventArgs(bool powered)
{
Powered = powered;
}
}
}

View File

@@ -1,101 +0,0 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Component that creates power and supplies it to the powernet
/// </summary>
[RegisterComponent]
public class PowerGeneratorComponent : Component
{
public override string Name => "PowerGenerator";
/// <summary>
/// Power supply from this entity
/// </summary>
private float _supply = 1000; //arbitrary initial magic number to start
[ViewVariables(VVAccess.ReadWrite)]
public float Supply
{
get => _supply;
set { UpdateSupply(value); }
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _supply, "supply", 1000);
}
public override void OnAdd()
{
base.OnAdd();
if (!Owner.TryGetComponent(out PowerNodeComponent node))
{
Owner.AddComponent<PowerNodeComponent>();
node = Owner.GetComponent<PowerNodeComponent>();
}
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<PowerNodeComponent>();
node?.Parent?.UpdateGenerator(this);
}
/// <summary>
/// Node has become anchored to a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
private void PowernetConnect(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.AddGenerator(this);
}
/// <summary>
/// Node has had its powernet regenerated
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
private void PowernetRegenerate(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.AddGenerator(this);
}
/// <summary>
/// Node has become unanchored from a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
private void PowernetDisconnect(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.RemoveGenerator(this);
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
public abstract class BasePowerNetComponent : BaseNetConnectorComponent<IPowerNet>
{
protected override IPowerNet NullNet => PowerNetNodeGroup.NullNet;
}
}

View File

@@ -0,0 +1,73 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
/// <summary>
/// Uses charge from a <see cref="BatteryComponent"/> to supply power via a <see cref="PowerSupplierComponent"/>.
/// </summary>
[RegisterComponent]
public class BatteryDischargerComponent : Component
{
public override string Name => "BatteryDischarger";
[ViewVariables]
private BatteryComponent _battery;
[ViewVariables]
private PowerSupplierComponent _supplier;
[ViewVariables(VVAccess.ReadWrite)]
public int ActiveSupplyRate { get => _activeSupplyRate; set => SetActiveSupplyRate(value); }
private int _activeSupplyRate;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _activeSupplyRate, "activeSupplyRate", 50);
}
public override void Initialize()
{
base.Initialize();
_battery = Owner.GetComponent<BatteryComponent>();
_supplier = Owner.GetComponent<PowerSupplierComponent>();
UpdateSupplyRate();
}
public void Update(float frameTime)
{
//Simplified implementation - if the battery is empty, and charge is being added to the battery
//at a lower rate that this is using it, the charge is used without creating power supply.
_battery.CurrentCharge -= ActiveSupplyRate * frameTime;
UpdateSupplyRate();
}
private void UpdateSupplyRate()
{
if (_battery.BatteryState == BatteryState.Empty)
{
SetSupplierSupplyRate(0);
}
else
{
SetSupplierSupplyRate(ActiveSupplyRate);
}
}
private void SetSupplierSupplyRate(int newSupplierSupplyRate)
{
if (_supplier.SupplyRate != newSupplierSupplyRate)
{
_supplier.SupplyRate = newSupplierSupplyRate;
}
}
private void SetActiveSupplyRate(int newEnabledSupplyRate)
{
_activeSupplyRate = newEnabledSupplyRate;
UpdateSupplyRate();
}
}
}

View File

@@ -0,0 +1,72 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
/// <summary>
/// Takes power via a <see cref="PowerConsumerComponent"/> to charge a <see cref="BatteryComponent"/>.
/// </summary>
[RegisterComponent]
public class BatteryStorageComponent : Component
{
public override string Name => "BatteryStorage";
[ViewVariables(VVAccess.ReadWrite)]
public int ActiveDrawRate { get => _activeDrawRate; set => SetActiveDrawRate(value); }
private int _activeDrawRate;
[ViewVariables]
private BatteryComponent _battery;
[ViewVariables]
public PowerConsumerComponent Consumer { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _activeDrawRate, "activeDrawRate", 100);
}
public override void Initialize()
{
base.Initialize();
_battery = Owner.GetComponent<BatteryComponent>();
Consumer = Owner.GetComponent<PowerConsumerComponent>();
UpdateDrawRate();
}
public void Update(float frameTime)
{
//Simplified implementation - If a frame adds more power to a partially full battery than it can hold, the power is lost.
_battery.CurrentCharge += Consumer.ReceivedPower * frameTime;
UpdateDrawRate();
}
private void UpdateDrawRate()
{
if (_battery.BatteryState == BatteryState.Full)
{
SetConsumerDraw(0);
}
else
{
SetConsumerDraw(ActiveDrawRate);
}
}
private void SetConsumerDraw(int newConsumerDrawRate)
{
if (Consumer.DrawRate != newConsumerDrawRate)
{
Consumer.DrawRate = newConsumerDrawRate;
}
}
private void SetActiveDrawRate(int newEnabledDrawRate)
{
_activeDrawRate = newEnabledDrawRate;
UpdateDrawRate();
}
}
}

View File

@@ -0,0 +1,78 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Diagnostics;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
[RegisterComponent]
public class PowerConsumerComponent : BasePowerNetComponent
{
public override string Name => "PowerConsumer";
/// <summary>
/// How much power this needs to be fully powered.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int DrawRate { get => _drawRate; set => SetDrawRate(value); }
private int _drawRate;
/// <summary>
/// Determines which <see cref="PowerConsumerComponent"/>s receive power when there is not enough
/// power for each.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Priority Priority { get => _priority; set => SetPriority(value); }
private Priority _priority;
/// <summary>
/// How much power this is currently receiving from <see cref="PowerSupplierComponent"/>s.
/// </summary>
[ViewVariables]
public int ReceivedPower { get => _receivedPower; set => SetReceivedPower(value); }
private int _receivedPower;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _drawRate, "drawRate", 0);
serializer.DataField(ref _priority, "priority", Priority.First);
}
protected override void AddSelfToNet(IPowerNet powerNet)
{
powerNet.AddConsumer(this);
}
protected override void RemoveSelfFromNet(IPowerNet powerNet)
{
powerNet.RemoveConsumer(this);
}
private void SetDrawRate(int newDrawRate)
{
var oldDrawRate = DrawRate;
_drawRate = newDrawRate; //must be set before updating powernet, as it checks the DrawRate of every consumer
Net.UpdateConsumerDraw(this, oldDrawRate, newDrawRate);
}
private void SetReceivedPower(int newReceivedPower)
{
Debug.Assert(newReceivedPower >= 0 && newReceivedPower <= DrawRate);
_receivedPower = newReceivedPower;
}
private void SetPriority(Priority newPriority)
{
Net.UpdateConsumerPriority(this, Priority, newPriority);
_priority = newPriority;
}
}
public enum Priority
{
First,
Last,
}
}

View File

@@ -0,0 +1,39 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
[RegisterComponent]
public class PowerSupplierComponent : BasePowerNetComponent
{
public override string Name => "PowerSupplier";
[ViewVariables(VVAccess.ReadWrite)]
public int SupplyRate { get => _supplyRate; set => SetSupplyRate(value); }
private int _supplyRate;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _supplyRate, "supplyRate", 0);
}
protected override void AddSelfToNet(IPowerNet powerNet)
{
powerNet.AddSupplier(this);
}
protected override void RemoveSelfFromNet(IPowerNet powerNet)
{
powerNet.RemoveSupplier(this);
}
private void SetSupplyRate(int newSupplyRate)
{
Net.UpdateSupplierSupply(this, SupplyRate, newSupplyRate);
_supplyRate = newSupplyRate;
}
}
}

View File

@@ -0,0 +1,89 @@
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using System;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Handles the "user-facing" side of the actual SMES object.
/// This is operations that are specific to the SMES, like UI and visuals.
/// Code interfacing with the powernet is handled in <see cref="BatteryStorageComponent"/> and <see cref="BatteryDischargerComponent"/>.
/// </summary>
[RegisterComponent]
public class SmesComponent : Component
{
public override string Name => "Smes";
private BatteryComponent _battery;
private AppearanceComponent _appearance;
private int _lastChargeLevel = 0;
private TimeSpan _lastChargeLevelChange;
private ChargeState _lastChargeState;
private TimeSpan _lastChargeStateChange;
private const int VisualsChangeDelay = 1;
#pragma warning disable 649
[Dependency] private readonly IGameTiming _gameTiming;
#pragma warning restore 649
public override void Initialize()
{
base.Initialize();
_battery = Owner.GetComponent<BatteryComponent>();
_appearance = Owner.GetComponent<AppearanceComponent>();
}
public void OnUpdate()
{
var newLevel = GetNewChargeLevel();
if (newLevel != _lastChargeLevel && _lastChargeLevelChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastChargeLevel = newLevel;
_lastChargeLevelChange = _gameTiming.CurTime;
_appearance.SetData(SmesVisuals.LastChargeLevel, newLevel);
}
var newChargeState = GetNewChargeState();
if (newChargeState != _lastChargeState && _lastChargeStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastChargeState = newChargeState;
_lastChargeStateChange = _gameTiming.CurTime;
_appearance.SetData(SmesVisuals.LastChargeState, newChargeState);
}
}
private int GetNewChargeLevel()
{
return ContentHelpers.RoundToLevels(_battery.CurrentCharge, _battery.MaxCharge, 6);
}
private ChargeState GetNewChargeState()
{
var supplier = Owner.GetComponent<PowerSupplierComponent>();
var consumer = Owner.GetComponent<PowerConsumerComponent>();
if (supplier.SupplyRate > 0 && consumer.DrawRate != consumer.ReceivedPower)
{
return ChargeState.Discharging;
}
else if (supplier.SupplyRate == 0 && consumer.DrawRate > 0)
{
return ChargeState.Charging;
}
else
{
return ChargeState.Still;
}
}
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
@@ -20,9 +21,9 @@ namespace Content.Server.GameObjects.Components.Power
#pragma warning restore 649
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
private PowerSolarSystem _powerSolarSystem;
private bool Powered => _powerDevice.Powered;
private bool Powered => _powerReceiver.Powered;
public override void Initialize()
{
@@ -30,7 +31,7 @@ namespace Content.Server.GameObjects.Components.Power
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(SolarControlConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerSolarSystem = _entitySystemManager.GetEntitySystem<PowerSolarSystem>();
}

View File

@@ -1,5 +1,6 @@
using System;
using System;
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
@@ -27,14 +28,14 @@ namespace Content.Server.GameObjects.Components.Power
{
public override string Name => "SolarPanel";
private PowerGeneratorComponent _powerGenerator;
private PowerSupplierComponent _powerSupplier;
/// <summary>
/// Maximum supply output by this panel (coverage = 1)
/// </summary>
private float _maxSupply = 1500;
private int _maxSupply = 1500;
[ViewVariables(VVAccess.ReadWrite)]
public float MaxSupply
public int MaxSupply
{
get => _maxSupply;
set {
@@ -72,15 +73,15 @@ namespace Content.Server.GameObjects.Components.Power
private void UpdateSupply()
{
if (_powerGenerator != null)
_powerGenerator.Supply = _maxSupply * _coverage;
if (_powerSupplier != null)
_powerSupplier.SupplyRate = (int) (_maxSupply * _coverage);
}
public override void Initialize()
{
base.Initialize();
_powerGenerator = Owner.GetComponent<PowerGeneratorComponent>();
_powerSupplier = Owner.GetComponent<PowerSupplierComponent>();
UpdateSupply();
}

View File

@@ -1,131 +0,0 @@
using System;
using System.Linq;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Component that connects to the powernet
/// </summary>
[RegisterComponent]
public class PowerNodeComponent : Component
{
public override string Name => "PowerNode";
/// <summary>
/// The powernet this node is connected to
/// </summary>
[ViewVariables]
public Powernet Parent { get; set; }
/// <summary>
/// An event handling when this node connects to a powernet
/// </summary>
public event EventHandler<PowernetEventArgs> OnPowernetConnect;
/// <summary>
/// An event handling when this node disconnects from a powernet
/// </summary>
public event EventHandler<PowernetEventArgs> OnPowernetDisconnect;
/// <summary>
/// An event that registers us to a regenerating powernet
/// </summary>
public event EventHandler<PowernetEventArgs> OnPowernetRegenerate;
protected override void Startup()
{
base.Startup();
TryCreatePowernetConnection();
}
public override void OnRemove()
{
DisconnectFromPowernet();
base.OnRemove();
}
/// <summary>
/// Find a nearby wire which will have a powernet and connect ourselves to its powernet
/// </summary>
public void TryCreatePowernetConnection()
{
if (Parent != null)
{
return;
}
var position = Owner.Transform.WorldPosition;
var sgc = Owner.GetComponent<SnapGridComponent>();
var wire = sgc.GetCardinalNeighborCells()
.SelectMany(x => x.GetLocal()).Distinct()
.Select(x => x.TryGetComponent<PowerTransferComponent>(out var c) ? c : null)
.Where(x => x != null).Distinct()
.ToArray()
.OrderByDescending(x => (x.Owner.Transform.WorldPosition - position).Length)
.FirstOrDefault();
if (wire?.Parent != null)
{
ConnectToPowernet(wire.Parent);
}
}
/// <summary>
/// Triggers event telling power components that we connected to a powernet
/// </summary>
/// <param name="toconnect"></param>
public void ConnectToPowernet(Powernet toconnect)
{
Parent = toconnect;
Parent.NodeList.Add(this);
OnPowernetConnect?.Invoke(this, new PowernetEventArgs(Parent));
}
/// <summary>
/// Triggers event telling power components that we haven't disconnected but have readded ourselves to a regenerated powernet
/// </summary>
/// <param name="toconnect"></param>
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));
}
/// <summary>
/// Triggers event telling power components we have exited any powernets
/// </summary>
public void DisconnectFromPowernet()
{
if (Parent == null)
{
return;
}
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; }
}
}

View File

@@ -1,292 +0,0 @@
// Only unused on .NET Core due to KeyValuePair.Deconstruct
// ReSharper disable once RedundantUsingDirective
using Robust.Shared.Utility;
using System.Collections.Generic;
using System.Linq;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Shared.Map;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Component that wirelessly connects and powers devices, connects to powernet via node and can be combined with internal storage component
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(PowerDeviceComponent))]
public class PowerProviderComponent : PowerDeviceComponent
{
public override string Name => "PowerProvider";
/// <inheritdoc />
protected override DrawTypes DefaultDrawType => DrawTypes.Node;
protected override bool SaveLoad => false;
/// <summary>
/// Variable that determines the range that the power provider will try to supply power to
/// </summary>
[ViewVariables]
public int PowerRange
{
get => _range;
private set => _range = value;
}
private int _range = 0;
/// <summary>
/// List storing all the power devices that we are currently providing power to
/// </summary>
public SortedSet<PowerDeviceComponent> DeviceLoadList =
new SortedSet<PowerDeviceComponent>(new Powernet.DevicePriorityCompare());
/// <summary>
/// List of devices in range that we "advertised" to.
/// </summary>
public HashSet<PowerDeviceComponent> AdvertisedDevices = new HashSet<PowerDeviceComponent>();
public List<PowerDeviceComponent> DepoweredDevices = new List<PowerDeviceComponent>();
public override Powernet.Priority Priority { get; protected set; } = Powernet.Priority.Provider;
private bool _mainBreaker = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool MainBreaker
{
get => _mainBreaker;
set
{
if (_mainBreaker == value)
{
return;
}
_mainBreaker = value;
if (!value)
{
DepowerAllDevices();
Load = 0;
}
else
{
Load = TheoreticalLoad;
}
}
}
private float _theoreticalLoad = 0f;
[ViewVariables]
public float TheoreticalLoad
{
get => _theoreticalLoad;
set
{
_theoreticalLoad = value;
if (MainBreaker)
{
Load = value;
}
}
}
public PowerProviderComponent()
{
Load = 0;
}
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
foreach (var device in AdvertisedDevices.ToList())
{
device.RemoveProvider(this);
}
AdvertisedDevices.Clear();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _range, "range", 0);
}
internal override void ProcessInternalPower(float frametime)
{
// Right now let's just assume that APCs don't have a power demand themselves and as such they're always marked as powered.
InternalPowered = true;
if (!Owner.TryGetComponent<PowerStorageComponent>(out var storage))
{
return;
}
if (!MainBreaker)
{
return;
}
if (ExternalPowered)
{
PowerAllDevices();
return;
}
if (storage.CanDeductCharge(TheoreticalLoad * frametime))
{
PowerAllDevices();
storage.DeductCharge(TheoreticalLoad * frametime);
return;
}
var remainingEnergy = storage.AvailableCharge(frametime);
var usedEnergy = 0f;
foreach (var device in DeviceLoadList)
{
var deviceLoad = device.Load * frametime;
if (deviceLoad > remainingEnergy)
{
device.ExternalPowered = false;
DepoweredDevices.Add(device);
}
else
{
if (!device.ExternalPowered)
{
DepoweredDevices.Remove(device);
device.ExternalPowered = true;
}
usedEnergy += deviceLoad;
remainingEnergy -= deviceLoad;
}
}
storage.DeductCharge(usedEnergy);
}
private void PowerAllDevices()
{
foreach (var device in DepoweredDevices)
{
device.ExternalPowered = true;
}
DepoweredDevices.Clear();
}
private void DepowerAllDevices()
{
foreach (var device in DeviceLoadList)
{
device.ExternalPowered = false;
DepoweredDevices.Add(device);
}
}
protected override void PowernetConnect(object sender, PowernetEventArgs eventarg)
{
base.PowernetConnect(sender, eventarg);
//Find devices within range to take under our control
var entMgr = IoCManager.Resolve<IServerEntityManager>();
var position = Owner.GetComponent<ITransformComponent>().WorldPosition;
var entities = entMgr.GetEntitiesInRange(Owner, PowerRange)
.Where(x => x.HasComponent<PowerDeviceComponent>());
foreach (var entity in entities)
{
var device = entity.GetComponent<PowerDeviceComponent>();
//Make sure the device can accept power providers to give it power
if (device.DrawType == DrawTypes.Provider || device.DrawType == DrawTypes.Both)
{
if (!AdvertisedDevices.Contains(device))
{
device.AddProvider(this);
AdvertisedDevices.Add(device);
}
}
}
}
protected override void PowernetDisconnect(object sender, PowernetEventArgs eventarg)
{
base.PowernetDisconnect(sender, eventarg);
//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 AdvertisedDevices.ToList())
{
device.RemoveProvider(this);
}
AdvertisedDevices.Clear();
}
/// <summary>
/// Register a continuous load from a device connected to the powernet
/// </summary>
public void AddDevice(PowerDeviceComponent device)
{
DeviceLoadList.Add(device);
TheoreticalLoad += device.Load;
if (!device.Powered)
DepoweredDevices.Add(device);
}
/// <summary>
/// Update one of the loads from a deviceconnected to the powernet
/// </summary>
public void UpdateDevice(PowerDeviceComponent device, float oldLoad)
{
if (DeviceLoadList.Contains(device))
{
TheoreticalLoad = TheoreticalLoad - oldLoad + device.Load;
}
}
/// <summary>
/// Remove a continuous load from a device connected to the powernet
/// </summary>
public void RemoveDevice(PowerDeviceComponent device)
{
if (DeviceLoadList.Contains(device))
{
TheoreticalLoad -= device.Load;
DeviceLoadList.Remove(device);
if (DepoweredDevices.Contains(device))
DepoweredDevices.Remove(device);
}
else
{
Logger.WarningS("power", "We tried to remove device {0} twice from the same {1}, somehow.",
device.Owner, Owner);
}
}
/// <summary>
/// Whether the device can be serviced by this provider.
/// </summary>
public bool CanServiceDevice(PowerDeviceComponent device)
{
// Stops an APC from trying to connect to itself
if (this == device)
return false;
return device.Owner.Transform.MapID == Owner.Transform.MapID &&
(device.Owner.Transform.WorldPosition - Owner.Transform.WorldPosition).Length <= _range;
}
}
}

View File

@@ -1,179 +0,0 @@
using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Power;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Stores electrical energy. Used by power cells and SMESes.
/// </summary>
public abstract class PowerStorageComponent : Component, IExamine
{
[ViewVariables]
public ChargeState LastChargeState { get; private set; } = ChargeState.Still;
public DateTime LastChargeStateChange { get; private set; }
/// <summary>
/// Maximum amount of energy the internal battery can store.
/// In Joules.
/// </summary>
[ViewVariables]
public float Capacity => _capacity;
private float _capacity = 10000; // Arbitrary value, replace.
/// <summary>
/// Energy the battery is currently storing.
/// In Joules.
/// In most cases you should use <see cref="DeductCharge"/> and <see cref="AddCharge"/> to modify this.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public virtual float Charge
{
get => _charge;
set => _charge = value;
}
private float _charge = 0;
/// <summary>
/// Rate at which energy will be taken to charge internal battery.
/// In Watts.
/// </summary>
[ViewVariables]
public float ChargeRate => _chargeRate;
private float _chargeRate = 1000;
/// <summary>
/// Rate at which energy will be distributed to the powernet if needed.
/// In Watts.
/// </summary>
[ViewVariables]
public float DistributionRate => _distributionRate;
private float _distributionRate = 1000;
[ViewVariables]
public bool Full => Charge >= Capacity;
public event Action OnChargeChanged;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _capacity, "capacity", 10000);
serializer.DataField(ref _charge, "charge", 0);
serializer.DataField(ref _chargeRate, "chargerate", 1000);
serializer.DataField(ref _distributionRate, "distributionrate", 1000);
}
protected virtual void ChargeChanged()
{
if (OnChargeChanged != null)
{ //Only fire this event if anyone actually subscribes to it
OnChargeChanged.Invoke();
}
}
/// <summary>
/// Checks if the storage can supply the amount of charge directly requested
/// </summary>
public bool CanDeductCharge(float toDeduct)
{
if (Charge >= toDeduct)
return true;
return false;
}
/// <summary>
/// Deducts the requested charge from the energy storage
/// </summary>
public virtual void DeductCharge(float toDeduct)
{
_charge = Math.Max(0, Charge - toDeduct);
LastChargeState = ChargeState.Discharging;
LastChargeStateChange = DateTime.Now;
ChargeChanged();
}
public virtual void AddCharge(float charge)
{
_charge = Math.Min(Capacity, Charge + charge);
LastChargeState = ChargeState.Charging;
LastChargeStateChange = DateTime.Now;
ChargeChanged();
}
/// <summary>
/// Returns the amount of energy that can be taken in by this storage in the specified amount of time.
/// </summary>
public float RequestCharge(float frameTime)
{
return Math.Min(ChargeRate * frameTime, Capacity - Charge);
}
/// <summary>
/// Returns the amount of energy available for discharge in the specified amount of time.
/// </summary>
public float AvailableCharge(float frameTime)
{
return Math.Min(DistributionRate * frameTime, Charge);
}
/// <summary>
/// Tries to deduct a wattage over a certain amount of time.
/// </summary>
/// <param name="wattage">The wattage of the power drain.</param>
/// <param name="frameTime">The amount of time in this "frame".</param>
/// <returns>True if the amount of energy was deducted, false.</returns>
public bool TryDeductWattage(float wattage, float frameTime)
{
var avail = AvailableCharge(frameTime);
if (avail < wattage * frameTime)
{
return false;
}
DeductCharge(wattage * frameTime);
return true;
}
public ChargeState GetChargeState()
{
return GetChargeState(TimeSpan.FromSeconds(1));
}
public ChargeState GetChargeState(TimeSpan timeout)
{
if (LastChargeStateChange + timeout > DateTime.Now)
{
return LastChargeState;
}
return ChargeState.Still;
}
public void ChargePowerTick(float frameTime)
{
if (Full)
{
return;
}
AddCharge(RequestCharge(frameTime));
}
/// <inheritdoc />
public void Examine(FormattedMessage message, bool inDetailsRange)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
var chargePercent = Math.Round(100*Charge/Capacity, 2);
message.AddMarkup(loc.GetString(
"[color=yellow]Charge: {0}J / {1}J ({2}%)\nRate: {3}W IN, {4}W OUT[/color]",
Math.Round(Charge, 2), Capacity, chargePercent, ChargeRate, DistributionRate));
}
}
}

View File

@@ -1,103 +0,0 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Feeds energy from the powernet and may have the ability to supply back into it
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(PowerStorageComponent))]
public class PowerStorageNetComponent : PowerStorageComponent
{
public override string Name => "PowerStorage";
private bool _chargePowernet = false;
/// <summary>
/// Do we distribute power into the powernet from our stores if the powernet requires it?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool ChargePowernet
{
get => _chargePowernet;
set
{
_chargePowernet = value;
if (Owner.TryGetComponent(out PowerNodeComponent node))
{
node.Parent?.UpdateStorageType(this);
}
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _chargePowernet, "chargepowernet", false);
}
public override void OnAdd()
{
base.OnAdd();
if (!Owner.TryGetComponent(out PowerNodeComponent node))
{
Owner.AddComponent<PowerNodeComponent>();
node = Owner.GetComponent<PowerNodeComponent>();
}
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();
}
/// <summary>
/// Node has become anchored to a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
private void PowernetConnect(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.AddPowerStorage(this);
}
/// <summary>
/// Node has had its powernet regenerated
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
private void PowernetRegenerate(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.AddPowerStorage(this);
}
/// <summary>
/// Node has become unanchored from a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
private void PowernetDisconnect(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.RemovePowerStorage(this);
}
}
}

View File

@@ -1,157 +0,0 @@
using System.Linq;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Server.Utility;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Component to transfer power to nearby components, can create powernets and connect to nodes
/// </summary>
[RegisterComponent]
public class PowerTransferComponent : Component, IInteractUsing
{
public override string Name => "PowerTransfer";
/// <summary>
/// The powernet this component is connected to
/// </summary>
[ViewVariables]
public Powernet Parent { get; set; }
[ViewVariables]
public bool Regenerating { get; set; } = false;
protected override void Startup()
{
base.Startup();
if (Parent == null)
{
SpreadPowernet();
}
}
public override void OnRemove()
{
DisconnectFromPowernet();
base.OnRemove();
}
/// <summary>
/// Searches for local powernets to connect to, otherwise creates its own, and spreads powernet to nearby entities
/// </summary>
public void SpreadPowernet()
{
var entMgr = IoCManager.Resolve<IServerEntityManager>();
var sgc = Owner.GetComponent<SnapGridComponent>();
var wires = sgc.GetCardinalNeighborCells()
.SelectMany(x => x.GetLocal()).Distinct()
.Select(x => x.TryGetComponent<PowerTransferComponent>(out var c) ? c : null)
.Where(x => x != null).Distinct()
.ToArray();
//we have no parent so lets find a partner we can join his powernet
if (Parent == null || Regenerating)
{
foreach (var wire in wires)
{
if (wire.CanConnectTo())
{
ConnectToPowernet(wire.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)
{
ConnectToPowernet(new Powernet());
}
}
//Find nodes intersecting us and if not already assigned to a powernet assign them to us
var nodes = entMgr.GetEntitiesIntersecting(Owner)
.Select(x => x.TryGetComponent(out PowerNodeComponent pnc) ? pnc : null)
.Where(x => x != null);
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)
{
if (wire.Parent == null || Regenerating)
{
wire.ConnectToPowernet(Parent);
wire.SpreadPowernet();
}
else if (wire.Parent != Parent && !wire.Parent.Dirty)
{
Parent.MergePowernets(wire.Parent);
}
}
}
/// <summary>
/// Called when connecting to a new powernet, either on creation or on regeneration
/// </summary>
/// <param name="toconnect"></param>
public void ConnectToPowernet(Powernet toconnect)
{
Parent = toconnect;
Parent.WireList.Add(this);
Regenerating = false;
}
/// <summary>
/// Called when we are removed and telling the powernet that it is now dirty and must regenerate
/// </summary>
public void DisconnectFromPowernet()
{
Parent.WireList.Remove(this);
Parent.Dirty = true;
Parent = null;
}
public bool CanConnectTo()
{
return Parent != null && Parent.Dirty == false && !Regenerating;
}
public bool InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent(out ToolComponent tool)) return false;
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Cutting)) return false;
Owner.Delete();
var droppedEnt = Owner.EntityManager.SpawnEntity("CableStack", eventArgs.ClickLocation);
if (droppedEnt.TryGetComponent<StackComponent>(out var stackComp))
stackComp.Count = 1;
return true;
}
}
}

View File

@@ -1,555 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Master class for group of <see cref="PowerTransferComponent"/>, takes in and distributes power via nodes
/// </summary>
public class Powernet
{
public Powernet()
{
var powerSystem = EntitySystem.Get<PowerSystem>();
powerSystem.Powernets.Add(this);
Uid = powerSystem.NewUid();
}
/// <summary>
/// Unique identifier per powernet, used for debugging mostly.
/// </summary>
[ViewVariables]
public int Uid { get; }
/// <summary>
/// The entities that make up the powernet's physical location and allow powernet connection
/// </summary>
public readonly List<PowerTransferComponent> WireList = new List<PowerTransferComponent>();
/// <summary>
/// Entities that connect directly to the powernet through <see cref="PowerTransferComponent" /> above to add power or add power load
/// </summary>
public readonly List<PowerNodeComponent> NodeList = new List<PowerNodeComponent>();
/// <summary>
/// Subset of nodelist that adds a continuous power supply to the network
/// </summary>
private readonly Dictionary<PowerGeneratorComponent, float> GeneratorList =
new Dictionary<PowerGeneratorComponent, float>();
[ViewVariables]
public int GeneratorCount => GeneratorList.Count;
/// <summary>
/// Subset of nodelist that draw power, stores information on current continuous powernet load
/// </summary>
private readonly SortedSet<PowerDeviceComponent> DeviceLoadList =
new SortedSet<PowerDeviceComponent>(new DevicePriorityCompare());
[ViewVariables]
public int DeviceCount => DeviceLoadList.Count;
/// <summary>
/// All the devices that have been depowered by this powernet or depowered prior to being absorted into this powernet
/// </summary>
private readonly List<PowerDeviceComponent> DepoweredDevices = new List<PowerDeviceComponent>();
/// <summary>
/// A list of the energy storage components that will feed the powernet if necessary, and if there is enough power feed itself
/// </summary>
private readonly List<PowerStorageNetComponent> PowerStorageSupplierList = new List<PowerStorageNetComponent>();
[ViewVariables]
public int PowerStorageSupplierCount => PowerStorageSupplierList.Count;
/// <summary>
/// A list of energy storage components that will never feed the powernet, will try to draw energy to feed themselves if possible
/// </summary>
private readonly List<PowerStorageNetComponent> PowerStorageConsumerList = new List<PowerStorageNetComponent>();
[ViewVariables]
public int PowerStorageConsumerCount => PowerStorageConsumerList.Count;
/// <summary>
/// Static counter of all continuous load placed from devices on this power network.
/// In Watts.
/// </summary>
[ViewVariables]
public float Load { get; private set; } = 0;
/// <summary>
/// Static counter of all continuous supply from generators on this power network.
/// In Watts.
/// </summary>
[ViewVariables]
public float Supply { get; private set; } = 0;
/// <summary>
/// Variable that causes powernet to be regenerated from its wires during the next update cycle.
/// </summary>
[ViewVariables]
public bool Dirty { get; set; } = false;
// These are stats for power monitoring equipment such as APCs.
/// <summary>
/// The total supply that was available to us last tick.
/// This does not mean it was used.
/// </summary>
[ViewVariables]
public float LastTotalAvailable { get; private set; }
/// <summary>
/// The total power drawn last tick.
/// This is how much power was actually, in practice, drawn.
/// Not how much SHOULD have been drawn.
/// If avail &lt; demand, this will be just &lt;= than the actual avail
/// (e.g. if all machines need 100 W but there's 20 W excess, the 20 W will be avail but not drawn.)
/// </summary>
[ViewVariables]
public float LastTotalDraw { get; private set; }
/// <summary>
/// The amount of power that was demanded last tick.
/// This does not mean it was full filled in practice.
/// This does not include the demand from storage suppliers until the suppliers are actually capable of drawing power.
/// As such, this will quite abruptly shoot up if available rises to cover supplier charge demand too.
/// </summary>
/// <seealso cref="LastTotalDemandWithSuppliers"/>
[ViewVariables]
public float LastTotalDemand { get; private set; }
/// <summary>
/// The amount of power that was demanded last tick, ALWAYS including storage supplier draw.
/// This does not mean it was full filled in practice.
/// See <see cref="LastTotalDemand"/> for the difference.
/// </summary>
[ViewVariables]
public float LastTotalDemandWithSuppliers { get; private set; }
/// <summary>
/// The amount of power that we are lacking to properly power everything (excluding storage supplier charging).
/// </summary>
[ViewVariables]
public float Lack => Math.Max(0, LastTotalDemand - LastTotalAvailable);
/// <summary>
/// The total amount of power that wasn't used last tick.
/// This does not necessarily mean it went to waste, unused supply from storage is also counted.
/// It is ALSO not implied that if this is &gt; 0, that we have sufficient power for everything.
/// See the doc comment on <see cref="LastTotalDraw"/>.
/// </summary>
[ViewVariables]
public float Excess => Math.Max(0, LastTotalAvailable - LastTotalDraw);
public void Update(float frameTime)
{
// The amount of energy that is supplied from generators that do not care if it's used or not.
var activeSupply = Supply * frameTime;
// The total load we need to fill for machines.
var activeLoad = Load * frameTime;
// The total load from storage consumers (batteries that do not supply like an SMES)
float storageConsumerDemand = 0;
foreach (var supply in PowerStorageConsumerList)
{
storageConsumerDemand += supply.RequestCharge(frameTime);
}
// The total supply from storage suppliers.
float storageSupply = 0;
// The total load from storage suppliers (batteries that DO supply like an SMES)
float storageSupplierDemand = 0;
foreach (var supply in PowerStorageSupplierList)
{
storageSupply += supply.AvailableCharge(frameTime);
storageSupplierDemand += supply.RequestCharge(frameTime);
}
LastTotalAvailable = (storageSupply + activeSupply) / frameTime;
LastTotalDemandWithSuppliers = (activeLoad + storageConsumerDemand + storageSupplierDemand) / frameTime;
// The happy case.
// If we have enough power to feed all load and storage demand, then feed everything
if (activeSupply > activeLoad + storageConsumerDemand + storageSupplierDemand)
{
PowerAllDevices();
ChargeStorageConsumers(frameTime);
ChargeStorageSuppliers(frameTime);
LastTotalDraw = LastTotalDemand = LastTotalDemandWithSuppliers;
return;
}
LastTotalDemand = (activeLoad + storageConsumerDemand) / frameTime;
// We don't have enough power for the storage powernet suppliers, ignore powering them
// TODO: This is technically incorrect, it's totally possible to power *some* suppliers here,
// just not all.
if (activeSupply > activeLoad + storageConsumerDemand)
{
PowerAllDevices();
ChargeStorageConsumers(frameTime);
LastTotalDraw = LastTotalDemand;
return;
}
// The complex case: There is too little power to power everything without using storage suppliers (SMES).
// We have to keep track of power draw as to not incorrectly detract too much from storage suppliers.
// Calculate the total potential supply, then go through every normal load and detract.
var totalRemaining = activeSupply + storageSupply;
foreach (var device in DeviceLoadList)
{
var deviceLoad = device.Load * frameTime;
if (deviceLoad > totalRemaining)
{
device.ExternalPowered = false;
DepoweredDevices.Add(device);
}
else
{
totalRemaining -= deviceLoad;
if (!device.ExternalPowered)
{
DepoweredDevices.Remove(device);
device.ExternalPowered = true;
}
}
}
if (totalRemaining > 0)
{
// What we have left (if any) goes into storage consumers.
foreach (var consumer in PowerStorageConsumerList)
{
if (totalRemaining < 0)
{
break;
}
var demand = consumer.RequestCharge(frameTime);
if (demand == 0)
{
continue;
}
var taken = Math.Min(demand, totalRemaining);
totalRemaining -= taken;
consumer.AddCharge(taken);
}
}
LastTotalDraw = (activeSupply + storageSupply - totalRemaining) / frameTime;
// activeSupply is free to use, but storageSupply is not.
// Calculate how much of storageSupply, and deduct it from the storage suppliers.
var supplierUsed = storageSupply - totalRemaining;
// And deduct!
foreach (var supplier in PowerStorageSupplierList)
{
var load = supplier.AvailableCharge(frameTime);
if (load == 0)
{
continue;
}
var added = Math.Min(load, supplierUsed);
supplierUsed -= added;
supplier.DeductCharge(added);
if (supplierUsed <= 0)
{
return;
}
}
}
private void PowerAllDevices()
{
foreach (var device in DepoweredDevices)
{
device.ExternalPowered = true;
}
DepoweredDevices.Clear();
}
private void ChargeStorageConsumers(float frametime)
{
foreach (var storage in PowerStorageConsumerList)
{
storage.ChargePowerTick(frametime);
}
}
private void ChargeStorageSuppliers(float frametime)
{
foreach (var storage in PowerStorageSupplierList)
{
storage.ChargePowerTick(frametime);
}
}
/// <summary>
/// Kills a powernet after it is marked dirty and its component have already been regenerated by the powernet system
/// </summary>
public void DirtyKill()
{
WireList.Clear();
while (NodeList.Count != 0)
{
NodeList[0].DisconnectFromPowernet();
}
GeneratorList.Clear();
DeviceLoadList.Clear();
DepoweredDevices.Clear();
PowerStorageSupplierList.Clear();
PowerStorageConsumerList.Clear();
RemoveFromSystem();
}
/// <summary>
/// Combines two powernets when they connect via powertransfer components
/// </summary>
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);
}
Supply += toMerge.Supply;
toMerge.Supply = 0;
toMerge.GeneratorList.Clear();
foreach (var device in toMerge.DeviceLoadList)
{
DeviceLoadList.Add(device);
}
Load += toMerge.Load;
toMerge.Load = 0;
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();
}
/// <summary>
/// Removes reference from the powernets list on the powernet system
/// </summary>
private void RemoveFromSystem()
{
EntitySystem.Get<PowerSystem>().Powernets.Remove(this);
}
#region Registration
/// <summary>
/// Register a continuous load from a device connected to the powernet
/// </summary>
public void AddDevice(PowerDeviceComponent device)
{
DeviceLoadList.Add(device);
Load += device.Load;
if (!device.Powered)
DepoweredDevices.Add(device);
}
/// <summary>
/// Update one of the loads from a deviceconnected to the powernet
/// </summary>
public void UpdateDevice(PowerDeviceComponent device, float oldLoad)
{
if (DeviceLoadList.Contains(device))
{
Load -= oldLoad;
Load += device.Load;
}
}
/// <summary>
/// Returns whether or not a power device is in this powernet's load list.
/// </summary>
/// <param name="device">The device to check for.</param>
/// <returns>True if the device is in the load list, false otherwise.</returns>
public bool HasDevice(PowerDeviceComponent device)
{
return DeviceLoadList.Contains(device);
}
/// <summary>
/// Remove a continuous load from a device connected to the powernet
/// </summary>
public void RemoveDevice(PowerDeviceComponent device)
{
if (DeviceLoadList.Contains(device))
{
Load -= device.Load;
DeviceLoadList.Remove(device);
if (DepoweredDevices.Contains(device))
DepoweredDevices.Remove(device);
}
else
{
Logger.WarningS("power", "We tried to remove device {0} twice from {1}, somehow.", device.Owner, this);
}
}
/// <summary>
/// Register a power supply from a generator connected to the powernet
/// </summary>
public void AddGenerator(PowerGeneratorComponent generator)
{
GeneratorList.Add(generator, generator.Supply);
Supply += generator.Supply;
}
/// <summary>
/// Update the value supplied from a generator connected to the powernet
/// </summary>
public void UpdateGenerator(PowerGeneratorComponent generator)
{
if (GeneratorList.ContainsKey(generator))
{
Supply -= GeneratorList[generator];
GeneratorList[generator] = generator.Supply;
Supply += generator.Supply;
}
}
/// <summary>
/// Remove a power supply from a generator connected to the powernet
/// </summary>
public void RemoveGenerator(PowerGeneratorComponent generator)
{
if (GeneratorList.ContainsKey(generator))
{
Supply -= GeneratorList[generator];
GeneratorList.Remove(generator);
}
else
{
Logger.WarningS("power", "We tried to remove generator {0} twice from {1}, somehow.", generator.Owner,
this);
}
}
/// <summary>
/// Register a power supply from a generator connected to the powernet
/// </summary>
public void AddPowerStorage(PowerStorageNetComponent storage)
{
if (storage.ChargePowernet)
PowerStorageSupplierList.Add(storage);
else
PowerStorageConsumerList.Add(storage);
}
//How do I even call this? TODO: fix
public void UpdateStorageType(PowerStorageNetComponent 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);
}
/// <summary>
/// Remove a power supply from a generator connected to the powernet
/// </summary>
public void RemovePowerStorage(PowerStorageNetComponent storage)
{
if (PowerStorageSupplierList.Contains(storage))
{
PowerStorageSupplierList.Remove(storage);
}
if (PowerStorageConsumerList.Contains(storage))
{
PowerStorageSupplierList.Remove(storage);
}
}
#endregion Registration
public override string ToString()
{
return $"Powernet {Uid}";
}
/// <summary>
/// Priority that a device will receive power if powernet cannot supply every device
/// </summary>
public enum Priority
{
Necessary = 0,
High = 1,
Medium = 2,
Low = 3,
Provider = 4,
Unnecessary = 5
}
/// <summary>
/// Comparer that keeps the device dictionary sorted by powernet priority
/// </summary>
public class DevicePriorityCompare : IComparer<PowerDeviceComponent>
{
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)
{
return y.Owner.Uid.CompareTo(x.Owner.Uid);
}
return compare;
}
}
}
}

View File

@@ -1,53 +0,0 @@
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Handles the "user-facing" side of the actual SMES object.
/// This is operations that are specific to the SMES, like UI and visuals.
/// Code interfacing with the powernet is handled in <see cref="PowerStorageComponent" />.
/// </summary>
[RegisterComponent]
public class SmesComponent : Component
{
public override string Name => "Smes";
PowerStorageComponent Storage;
AppearanceComponent Appearance;
int LastChargeLevel = 0;
ChargeState LastChargeState;
public override void Initialize()
{
base.Initialize();
Storage = Owner.GetComponent<PowerStorageComponent>();
Appearance = Owner.GetComponent<AppearanceComponent>();
}
public void OnUpdate()
{
var newLevel = CalcChargeLevel();
if (newLevel != LastChargeLevel)
{
LastChargeLevel = newLevel;
Appearance.SetData(SmesVisuals.LastChargeLevel, newLevel);
}
var newState = Storage.GetChargeState();
if (newState != LastChargeState)
{
LastChargeState = newState;
Appearance.SetData(SmesVisuals.LastChargeState, newState);
}
}
int CalcChargeLevel()
{
return ContentHelpers.RoundToLevels(Storage.Charge, Storage.Capacity, 6);
}
}
}

View File

@@ -0,0 +1,58 @@
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Interactable;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Allows the attached entity to be destroyed by a cutting tool, dropping a piece of wire.
/// </summary>
[RegisterComponent]
public class WireComponent : Component, IInteractUsing
{
public override string Name => "Wire";
[ViewVariables]
private string _wireDroppedOnCutPrototype;
/// <summary>
/// Checked by <see cref="WirePlacerComponent"/> to determine if there is
/// already a wire of a type on a tile.
/// </summary>
[ViewVariables]
public WireType WireType => _wireType;
private WireType _wireType;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _wireDroppedOnCutPrototype, "wireDroppedOnCutPrototype", "HVWireStack1");
serializer.DataField(ref _wireType, "wireType", WireType.HighVoltage);
}
public bool InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent(out ToolComponent tool)) return false;
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Cutting)) return false;
Owner.Delete();
var droppedEnt = Owner.EntityManager.SpawnEntity(_wireDroppedOnCutPrototype, eventArgs.ClickLocation);
if (droppedEnt.TryGetComponent<StackComponent>(out var stackComp))
stackComp.Count = 1;
return true;
}
}
public enum WireType
{
HighVoltage,
MediumVoltage,
Apc,
}
}

View File

@@ -1,14 +1,18 @@
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
using System.Linq;
namespace Content.Server.GameObjects.Components.Power
{
@@ -23,46 +27,39 @@ namespace Content.Server.GameObjects.Components.Power
/// <inheritdoc />
public override string Name => "WirePlacer";
[ViewVariables]
private string _wirePrototypeID;
[ViewVariables]
private WireType _blockingWireType;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _wirePrototypeID, "wirePrototypeID", "HVWire");
serializer.DataField(ref _blockingWireType, "blockingWireType", WireType.HighVoltage);
}
/// <inheritdoc />
public void AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if(!_mapManager.TryGetGrid(eventArgs.ClickLocation.GridID, out var grid))
return;
var snapPos = grid.SnapGridCellFor(eventArgs.ClickLocation, SnapGridOffset.Center);
var snapCell = grid.GetSnapGridCell(snapPos, SnapGridOffset.Center);
if(grid.GetTileRef(snapPos).Tile.IsEmpty)
return;
var found = false;
foreach (var snapComp in snapCell)
{
if (!snapComp.Owner.HasComponent<PowerTransferComponent>())
continue;
found = true;
break;
}
if (found)
if (snapComp.Owner.TryGetComponent<WireComponent>(out var wire) && wire.WireType == _blockingWireType)
{
return;
bool hasItemSpriteComp = Owner.TryGetComponent(out SpriteComponent itemSpriteComp);
}
}
if (Owner.TryGetComponent(out StackComponent stack) && !stack.Use(1))
return;
GridCoordinates coordinates = grid.GridTileToLocal(snapPos);
var newWire = _entityManager.SpawnEntity("Wire", coordinates);
if (newWire.TryGetComponent(out SpriteComponent wireSpriteComp) && hasItemSpriteComp)
{
wireSpriteComp.Color = itemSpriteComp.Color;
}
//TODO: There is no way to set this wire as above or below the floor
_entityManager.SpawnEntity(_wirePrototypeID, grid.GridTileToLocal(snapPos));
}
}
}

View File

@@ -20,6 +20,7 @@ using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Timers;
using Robust.Shared.ViewVariables;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
namespace Content.Server.GameObjects.Components.Research
{
@@ -47,8 +48,8 @@ namespace Content.Server.GameObjects.Components.Research
}
private LatheRecipePrototype _producingRecipe = null;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private static readonly TimeSpan InsertionTime = TimeSpan.FromSeconds(0.9f);
@@ -57,7 +58,7 @@ namespace Content.Server.GameObjects.Components.Research
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(LatheUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_appearance = Owner.GetComponent<AppearanceComponent>();
}

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.Audio;
@@ -31,10 +32,10 @@ namespace Content.Server.GameObjects.Components.Research
private BoundUserInterface _userInterface;
private ResearchClientComponent _client;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
private const string _soundCollectionName = "keyboard";
private bool Powered => _powerDevice.Powered;
private bool Powered => _powerReceiver.Powered;
public override void Initialize()
{
@@ -42,7 +43,7 @@ namespace Content.Server.GameObjects.Components.Research
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(ResearchConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_client = Owner.GetComponent<ResearchClientComponent>();
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)

View File

@@ -1,3 +1,4 @@
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
@@ -14,7 +15,7 @@ namespace Content.Server.GameObjects.Components.Research
private int _pointsPerSecond;
private bool _active;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
[ViewVariables]
public int PointsPerSecond
@@ -33,13 +34,13 @@ namespace Content.Server.GameObjects.Components.Research
/// <summary>
/// Whether this can be used to produce research points.
/// </summary>
/// <remarks>If no <see cref="PowerDeviceComponent"/> is found, it's assumed power is not required.</remarks>
public bool CanProduce => Active && (_powerDevice is null || _powerDevice.Powered);
/// <remarks>If no <see cref="PowerReceiverComponent"/> is found, it's assumed power is not required.</remarks>
public bool CanProduce => Active && (_powerReceiver is null || _powerReceiver.Powered);
public override void Initialize()
{
base.Initialize();
Owner.TryGetComponent(out _powerDevice);
Owner.TryGetComponent(out _powerReceiver);
}
public override void ExposeData(ObjectSerializer serializer)

View File

@@ -9,6 +9,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
namespace Content.Server.GameObjects.Components.Research
{
@@ -65,11 +66,11 @@ namespace Content.Server.GameObjects.Components.Research
}
}
/// <remarks>If no <see cref="PowerDeviceComponent"/> is found, it's assumed power is not required.</remarks>
/// <remarks>If no <see cref="PowerReceiverComponent"/> is found, it's assumed power is not required.</remarks>
[ViewVariables]
public bool CanRun => _powerDevice is null || _powerDevice.Powered;
public bool CanRun => _powerReceiver is null || _powerReceiver.Powered;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
public override void Initialize()
{
@@ -77,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Research
Id = ServerCount++;
EntitySystem.Get<ResearchSystem>()?.RegisterServer(this);
Database = Owner.GetComponent<TechnologyDatabaseComponent>();
Owner.TryGetComponent(out _powerDevice);
Owner.TryGetComponent(out _powerReceiver);
}
/// <inheritdoc />

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.VendingMachines;
@@ -31,7 +31,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines
#pragma warning restore 649
private AppearanceComponent _appearance;
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private PowerReceiverComponent _powerReceiver;
private bool _ejecting = false;
private TimeSpan _animationDuration = TimeSpan.Zero;
@@ -39,7 +39,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines
private string _description;
private string _spriteName;
private bool Powered => _powerDevice.Powered;
private bool Powered => _powerReceiver.Powered;
private bool _broken = false;
public void Activate(ActivateEventArgs eventArgs)
@@ -103,16 +103,17 @@ namespace Content.Server.GameObjects.Components.VendingMachines
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(VendingMachineUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_powerDevice.OnPowerStateChanged += UpdatePower;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerReceiver.OnPowerStateChanged += UpdatePower;
TrySetVisualState(_powerReceiver.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off);
InitializeFromPrototype();
}
public override void OnRemove()
{
_appearance = null;
_powerDevice.OnPowerStateChanged -= UpdatePower;
_powerDevice = null;
_powerReceiver.OnPowerStateChanged -= UpdatePower;
_powerReceiver = null;
base.OnRemove();
}

View File

@@ -57,13 +57,13 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
public bool Activated => _activated;
[ViewVariables]
private PowerCellComponent Cell
private BatteryComponent Cell
{
get
{
if (_cellContainer.ContainedEntity == null) return null;
_cellContainer.ContainedEntity.TryGetComponent(out PowerCellComponent cell);
_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent cell);
return cell;
}
}
@@ -88,9 +88,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
public override bool OnHitEntities(IReadOnlyList<IEntity> entities)
{
var cell = Cell;
if (!Activated || entities.Count == 0 || cell == null || !cell.CanDeductCharge(EnergyPerUse))
if (!Activated || entities.Count == 0 || cell == null)
return false;
if (!cell.TryUseCharge(EnergyPerUse))
{
return false;
}
EntitySystem.Get<AudioSystem>().PlayAtCoords("/Audio/weapons/egloves.ogg", Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f));
foreach (var entity in entities)
@@ -108,9 +111,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
else
stunnable.Slowdown(_slowdownTime);
}
cell.DeductCharge(EnergyPerUse);
if(cell.Charge < EnergyPerUse)
if(cell.CurrentCharge < EnergyPerUse)
{
EntitySystem.Get<AudioSystem>().PlayAtCoords(AudioHelpers.GetRandomFileFromSoundCollection("sparks"), Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f));
TurnOff();
@@ -169,7 +170,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
return;
}
if (cell.Charge < EnergyPerUse)
if (cell.CurrentCharge < EnergyPerUse)
{
EntitySystem.Get<AudioSystem>().PlayAtCoords("/Audio/machines/button.ogg", Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f));
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell..."));
@@ -192,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
public bool InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.HasComponent<PowerCellComponent>()) return false;
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
if (Cell != null) return false;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Projectiles;
@@ -33,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
[ViewVariables] private string _ammoPrototype;
[ViewVariables] public IEntity PowerCellEntity => _powerCellContainer.ContainedEntity;
public PowerCellComponent PowerCell => _powerCellContainer.ContainedEntity.GetComponent<PowerCellComponent>();
public BatteryComponent PowerCell => _powerCellContainer.ContainedEntity.GetComponent<BatteryComponent>();
private ContainerSlot _powerCellContainer;
private ContainerSlot _ammoContainer;
private string _powerCellPrototype;
@@ -50,7 +50,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
return 0;
}
return (int) Math.Ceiling(powerCell.GetComponent<PowerCellComponent>().Charge / _baseFireCost);
return (int) Math.Ceiling(powerCell.GetComponent<BatteryComponent>().CurrentCharge / _baseFireCost);
}
}
@@ -65,7 +65,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
return 0;
}
return (int) Math.Ceiling(powerCell.GetComponent<PowerCellComponent>().Capacity / _baseFireCost);
return (int) Math.Ceiling((float) (powerCell.GetComponent<BatteryComponent>().MaxCharge / _baseFireCost));
}
}
@@ -144,8 +144,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
return null;
}
var capacitor = powerCellEntity.GetComponent<PowerCellComponent>();
if (capacitor.Charge < _lowerChargeLimit)
var capacitor = powerCellEntity.GetComponent<BatteryComponent>();
if (capacitor.CurrentCharge < _lowerChargeLimit)
{
return null;
}
@@ -153,8 +153,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
// Can fire confirmed
// Multiply the entity's damage / whatever by the percentage of charge the shot has.
IEntity entity;
var chargeChange = Math.Min(capacitor.Charge, _baseFireCost);
capacitor.DeductCharge(chargeChange);
var chargeChange = Math.Min(capacitor.CurrentCharge, _baseFireCost);
capacitor.UseCharge(chargeChange);
var energyRatio = chargeChange / _baseFireCost;
if (_ammoContainer.ContainedEntity != null)
@@ -201,7 +201,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
return false;
}
if (!entity.HasComponent<PowerCellComponent>())
if (!entity.HasComponent<BatteryComponent>())
{
return false;
}
@@ -264,7 +264,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
public override bool InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.HasComponent<PowerStorageComponent>())
if (!eventArgs.Using.HasComponent<BatteryComponent>())
{
return false;
}

View File

@@ -1,4 +1,4 @@
using Content.Server.GameObjects.Components.Power.Chargers;
using Content.Server.GameObjects.Components.Power.Chargers;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
@@ -6,19 +6,18 @@ using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
internal class CellChargerSystem : EntitySystem
internal class BaseChargerSystem : EntitySystem
{
public override void Initialize()
{
EntityQuery = new TypeEntityQuery(typeof(PowerCellChargerComponent));
EntityQuery = new TypeEntityQuery(typeof(BaseCharger));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
var comp = entity.GetComponent<PowerCellChargerComponent>();
comp.OnUpdate(frameTime);
entity.GetComponent<BaseCharger>().OnUpdate(frameTime);
}
}
}

View File

@@ -0,0 +1,32 @@
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.EntitySystems
{
internal class BatteryDischargerSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IPauseManager _pauseManager;
#pragma warning restore 649
public override void Initialize()
{
EntityQuery = new TypeEntityQuery(typeof(BatteryDischargerComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
if (_pauseManager.IsEntityPaused(entity))
{
continue;
}
entity.GetComponent<BatteryDischargerComponent>().Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,32 @@
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.EntitySystems
{
internal class BatteryStorageSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IPauseManager _pauseManager;
#pragma warning restore 649
public override void Initialize()
{
EntityQuery = new TypeEntityQuery(typeof(BatteryStorageComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
if (_pauseManager.IsEntityPaused(entity))
{
continue;
}
entity.GetComponent<BatteryStorageComponent>().Update(frameTime);
}
}
}
}

View File

@@ -1,11 +1,19 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Server.Interfaces.Timing;
namespace Content.Server.GameObjects.EntitySystems
{
class PowerApcSystem : EntitySystem
public sealed class ApcSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IPauseManager _pauseManager;
#pragma warning restore 649
public override void Initialize()
{
EntityQuery = new TypeEntityQuery(typeof(ApcComponent));
@@ -13,10 +21,20 @@ namespace Content.Server.GameObjects.EntitySystems
public override void Update(float frameTime)
{
var uniqueApcNets = new HashSet<IApcNet>(); //could be improved by maintaining set instead of getting collection every frame
foreach (var entity in RelevantEntities)
{
var comp = entity.GetComponent<ApcComponent>();
comp.OnUpdate();
if (_pauseManager.IsEntityPaused(entity))
{
continue;
}
var apc = entity.GetComponent<ApcComponent>();
uniqueApcNets.Add(apc.Net);
entity.GetComponent<ApcComponent>().Update();
}
foreach (var apcNet in uniqueApcNets)
{
apcNet.Update(frameTime);
}
}
}

View File

@@ -15,8 +15,7 @@ namespace Content.Server.GameObjects.EntitySystems
{
foreach (var entity in RelevantEntities)
{
var comp = entity.GetComponent<SmesComponent>();
comp.OnUpdate();
entity.GetComponent<SmesComponent>().OnUpdate();
}
}
}

View File

@@ -1,73 +0,0 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Power;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Shared.GameObjects.EntitySystems
{
public class PowerSystem : EntitySystem
{
public List<Powernet> Powernets = new List<Powernet>();
private IComponentManager componentManager;
private int _lastUid = 0;
public PowerSystem()
{
EntityQuery = new TypeEntityQuery(typeof(PowerDeviceComponent));
}
public override void Initialize()
{
base.Initialize();
componentManager = IoCManager.Resolve<IComponentManager>();
}
public int NewUid()
{
return ++_lastUid;
}
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);
}
// Draw power for devices not connected to anything.
foreach (var entity in EntityManager.GetEntities(EntityQuery))
{
var device = entity.GetComponent<PowerDeviceComponent>();
device.ProcessInternalPower(frametime);
}
}
}
}

View File

@@ -1,26 +0,0 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power.Chargers;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
internal class WeaponCapacitorChargerSystem : EntitySystem
{
public override void Initialize()
{
EntityQuery = new TypeEntityQuery(typeof(WeaponCapacitorChargerComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
var comp = entity.GetComponent<WeaponCapacitorChargerComponent>();
comp.OnUpdate(frameTime);
}
}
}
}

View File

@@ -13,6 +13,8 @@ using Content.Server.Utility;
using Content.Shared.Interfaces;
using Content.Shared.Kitchen;
using Robust.Shared.IoC;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
namespace Content.Server
{
@@ -31,6 +33,8 @@ namespace Content.Server
IoCManager.Register<IServerPreferencesManager, ServerPreferencesManager>();
IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<IPDAUplinkManager,PDAUplinkManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<INodeFactory, NodeFactory>();
IoCManager.Register<BlackboardManager, BlackboardManager>();
}
}

View File

@@ -1,15 +1,10 @@
using System;
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Power
{
public abstract class SharedApcComponent : Component
{
public sealed override string Name => "Apc";
}
[Serializable, NetSerializable]
public enum ApcVisuals
{

View File

@@ -1,24 +0,0 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Power
{
public class SharedPowerDebugTool : Component
{
public override string Name => "PowerDebugTool";
public override uint? NetID => ContentNetIDs.POWER_DEBUG_TOOL;
[Serializable, NetSerializable]
protected class OpenDataWindowMsg : ComponentMessage
{
public string Data { get; }
public OpenDataWindowMsg(string data)
{
Directed = true;
Data = data;
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,11 @@
- type: entity
- type: entity
id: Arcade
name: arcade
parent: ComputerBase
components:
- type: Icon
state: arcade
- type: PowerDevice
priority: Low
- type: PowerReceiver
- type: Sprite
layers:
- state: arcade

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
id: Airlock
name: airlock
description: It opens, it closes, and maybe crushes you.
@@ -40,7 +40,7 @@
close_sound: /Audio/machines/airlock_close.ogg
deny_sound: /Audio/machines/airlock_deny.ogg
- type: WiresVisualizer2D
- type: PowerDevice
- type: PowerReceiver
- type: Wires
BoardName: "Airlock Control"
LayoutId: Airlock

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
id: BarSign
name: bar sign
components:
@@ -12,7 +12,7 @@
- type: Icon
sprite: Buildings/barsign.rsi
state: empty
- type: PowerDevice
- type: PowerReceiver
- type: BarSign

View File

@@ -10,7 +10,7 @@
texture: Buildings/chemicals.rsi/industrial_dispenser.png
- type: ReagentDispenser
pack: ChemDispenserStandardInventory
- type: PowerDevice
- type: PowerReceiver
- type: reagentDispenserInventory
id: ChemDispenserStandardInventory

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
id: ComputerBase
name: computer
abstract: true
@@ -26,8 +26,7 @@
sprite: Buildings/computer.rsi
state: computer
- type: Computer
- type: PowerDevice
priority: High
- type: PowerReceiver
- type: Anchorable
- type: Sprite
@@ -151,8 +150,7 @@
type: ResearchConsoleBoundUserInterface
- key: enum.ResearchClientUiKey.Key
type: ResearchClientBoundUserInterface
- type: PowerDevice
drawtype: Both
- type: PowerReceiver
load: 200
priority: Low

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
id: GravityGenerator
name: gravity generator
description: It's what keeps you to the floor.
@@ -15,8 +15,8 @@
state: on
- type: SnapGrid
offset: Center
- type: PowerDevice
load: 500
- type: PowerReceiver
powerLoad: 500
- type: Collidable
shapes:
- !type:PhysShapeAabb

View File

@@ -27,7 +27,7 @@
interfaces:
- key: enum.LatheUiKey.Key
type: LatheBoundUserInterface
- type: PowerDevice
- type: PowerReceiver
- type: entity
parent: BaseLathe

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
id: WallLight
name: "unpowered light"
components:
@@ -42,10 +42,9 @@
state: off
- type: PointLight
enabled: false
- type: PowerDevice
priority: Low
- type: PoweredLight
bulb: Tube
- type: PowerReceiver
- type: entity
name: small light
@@ -64,8 +63,6 @@
energy: 1.0
enabled: false
offset: "-0.5, 0"
- type: PowerDevice
priority: Low
- type: PoweredLight
bulb: Bulb
- type: PowerReceiver

View File

@@ -11,7 +11,7 @@
map: ["enum.MedicalScannerVisualLayers.Machine"]
- state: scanner_terminal_blue
map: ["enum.MedicalScannerVisualLayers.Terminal"]
- type: PowerDevice
- type: PowerReceiver
- type: Icon
sprite: Buildings/medical_scanner.rsi
state: scanner_open

View File

@@ -1,49 +1,88 @@
- type: entity
id: Wire
name: wire
description: Transfers power, avoid letting things come down it
abstract: true
id: WireBase
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
- type: Sprite
netsync: false
drawdepth: BelowFloor
color: Red
sprite: Objects/Power/power_cable.rsi
state: cable_0
- type: Icon
texture: Objects/Power/eightdirwire.png
- type: PowerTransfer
- type: SnapGrid
offset: Center
- type: Icon
texture: Objects/Power/eightdirwire.png
- type: Sprite
drawdepth: BelowFloor
- type: IconSmooth
base: cable_
key: power_cables
mode: CardinalFlags
- type: SubFloorHide
- type: Destructible
thresholdvalue: 100
spawnondestroy: CableStack1
snap:
- Wire
- type: SubFloorHide
- type: entity
parent: Wire
id: BlueWire
name: bluewire
description: Transfers power, and puts on a good show of it
parent: WireBase
id: HVWire
name: HV Wire
components:
- type: Sprite
color: Blue
sprite: Objects/Power/hv_cable.rsi
state: hvcable_0
- type: IconSmooth
base: hvcable_
key: hv_cables
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: Wire
wireDroppedOnCutPrototype: HVWireStack1
wireType: HighVoltage
- type: Destructible
spawnondestroy: HVWireStack1
- type: entity
id: Generator
name: generator
description: A portal to hell which summons power from the nether
parent: WireBase
id: MVWire
name: MV Wire
components:
- type: Sprite
color: Yellow
sprite: Objects/Power/mv_cable.rsi
state: mvcable_0
- type: IconSmooth
base: mvcable_
key: mv_cables
- type: NodeContainer
nodeTypes: { MVPower : ["AdjacentNode"] }
- type: Wire
wireDroppedOnCutPrototype: MVWireStack1
wireType: MediumVoltage
- type: Destructible
spawnondestroy: MVWireStack1
- type: entity
parent: WireBase
id: ApcExtensionCable
name: Apc Extension Cable
components:
- type: Sprite
color: Green
sprite: Objects/Power/lv_cable.rsi
state: lvcable_0
- type: IconSmooth
base: lvcable_
key: lv_cables
- type: NodeContainer
nodeTypes: { Apc : ["AdjacentNode"] }
- type: PowerProvider
voltage: Apc
- type: Wire
wireDroppedOnCutPrototype: ApcExtensionCableStack1
wireType: Apc
- type: Destructible
spawnondestroy: ApcExtensionCableStack1
- type: entity
id: DebugGenerator
name: Debug Generator
placement:
mode: SnapgridCenter
components:
@@ -54,15 +93,215 @@
- !type:PhysShapeAabb
bounds: "-0.5, -0.5, 0.3, 0.5"
layer: [MobMask, Opaque]
- type: SnapGrid
offset: Center
- type: Sprite
texture: Objects/Power/generator.png
- type: Icon
texture: Objects/Power/generator.png
- type: PowerGenerator
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: PowerSupplier
supplyRate: 3000
- type: Anchorable
- type: entity
id: DebugConsumer
name: Debug Consumer
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
- type: SnapGrid
offset: Center
- type: Sprite
texture: Objects/Power/wiredmachine.png
- type: Icon
texture: Objects/Power/wiredmachine.png
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: PowerConsumer
drawRate: 50
- type: Damageable
- type: Breakable
thresholdvalue: 100
- type: Anchorable
- type: entity
id: DebugBatteryStorage
name: Debug Battery Storage
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
- type: SnapGrid
offset: Center
- type: Sprite
texture: Objects/Power/provider.png
- type: Icon
texture: Objects/Power/provider.png
- type: Battery
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: PowerConsumer
- type: BatteryStorage
- type: entity
id: DebugBatteryDischarger
name: Debug Battery Discharger
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
- type: SnapGrid
offset: Center
- type: Sprite
texture: Objects/Power/provider.png
- type: Icon
texture: Objects/Power/provider.png
- type: Battery
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: PowerSupplier
- type: BatteryDischarger
- type: entity
id: DebugSmes
name: Debug Smes
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-0.5, -0.5, 0.5, 0.5"
layer: [MobMask, Opaque]
- type: SnapGrid
offset: Center
- type: Sprite
netsync: false
sprite: Buildings/smes.rsi
state: smes
layers:
- state: smes-display
shader: unshaded
- type: Icon
sprite: Buildings/smes.rsi
state: smes
- type: Smes
- type: Appearance
visuals:
- type: SmesVisualizer2D
- type: Battery
maxCharge: 1000
startingCharge: 1000
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: PowerConsumer
- type: BatteryStorage
activeDrawRate: 1500
- type: PowerSupplier
- type: BatteryDischarger
activeSupplyRate: 1000
- type: Anchorable
- type: entity
id: DebugSubstation
name: Debug Substation
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
- type: SnapGrid
offset: Center
- type: Sprite
texture: Objects\Power\storage.png
- type: Icon
texture: Objects\Power\storage.png
state: smes
- type: Battery
maxCharge: 1000
startingCharge: 1000
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"], MVPower : ["AdjacentNode"] }
- type: PowerConsumer
- type: BatteryStorage
activeDrawRate: 1500
- type: PowerSupplier
voltage: Medium
- type: BatteryDischarger
activeSupplyRate: 1000
- type: entity
id: DebugApc
name: Debug Apc
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-0.25, -0.25, 0.25, 0.3"
- type: SnapGrid
offset: Center
- type: Sprite
drawdepth: WallMountedItems
netsync: false
texture: ""
sprite: "Buildings/apc.rsi"
state: apc0
- type: Icon
texture: Buildings/apc.rsi/apc0.png
- type: Appearance
visuals:
- type: ApcVisualizer2D
- type: Battery
maxCharge: 10000
startingCharge: 10000
- type: NodeContainer
nodeTypes: { MVPower : ["AdjacentNode"], Apc : ["AdjacentNode"] }
- type: PowerConsumer
voltage: Medium
- type: BatteryStorage
activeDrawRate: 1000
- type: PowerProvider
voltage: Apc
- type: Apc
voltage: Apc
- type: UserInterface
interfaces:
- key: enum.ApcUiKey.Key
type: ApcBoundUserInterface
- type: entity
id: DebugPowerReceiver
name: Debug Power Receiver
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
- type: SnapGrid
offset: Center
- type: Sprite
texture: Objects/Furniture/wirelessmachine.png
- type: Icon
texture: Objects/Furniture/wirelessmachine.png
- type: PowerReceiver
- type: entity
id: SolarPanel
name: solar panel
@@ -82,7 +321,9 @@
- type: Icon
sprite: Buildings/solar_panel.rsi
state: normal
- type: PowerGenerator
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"] }
- type: PowerSupplier
- type: SolarPanel
supply: 1500
- type: SnapGrid
@@ -90,160 +331,54 @@
- type: Damageable
- type: Breakable
thresholdvalue: 100
- type: Anchorable
#Depriciated, to be removed from maps
- type: entity
id: WPPnobattery
name: wppnobattery
description: Supplies power directly to nearby objects
placement:
mode: SnapgridCenter
id: Wire
name: Depriciated Wire
parent: ApcExtensionCable
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
layer: [Clickable]
- type: Sprite
drawdepth: WallMountedItems
texture: Objects/Power/provider.png
- type: Icon
texture: Objects/Power/provider.png
- type: PowerProvider
range: 8
priority: Provider
load: 0
- type: SnapGrid
offset: Center
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"], Apc : ["AdjacentNode"] }
- type: entity
parent: WPPnobattery
id: WPP
name: WPP
description: Supplies power at range, has a backup battery just in case
id: Generator
name: Depriciated Generator
parent: DebugGenerator
components:
- type: PowerStorage
capacity: 1000
charge: 1000
chargerate: 200
chargepowernet: false
- type: PowerSupplier
voltage: High
supplyRate: 100000
- type: entity
parent: WPP
id: APC
name: APC
name: Depriciated Apc
parent: DebugApc
components:
- type: Apc
- type: Sprite
netsync: false
texture: ""
sprite: "Buildings/apc.rsi"
state: apc0
- type: Appearance
visuals:
- type: ApcVisualizer2D
- type: UserInterface
interfaces:
- key: enum.ApcUiKey.Key
type: ApcBoundUserInterface
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-0.25, -0.25, 0.25, 0.3"
- type: LoopingSound
- type: NodeContainer
nodeTypes: { HVPower : ["AdjacentNode"], Apc : ["AdjacentNode"] }
- type: PowerConsumer
voltage: High
- type: BatteryStorage
activeDrawRate: 10000
- type: entity
id: SMES
name: smes
description: Stores power in its super-magnetic cells
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-0.5, -0.5, 0.5, 0.5"
layer: [MobMask, Opaque]
- type: Sprite
netsync: false
sprite: Buildings/smes.rsi
state: smes
layers:
- state: smes-display
shader: unshaded
- type: Icon
sprite: Buildings/smes.rsi
state: smes
- type: PowerStorage
capacity: 3000
charge: 1000
chargerate: 200
distributionrate: 400
chargepowernet: true
- type: Smes
- type: Appearance
visuals:
- type: SmesVisualizer2D
- type: SnapGrid
offset: Center
- type: Anchorable
name: Depriciated Smes
parent: DebugSmes
- type: entity
id: SmesDry
parent: SMES
components:
- type: PowerStorage
charge: 0
name: Depriciated Smes
parent: DebugSmes
- type: entity
id: WiredMachine
name: wiredmachine
description: A monstrosity that does nothing but suck up power from the nearby wires
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-0.5, -0.25, 0.5, 0.25"
layer: [MobMask, Opaque]
- type: Sprite
texture: Objects/Power/wiredmachine.png
- type: Icon
texture: Objects/Power/wiredmachine.png
- type: PowerDevice
drawtype: Node
load: 100
priority: High
- type: SnapGrid
offset: Center
name: Depriciated WiredMachine
parent: DebugConsumer
- type: entity
id: WirelessMachine
name: wirelessmachine
description: A terrifying monstrosity that sucks up power from the wireless transmitters, Tesla would be proud
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Collidable
shapes:
- !type:PhysShapeAabb
bounds: "-0.5, -0.25, 0.5, 0.25"
layer: [MobMask, Opaque]
- type: Sprite
texture: Objects/Furniture/wirelessmachine.png
- type: Icon
texture: Objects/Furniture/wirelessmachine.png
- type: PowerDevice
drawtype: Both
load: 200
priority: Low
- type: SnapGrid
offset: Center
name: Depriciated WirelessMachine
parent: DebugPowerReceiver

View File

@@ -21,7 +21,7 @@
- type: SnapGrid
offset: Center
- type: ReagentDispenser
- type: PowerDevice
- type: PowerReceiver
- type: UserInterface
interfaces:
- key: enum.ReagentDispenserUiKey.Key

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
id: VendingMachine
name: vending machine
components:
@@ -45,8 +45,7 @@
type: VendingMachineBoundUserInterface
- key: enum.WiresUiKey.Key
type: WiresBoundUserInterface
- type: PowerDevice
priority: Low
- type: PowerReceiver
- type: Wires
BoardName: "Vending Machine"
LayoutId: Vending

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
name: material stack
id: MaterialStack
abstract: true
@@ -67,15 +67,14 @@
count: 1
- type: entity
name: cable coil
id: CableStack
abstract: true
parent: BaseItem
components:
- type: Stack
stacktype: enum.StackType.Cable
- type: Sprite
texture: Objects/Tools/cable_coil.png
color: red
- type: Icon
texture: Objects/Tools/cable_coil.png
- type: WirePlacer
@@ -84,10 +83,59 @@
all: -0.15,-0.15,0.15,0.15
- type: entity
id: CableStack1
name: cable stack 1
id: HVWireStack
name: HV Wire Coil
parent: CableStack
components:
- type: Sprite
color: Orange
- type: WirePlacer
wirePrototypeID: HVWire
blockingWireType: HighVoltage
- type: entity
id: MVWireStack
name: MV Wire Coil
parent: CableStack
components:
- type: Sprite
color: Yellow
- type: WirePlacer
wirePrototypeID: MVWire
blockingWireType: MediumVoltage
- type: entity
id: ApcExtensionCableStack
name: Apc Extension Cable Coil
parent: CableStack
components:
- type: Sprite
color: Green
- type: WirePlacer
wirePrototypeID: ApcExtensionCable
blockingWireType: Apc
- type: entity
id: HVWireStack1
name: HV Wire stack 1
parent: HVWireStack
components:
- type: Stack
count: 1
- type: entity
id: MVWireStack1
name: MV Wire stack 1
parent: MVWireStack
components:
- type: Stack
count: 1
- type: entity
id: ApcExtensionCableStack1
name: Apc Extension Cable stack 1
parent: ApcExtensionCableStack
components:
- type: Stack
count: 1

View File

@@ -1,18 +0,0 @@
- type: entity
name: power debug tool
parent: BaseItem
id: PowerDebug
description: An advanced tool to copy, store, and send electrical pulses and signals through wires and machines
components:
- type: Sprite
sprite: Objects/Tools/multitool.rsi
state: multitool
- type: Icon
sprite: Objects/Tools/multitool.rsi
state: multitool
- type: Item
sprite: Objects/Tools/multitool.rsi
- type: PowerDebugTool

Some files were not shown because too many files have changed in this diff Show More