diff --git a/Content.Client/Power/Generator/GeneratorWindow.xaml b/Content.Client/Power/Generator/GeneratorWindow.xaml
new file mode 100644
index 0000000000..86da3835e2
--- /dev/null
+++ b/Content.Client/Power/Generator/GeneratorWindow.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Power/Generator/GeneratorWindow.xaml.cs b/Content.Client/Power/Generator/GeneratorWindow.xaml.cs
new file mode 100644
index 0000000000..029ccbd6ab
--- /dev/null
+++ b/Content.Client/Power/Generator/GeneratorWindow.xaml.cs
@@ -0,0 +1,57 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Power.Generator;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Power.Generator;
+
+[GenerateTypedNameReferences]
+public sealed partial class GeneratorWindow : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+
+ private readonly FuelGeneratorComponent? _component;
+ private SolidFuelGeneratorComponentBuiState? _lastState;
+
+ public GeneratorWindow(SolidFuelGeneratorBoundUserInterface bui, EntityUid vis)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _entityManager.TryGetComponent(vis, out _component);
+
+ EntityView.SetEntity(vis);
+ TargetPower.IsValid += IsValid;
+ TargetPower.ValueChanged += (args) =>
+ {
+ bui.SetTargetPower(args.Value);
+ };
+ }
+
+ private bool IsValid(int arg)
+ {
+ if (arg < 0)
+ return false;
+
+ if (arg > (_lastState?.MaximumPower / 1000.0f ?? 0))
+ return false;
+
+ return true;
+ }
+
+ public void Update(SolidFuelGeneratorComponentBuiState state)
+ {
+ if (_component == null)
+ return;
+
+ var oldState = _lastState;
+ _lastState = state;
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ if (oldState?.TargetPower != state.TargetPower)
+ TargetPower.OverrideValue((int)(state.TargetPower / 1000.0f));
+ Efficiency.Text = SharedGeneratorSystem.CalcFuelEfficiency(state.TargetPower, state.OptimalPower, _component).ToString("P1");
+ FuelFraction.Value = state.RemainingFuel - (int) state.RemainingFuel;
+ FuelLeft.Text = ((int) MathF.Floor(state.RemainingFuel)).ToString();
+
+ }
+}
diff --git a/Content.Client/Power/Generator/SolidFuelGeneratorBoundUserInterface.cs b/Content.Client/Power/Generator/SolidFuelGeneratorBoundUserInterface.cs
new file mode 100644
index 0000000000..f43072f222
--- /dev/null
+++ b/Content.Client/Power/Generator/SolidFuelGeneratorBoundUserInterface.cs
@@ -0,0 +1,42 @@
+using Content.Shared.Power.Generator;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Power.Generator;
+
+[UsedImplicitly]
+public sealed class SolidFuelGeneratorBoundUserInterface : BoundUserInterface
+{
+ private GeneratorWindow? _window;
+
+ public SolidFuelGeneratorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window = new GeneratorWindow(this, Owner);
+
+ _window.OpenCenteredLeft();
+ _window.OnClose += Close;
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is not SolidFuelGeneratorComponentBuiState msg)
+ return;
+
+ _window?.Update(msg);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _window?.Dispose();
+ }
+
+ public void SetTargetPower(int target)
+ {
+ SendMessage(new SetTargetPowerMessage(target));
+ }
+}
diff --git a/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs b/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs
index 25cf38155f..e9262f481e 100644
--- a/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs
+++ b/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs
@@ -27,4 +27,10 @@ public sealed class UpgradePowerSupplierComponent : Component
///
[DataField("scaling", required: true), ViewVariables(VVAccess.ReadWrite)]
public MachineUpgradeScalingType Scaling;
+
+ ///
+ /// The current value that the power supply is being scaled by,
+ ///
+ [DataField("actualScalar"), ViewVariables(VVAccess.ReadWrite)]
+ public float ActualScalar = 1f;
}
diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs
index 3a4da764ce..07e1a62eed 100644
--- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs
+++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs
@@ -76,8 +76,12 @@ internal sealed class PowerMonitoringConsoleSystem : EntitySystem
}
foreach (PowerSupplierComponent pcc in netQ.Suppliers)
{
- sources.Add(LoadOrSource(pcc, pcc.MaxSupply, false));
- totalSources += pcc.MaxSupply;
+ var supply = pcc.Enabled
+ ? pcc.MaxSupply
+ : 0f;
+
+ sources.Add(LoadOrSource(pcc, supply, false));
+ totalSources += supply;
}
foreach (BatteryDischargerComponent pcc in netQ.Dischargers)
{
diff --git a/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs
index 0dd696b251..0d43f60a2b 100644
--- a/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs
+++ b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs
@@ -43,7 +43,7 @@ public sealed class UpgradePowerSystem : EntitySystem
load *= MathF.Pow(component.PowerDrawMultiplier, rating - 1);
break;
default:
- Logger.Error($"invalid power scaling type for {ToPrettyString(uid)}.");
+ Log.Error($"invalid power scaling type for {ToPrettyString(uid)}.");
load = 0;
break;
}
@@ -82,19 +82,19 @@ public sealed class UpgradePowerSystem : EntitySystem
supply *= MathF.Pow(component.PowerSupplyMultiplier, rating - 1);
break;
default:
- Logger.Error($"invalid power scaling type for {ToPrettyString(uid)}.");
+ Log.Error($"invalid power scaling type for {ToPrettyString(uid)}.");
supply = component.BaseSupplyRate;
break;
}
+ component.ActualScalar = supply / component.BaseSupplyRate;
+
if (TryComp(uid, out var powa))
powa.MaxSupply = supply;
}
private void OnSupplierUpgradeExamine(EntityUid uid, UpgradePowerSupplierComponent component, UpgradeExamineEvent args)
{
- // UpgradePowerSupplierComponent.PowerSupplyMultiplier is not the actual multiplier, so we have to do this.
- if (TryComp(uid, out var powa))
- args.AddPercentageUpgrade("upgrade-power-supply", powa.MaxSupply / component.BaseSupplyRate);
+ args.AddPercentageUpgrade("upgrade-power-supply", component.ActualScalar);
}
}
diff --git a/Content.Server/Power/Generator/ChemicalFuelGeneratorAdapterComponent.cs b/Content.Server/Power/Generator/ChemicalFuelGeneratorAdapterComponent.cs
new file mode 100644
index 0000000000..a16bdbbca1
--- /dev/null
+++ b/Content.Server/Power/Generator/ChemicalFuelGeneratorAdapterComponent.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Whitelist;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
+
+namespace Content.Server.Power.Generator;
+
+///
+/// This is used for chemical fuel input into generators.
+///
+[RegisterComponent, Access(typeof(GeneratorSystem))]
+public sealed class ChemicalFuelGeneratorAdapterComponent : Component
+{
+ ///
+ /// The acceptable list of input entities.
+ ///
+ [DataField("whitelist")]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// The conversion factor for different chems you can put in.
+ ///
+ [DataField("chemConversionFactors", required: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer))]
+ public Dictionary ChemConversionFactors = default!;
+}
diff --git a/Content.Server/Power/Generator/ChemicalFuelGeneratorDirectSourceComponent.cs b/Content.Server/Power/Generator/ChemicalFuelGeneratorDirectSourceComponent.cs
new file mode 100644
index 0000000000..3904a3bdf7
--- /dev/null
+++ b/Content.Server/Power/Generator/ChemicalFuelGeneratorDirectSourceComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Server.Power.Generator;
+
+///
+/// This is used for stuff that can directly be shoved into a generator.
+///
+[RegisterComponent, Access(typeof(GeneratorSystem))]
+public sealed class ChemicalFuelGeneratorDirectSourceComponent : Component
+{
+ ///
+ /// The solution to pull fuel material from.
+ ///
+ [DataField("solution", required: true), ViewVariables(VVAccess.ReadWrite)]
+ public string Solution = default!;
+}
diff --git a/Content.Server/Power/Generator/GasPowerReceiverComponent.cs b/Content.Server/Power/Generator/GasPowerReceiverComponent.cs
new file mode 100644
index 0000000000..2eede9f243
--- /dev/null
+++ b/Content.Server/Power/Generator/GasPowerReceiverComponent.cs
@@ -0,0 +1,47 @@
+using Content.Shared.Atmos;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Power.Generator;
+
+///
+/// This is used for providing gas power to machinery.
+///
+[RegisterComponent, Access(typeof(GasPowerReceiverSystem))]
+public sealed class GasPowerReceiverComponent : Component
+{
+ ///
+ /// Past this temperature we assume we're in reaction mass mode and not magic mode.
+ ///
+ [DataField("maxTemperature"), ViewVariables(VVAccess.ReadWrite)]
+ public float MaxTemperature = 1000.0f;
+
+ ///
+ /// The gas that fuels this generator
+ ///
+ [DataField("targetGas", required: true), ViewVariables(VVAccess.ReadWrite)]
+ public Gas TargetGas;
+
+ ///
+ /// The amount of gas consumed for operation in magic mode.
+ ///
+ [DataField("molesConsumedSec"), ViewVariables(VVAccess.ReadWrite)]
+ public float MolesConsumedSec = 1.55975875833f / 4;
+
+ ///
+ /// The amount of kPA "consumed" for operation in pressure mode.
+ ///
+ [DataField("pressureConsumedSec"), ViewVariables(VVAccess.ReadWrite)]
+ public float PressureConsumedSec = 100f;
+
+ ///
+ /// Whether the consumed gas should then be ejected directly into the atmosphere.
+ ///
+ [DataField("offVentGas"), ViewVariables(VVAccess.ReadWrite)]
+ public bool OffVentGas;
+
+ [DataField("lastProcess", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan LastProcess = TimeSpan.Zero;
+
+ [DataField("powered"), ViewVariables(VVAccess.ReadWrite)]
+ public bool Powered = true;
+}
diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs
new file mode 100644
index 0000000000..a8c19fe71b
--- /dev/null
+++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs
@@ -0,0 +1,92 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Atmos.Piping.Components;
+using Content.Server.NodeContainer;
+using Content.Server.NodeContainer.EntitySystems;
+using Content.Server.NodeContainer.Nodes;
+using Content.Server.Power.Components;
+using Content.Shared.Atmos;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Power.Generator;
+
+///
+/// This handles gas power receivers, allowing devices to accept power in the form of a gas.
+///
+public sealed class GasPowerReceiverSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnDeviceUpdated);
+ }
+
+ private void OnMapInit(EntityUid uid, GasPowerReceiverComponent component, MapInitEvent args)
+ {
+ component.LastProcess = _gameTiming.CurTime;
+ }
+
+ private void OnDeviceUpdated(EntityUid uid, GasPowerReceiverComponent component, AtmosDeviceUpdateEvent args)
+ {
+ var timeDelta = (float)(_gameTiming.CurTime - component.LastProcess).TotalSeconds;
+ component.LastProcess = _gameTiming.CurTime;
+
+ if (!HasComp(uid)
+ || !TryComp(uid, out var nodeContainer)
+ || !_nodeContainer.TryGetNode(nodeContainer, "pipe", out var pipe))
+ {
+ return;
+ }
+
+ // if we're below the max temperature, then we are simply consuming our target gas
+ if (pipe.Air.Temperature <= component.MaxTemperature)
+ {
+ // we have enough gas, so we consume it and are powered
+ if (pipe.Air.Moles[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta)
+ {
+ pipe.Air.AdjustMoles(component.TargetGas, -component.MolesConsumedSec * timeDelta);
+ SetPowered(uid, component, true);
+ }
+ else // we do not have enough gas, so we power off
+ {
+ SetPowered(uid, component, false);
+ }
+ }
+ else // we are exceeding the max temp and are now operating in pressure mode
+ {
+ var pres = component.PressureConsumedSec * timeDelta;
+ if (pipe.Air.Pressure >= pres)
+ {
+ // remove gas from the pipe
+ var res = pipe.Air.Remove(pres * 100.0f / (Atmospherics.R * pipe.Air.Temperature));
+ if (component.OffVentGas)
+ {
+ // eject the gas into the atmosphere
+ var mix = _atmosphereSystem.GetContainingMixture(uid, false, true);
+ if (mix is not null)
+ _atmosphereSystem.Merge(res, mix);
+ }
+
+ SetPowered(uid, component, true);
+ }
+ else // if we do not have high enough pressure to operate, power off
+ {
+ SetPowered(uid, component, false);
+ }
+ }
+ }
+
+ private void SetPowered(EntityUid uid, GasPowerReceiverComponent comp, bool state)
+ {
+ if (state != comp.Powered)
+ {
+ comp.Powered = state;
+ var ev = new PowerChangedEvent(state, 0);
+ RaiseLocalEvent(uid, ref ev);
+ }
+ }
+}
diff --git a/Content.Server/Power/Generator/GeneratorSystem.cs b/Content.Server/Power/Generator/GeneratorSystem.cs
new file mode 100644
index 0000000000..c34922e5f4
--- /dev/null
+++ b/Content.Server/Power/Generator/GeneratorSystem.cs
@@ -0,0 +1,123 @@
+using Content.Server.Chemistry.Components.SolutionManager;
+using Content.Server.Popups;
+using Content.Server.Power.Components;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Power.Generator;
+using Content.Shared.Stacks;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Power.Generator;
+
+///
+public sealed class GeneratorSystem : SharedGeneratorSystem
+{
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly SharedStackSystem _stack = default!;
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+
+
+ private EntityQuery _upgradeQuery;
+
+ public override void Initialize()
+ {
+ _upgradeQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnSolidFuelAdapterInteractUsing);
+ SubscribeLocalEvent(OnChemicalFuelAdapterInteractUsing);
+ SubscribeLocalEvent(OnTargetPowerSet);
+ }
+
+ private void OnChemicalFuelAdapterInteractUsing(EntityUid uid, ChemicalFuelGeneratorAdapterComponent component, InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!TryComp(args.Used, out var solutions) ||
+ !TryComp(uid, out var generator))
+ return;
+
+ if (!(component.Whitelist?.IsValid(args.Used) ?? true))
+ return;
+
+ if (TryComp(args.Used, out var source))
+ {
+ if (!solutions.Solutions.ContainsKey(source.Solution))
+ {
+ Log.Error($"Couldn't get solution {source.Solution} on {ToPrettyString(args.Used)}");
+ return;
+ }
+
+ var solution = solutions.Solutions[source.Solution];
+ generator.RemainingFuel += ReagentsToFuel(component, solution);
+ solution.RemoveAllSolution();
+ QueueDel(args.Used);
+ }
+ }
+
+ private float ReagentsToFuel(ChemicalFuelGeneratorAdapterComponent component, Solution solution)
+ {
+ var total = 0.0f;
+ foreach (var reagent in solution.Contents)
+ {
+ if (!component.ChemConversionFactors.ContainsKey(reagent.ReagentId))
+ continue;
+
+ total += reagent.Quantity.Float() * component.ChemConversionFactors[reagent.ReagentId];
+ }
+
+ return total;
+ }
+
+ private void OnTargetPowerSet(EntityUid uid, FuelGeneratorComponent component, SetTargetPowerMessage args)
+ {
+ component.TargetPower = Math.Clamp(args.TargetPower, 0, component.MaxTargetPower / 1000) * 1000;
+ }
+
+ private void OnSolidFuelAdapterInteractUsing(EntityUid uid, SolidFuelGeneratorAdapterComponent component, InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!TryComp(args.Used, out var mat) ||
+ !HasComp(args.Used) ||
+ !TryComp(uid, out var generator))
+ return;
+
+ if (!mat.MaterialComposition.ContainsKey(component.FuelMaterial))
+ return;
+
+ _popup.PopupEntity(Loc.GetString("generator-insert-material", ("item", args.Used), ("generator", uid)), uid);
+ generator.RemainingFuel += _stack.GetCount(args.Used) * component.Multiplier;
+ QueueDel(args.Used);
+ args.Handled = true;
+ }
+
+ public override void Update(float frameTime)
+ {
+ var query = EntityQueryEnumerator();
+
+ while (query.MoveNext(out var uid, out var gen, out var supplier, out var xform))
+ {
+ supplier.Enabled = gen.RemainingFuel > 0.0f && xform.Anchored;
+
+ var upgradeMultiplier = _upgradeQuery.CompOrNull(uid)?.ActualScalar ?? 1f;
+
+ supplier.MaxSupply = gen.TargetPower * upgradeMultiplier;
+
+ var eff = 1 / CalcFuelEfficiency(gen.TargetPower, gen.OptimalPower, gen);
+
+ gen.RemainingFuel = MathF.Max(gen.RemainingFuel - (gen.OptimalBurnRate * frameTime * eff), 0.0f);
+ UpdateUi(uid, gen);
+ }
+ }
+
+ private void UpdateUi(EntityUid uid, FuelGeneratorComponent comp)
+ {
+ if (!_uiSystem.IsUiOpen(uid, GeneratorComponentUiKey.Key))
+ return;
+
+ _uiSystem.TrySetUiState(uid, GeneratorComponentUiKey.Key, new SolidFuelGeneratorComponentBuiState(comp));
+ }
+}
diff --git a/Content.Server/Power/Generator/SolidFuelGeneratorAdapterComponent.cs b/Content.Server/Power/Generator/SolidFuelGeneratorAdapterComponent.cs
new file mode 100644
index 0000000000..70e3d16410
--- /dev/null
+++ b/Content.Server/Power/Generator/SolidFuelGeneratorAdapterComponent.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Materials;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.Power.Generator;
+
+///
+/// This is used for allowing you to insert fuel into gens.
+///
+[RegisterComponent, Access(typeof(GeneratorSystem))]
+public sealed class SolidFuelGeneratorAdapterComponent : Component
+{
+ ///
+ /// The material to accept as fuel.
+ ///
+ [DataField("fuelMaterial", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public string FuelMaterial = "Plasma";
+
+ ///
+ /// How much fuel that material should count for.
+ ///
+ [DataField("multiplier"), ViewVariables(VVAccess.ReadWrite)]
+ public float Multiplier = 1.0f;
+}
diff --git a/Content.Shared/Power/Generator/FuelGeneratorComponent.cs b/Content.Shared/Power/Generator/FuelGeneratorComponent.cs
new file mode 100644
index 0000000000..0f98d16abf
--- /dev/null
+++ b/Content.Shared/Power/Generator/FuelGeneratorComponent.cs
@@ -0,0 +1,87 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power.Generator;
+
+///
+/// This is used for generators that run off some kind of fuel.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedGeneratorSystem))]
+public sealed class FuelGeneratorComponent : Component
+{
+ ///
+ /// The amount of fuel left in the generator.
+ ///
+ [DataField("remainingFuel"), ViewVariables(VVAccess.ReadWrite)]
+ public float RemainingFuel;
+
+ ///
+ /// The generator's target power.
+ ///
+ [DataField("targetPower"), ViewVariables(VVAccess.ReadWrite)]
+ public float TargetPower = 15_000.0f;
+
+ ///
+ /// The maximum target power.
+ ///
+ [DataField("maxTargetPower"), ViewVariables(VVAccess.ReadWrite)]
+ public float MaxTargetPower = 30_000.0f;
+
+ ///
+ /// The "optimal" power at which the generator is considered to be at 100% efficiency.
+ ///
+ [DataField("optimalPower"), ViewVariables(VVAccess.ReadWrite)]
+ public float OptimalPower = 15_000.0f;
+
+ ///
+ /// The rate at which one unit of fuel should be consumed.
+ ///
+ [DataField("optimalBurnRate"), ViewVariables(VVAccess.ReadWrite)]
+ public float OptimalBurnRate = 1 / 60.0f; // Once every 60 seconds.
+
+ ///
+ /// A constant used to calculate fuel efficiency in relation to target power output and optimal power output
+ ///
+ [DataField("fuelEfficiencyConstant")]
+ public float FuelEfficiencyConstant = 1.3f;
+}
+
+///
+/// Sent to the server to adjust the targeted power level.
+///
+[Serializable, NetSerializable]
+public sealed class SetTargetPowerMessage : BoundUserInterfaceMessage
+{
+ public int TargetPower;
+
+ public SetTargetPowerMessage(int targetPower)
+ {
+ TargetPower = targetPower;
+ }
+}
+
+///
+/// Contains network state for FuelGeneratorComponent.
+///
+[Serializable, NetSerializable]
+public sealed class SolidFuelGeneratorComponentBuiState : BoundUserInterfaceState
+{
+ public float RemainingFuel;
+ public float TargetPower;
+ public float MaximumPower;
+ public float OptimalPower;
+
+ public SolidFuelGeneratorComponentBuiState(FuelGeneratorComponent component)
+ {
+ RemainingFuel = component.RemainingFuel;
+ TargetPower = component.TargetPower;
+ MaximumPower = component.MaxTargetPower;
+ OptimalPower = component.OptimalPower;
+ }
+}
+
+[Serializable, NetSerializable]
+public enum GeneratorComponentUiKey
+{
+ Key
+}
diff --git a/Content.Shared/Power/Generator/SharedGeneratorSystem.cs b/Content.Shared/Power/Generator/SharedGeneratorSystem.cs
new file mode 100644
index 0000000000..aafef6272b
--- /dev/null
+++ b/Content.Shared/Power/Generator/SharedGeneratorSystem.cs
@@ -0,0 +1,19 @@
+namespace Content.Shared.Power.Generator;
+
+///
+/// This handles small, portable generators that run off a material fuel.
+///
+public abstract class SharedGeneratorSystem : EntitySystem
+{
+ ///
+ /// Calculates the expected fuel efficiency based on the optimal and target power levels.
+ ///
+ /// Target power level
+ /// Optimal power level
+ ///
+ /// Expected fuel efficiency as a percentage
+ public static float CalcFuelEfficiency(float targetPower, float optimalPower, FuelGeneratorComponent component)
+ {
+ return MathF.Pow(optimalPower / targetPower, component.FuelEfficiencyConstant);
+ }
+}
diff --git a/Content.Shared/Stacks/SharedStackSystem.cs b/Content.Shared/Stacks/SharedStackSystem.cs
index b01c486213..31d8033171 100644
--- a/Content.Shared/Stacks/SharedStackSystem.cs
+++ b/Content.Shared/Stacks/SharedStackSystem.cs
@@ -3,7 +3,6 @@ using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
-using Content.Shared.Item;
using Content.Shared.Popups;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
@@ -228,6 +227,17 @@ namespace Content.Shared.Stacks
return merged;
}
+ ///
+ /// Gets the amount of items in a stack. If it cannot be stacked, returns 1.
+ ///
+ ///
+ ///
+ ///
+ public int GetCount(EntityUid uid, StackComponent? component = null)
+ {
+ return Resolve(uid, ref component, false) ? component.Count : 1;
+ }
+
///
/// Gets the max count for a given entity prototype
///
diff --git a/Resources/Locale/en-US/power/components/generator.ftl b/Resources/Locale/en-US/power/components/generator.ftl
new file mode 100644
index 0000000000..f5831caa90
--- /dev/null
+++ b/Resources/Locale/en-US/power/components/generator.ftl
@@ -0,0 +1,7 @@
+generator-ui-title = Generator
+generator-ui-target-power-label = Target Power (KW):
+generator-ui-efficiency-label = Efficiency:
+generator-ui-fuel-use-label = Fuel use:
+generator-ui-fuel-left-label = Fuel left:
+
+generator-insert-material = Inserted {THE($item)} into {THE($generator)}...
diff --git a/Resources/Maps/Misc/terminal.yml b/Resources/Maps/Misc/terminal.yml
index f046210aa3..e5a34b68cc 100644
--- a/Resources/Maps/Misc/terminal.yml
+++ b/Resources/Maps/Misc/terminal.yml
@@ -3495,7 +3495,7 @@ entities:
- pos: -12.5,2.5
parent: 818
type: Transform
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 124
components:
diff --git a/Resources/Maps/Shuttles/arrivals.yml b/Resources/Maps/Shuttles/arrivals.yml
index fc1f95771f..428300c345 100644
--- a/Resources/Maps/Shuttles/arrivals.yml
+++ b/Resources/Maps/Shuttles/arrivals.yml
@@ -1243,7 +1243,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 114
components:
diff --git a/Resources/Maps/Shuttles/cargo.yml b/Resources/Maps/Shuttles/cargo.yml
index 38565e20a0..6555e5ccb0 100644
--- a/Resources/Maps/Shuttles/cargo.yml
+++ b/Resources/Maps/Shuttles/cargo.yml
@@ -972,7 +972,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 83
components:
diff --git a/Resources/Maps/Shuttles/dart.yml b/Resources/Maps/Shuttles/dart.yml
index 67b28cbbb1..57218ff88a 100644
--- a/Resources/Maps/Shuttles/dart.yml
+++ b/Resources/Maps/Shuttles/dart.yml
@@ -4489,7 +4489,7 @@ entities:
- pos: 4.5,-21.5
parent: 1
type: Transform
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 72
components:
diff --git a/Resources/Maps/Shuttles/emergency.yml b/Resources/Maps/Shuttles/emergency.yml
index bfcb5a27b7..d82cbee682 100644
--- a/Resources/Maps/Shuttles/emergency.yml
+++ b/Resources/Maps/Shuttles/emergency.yml
@@ -1908,7 +1908,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 239
components:
diff --git a/Resources/Maps/Shuttles/emergency_box.yml b/Resources/Maps/Shuttles/emergency_box.yml
index 0cf29e8e5b..215821df97 100644
--- a/Resources/Maps/Shuttles/emergency_box.yml
+++ b/Resources/Maps/Shuttles/emergency_box.yml
@@ -2874,7 +2874,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 214
components:
diff --git a/Resources/Maps/Shuttles/emergency_courser.yml b/Resources/Maps/Shuttles/emergency_courser.yml
index c47c873834..637634ff5c 100644
--- a/Resources/Maps/Shuttles/emergency_courser.yml
+++ b/Resources/Maps/Shuttles/emergency_courser.yml
@@ -2619,7 +2619,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 66
components:
diff --git a/Resources/Maps/Shuttles/emergency_lox.yml b/Resources/Maps/Shuttles/emergency_lox.yml
index 2f61e00f56..00b3663c16 100644
--- a/Resources/Maps/Shuttles/emergency_lox.yml
+++ b/Resources/Maps/Shuttles/emergency_lox.yml
@@ -3153,7 +3153,7 @@ entities:
type: AmbientSound
- color: '#0335FCFF'
type: AtmosPipeColor
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 402
components:
diff --git a/Resources/Maps/Shuttles/emergency_raven.yml b/Resources/Maps/Shuttles/emergency_raven.yml
index ad2b7ed7de..d71b0bf7eb 100644
--- a/Resources/Maps/Shuttles/emergency_raven.yml
+++ b/Resources/Maps/Shuttles/emergency_raven.yml
@@ -11414,7 +11414,7 @@ entities:
type: AmbientSound
- color: '#0055CCFF'
type: AtmosPipeColor
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 609
components:
diff --git a/Resources/Maps/Shuttles/emergency_rod.yml b/Resources/Maps/Shuttles/emergency_rod.yml
index 9b3e8e4d36..f877c28e37 100644
--- a/Resources/Maps/Shuttles/emergency_rod.yml
+++ b/Resources/Maps/Shuttles/emergency_rod.yml
@@ -2548,7 +2548,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 344
components:
diff --git a/Resources/Maps/Shuttles/emergency_transit.yml b/Resources/Maps/Shuttles/emergency_transit.yml
index 32508e7d5a..0137633ed2 100644
--- a/Resources/Maps/Shuttles/emergency_transit.yml
+++ b/Resources/Maps/Shuttles/emergency_transit.yml
@@ -2434,7 +2434,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 138
components:
diff --git a/Resources/Maps/Shuttles/mining.yml b/Resources/Maps/Shuttles/mining.yml
index 6e952eeb90..9d90444c75 100644
--- a/Resources/Maps/Shuttles/mining.yml
+++ b/Resources/Maps/Shuttles/mining.yml
@@ -982,7 +982,7 @@ entities:
- pos: -1.5,-5.5
parent: 181
type: Transform
-- proto: GeneratorPlasma
+- proto: GeneratorBasic15kW
entities:
- uid: 67
components:
diff --git a/Resources/Maps/Shuttles/pirate.yml b/Resources/Maps/Shuttles/pirate.yml
index faf091665f..3f174fed53 100644
--- a/Resources/Maps/Shuttles/pirate.yml
+++ b/Resources/Maps/Shuttles/pirate.yml
@@ -2883,7 +2883,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 55
components:
diff --git a/Resources/Maps/Shuttles/striker.yml b/Resources/Maps/Shuttles/striker.yml
index 4d341f61e7..3f194ee8d2 100644
--- a/Resources/Maps/Shuttles/striker.yml
+++ b/Resources/Maps/Shuttles/striker.yml
@@ -1241,7 +1241,7 @@ entities:
type: DeviceNetwork
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 41
components:
diff --git a/Resources/Maps/Shuttles/wizard.yml b/Resources/Maps/Shuttles/wizard.yml
index 65d560d538..eaf048501a 100644
--- a/Resources/Maps/Shuttles/wizard.yml
+++ b/Resources/Maps/Shuttles/wizard.yml
@@ -2871,7 +2871,7 @@ entities:
type: Transform
- enabled: False
type: AmbientSound
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 358
components:
diff --git a/Resources/Maps/centcomm.yml b/Resources/Maps/centcomm.yml
index 6de3b3d350..09ed37a1e1 100644
--- a/Resources/Maps/centcomm.yml
+++ b/Resources/Maps/centcomm.yml
@@ -25140,7 +25140,7 @@ entities:
- pos: 32.5,-21.5
parent: 1668
type: Transform
-- proto: GeneratorUranium
+- proto: GeneratorBasic15kW
entities:
- uid: 5176
components:
diff --git a/Resources/Maps/nukieplanet.yml b/Resources/Maps/nukieplanet.yml
index 8d6ebb8065..4676d7687c 100644
--- a/Resources/Maps/nukieplanet.yml
+++ b/Resources/Maps/nukieplanet.yml
@@ -6031,7 +6031,7 @@ entities:
- pos: -5.7255287,20.539352
parent: 104
type: Transform
-- proto: GeneratorPlasma
+- proto: GeneratorBasic15kW
entities:
- uid: 2326
components:
diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml
index d9434d0e93..ce912ae2c2 100644
--- a/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml
@@ -167,13 +167,20 @@
- type: PowerSupplier
supplyRate: 3000
+- type: entity
+ parent: BaseGenerator
+ id: GeneratorBasic15kW
+ suffix: Basic, 15kW
+ components:
+ - type: PowerSupplier
+ supplyRate: 15000
+
- type: entity
parent: [ BaseGenerator, ConstructibleMachine ]
id: GeneratorPlasma
- suffix: Plasma, 5kW
+ suffix: Plasma, 20kW
components:
- type: PowerSupplier
- supplyRate: 5000 # 260 sec / sheet
- type: Sprite
sprite: Structures/Power/Generation/portable_generator.rsi
state: portgen0_1
@@ -186,14 +193,24 @@
- type: UpgradePowerSupplier
powerSupplyMultiplier: 1.25
scaling: Exponential
+ - type: FuelGenerator
+ targetPower: 20000
+ optimalPower: 20000
+ - type: SolidFuelGeneratorAdapter
+ fuelMaterial: Plasma
+ - type: ActivatableUI
+ key: enum.GeneratorComponentUiKey.Key
+ - type: UserInterface
+ interfaces:
+ - key: enum.GeneratorComponentUiKey.Key
+ type: SolidFuelGeneratorBoundUserInterface
- type: entity
parent: [ BaseGenerator, ConstructibleMachine ]
id: GeneratorUranium
- suffix: Uranium, 15kW
+ suffix: Uranium, 30kW
components:
- type: PowerSupplier
- supplyRate: 15000 # 85 sec / sheet
- type: Sprite
sprite: Structures/Power/Generation/portable_generator.rsi
state: portgen1_1
@@ -206,6 +223,18 @@
- type: UpgradePowerSupplier
powerSupplyMultiplier: 1.25
scaling: Exponential
+ - type: FuelGenerator
+ targetPower: 30000
+ optimalPower: 30000
+ optimalBurnRate: 0.00416666666
+ - type: SolidFuelGeneratorAdapter
+ fuelMaterial: Uranium
+ - type: ActivatableUI
+ key: enum.GeneratorComponentUiKey.Key
+ - type: UserInterface
+ interfaces:
+ - key: enum.GeneratorComponentUiKey.Key
+ type: SolidFuelGeneratorBoundUserInterface
- type: entity
parent: BaseGeneratorWallmount