Portable Generator Rework (#19302)
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -51,4 +52,58 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
|
||||
var handsQuery = GetEntityQuery<HandsComponent>();
|
||||
Update(playerEntity.Value, active, comp, time, xformQuery, handsQuery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find an active do-after being executed by the local player.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity the do after must be targeting (<see cref="DoAfterArgs.Target"/>)</param>
|
||||
/// <param name="doAfter">The found do-after.</param>
|
||||
/// <param name="event">The event to be raised on the found do-after when it completes.</param>
|
||||
/// <param name="progress">The progress of the found do-after, from 0 to 1.</param>
|
||||
/// <typeparam name="T">The type of event that must be raised by the found do-after.</typeparam>
|
||||
/// <returns>True if a do-after was found.</returns>
|
||||
public bool TryFindActiveDoAfter<T>(
|
||||
EntityUid entity,
|
||||
[NotNullWhen(true)] out Shared.DoAfter.DoAfter? doAfter,
|
||||
[NotNullWhen(true)] out T? @event,
|
||||
out float progress)
|
||||
where T : DoAfterEvent
|
||||
{
|
||||
var playerEntity = _player.LocalPlayer?.ControlledEntity;
|
||||
|
||||
doAfter = null;
|
||||
@event = null;
|
||||
progress = default;
|
||||
|
||||
if (!TryComp(playerEntity, out ActiveDoAfterComponent? active))
|
||||
return false;
|
||||
|
||||
if (_metadata.EntityPaused(playerEntity.Value))
|
||||
return false;
|
||||
|
||||
var comp = Comp<DoAfterComponent>(playerEntity.Value);
|
||||
|
||||
var time = GameTiming.CurTime;
|
||||
|
||||
foreach (var candidate in comp.DoAfters.Values)
|
||||
{
|
||||
if (candidate.Cancelled)
|
||||
continue;
|
||||
|
||||
if (candidate.Args.Target != entity)
|
||||
continue;
|
||||
|
||||
if (candidate.Args.Event is not T candidateEvent)
|
||||
continue;
|
||||
|
||||
@event = candidateEvent;
|
||||
doAfter = candidate;
|
||||
var elapsed = time - doAfter.StartTime;
|
||||
progress = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="350 130"
|
||||
SetSize="360 180"
|
||||
Title="{Loc 'generator-ui-title'}">
|
||||
MinSize="450 235"
|
||||
SetSize="450 235"
|
||||
Resizable="False"
|
||||
Title="{Loc 'portable-generator-ui-title'}">
|
||||
<BoxContainer Margin="4 0" Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2" VerticalAlignment="Center" Margin="5">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2" VerticalAlignment="Top" Margin="5">
|
||||
<GridContainer Margin="2 0 0 0" Columns="2" HorizontalExpand="True">
|
||||
<Label Name="StatusLabel" Text="{Loc 'portable-generator-ui-power-switch'}" HorizontalExpand="True" />
|
||||
<Control MinWidth="120">
|
||||
<Button Name="StartButton" Text="{Loc 'portable-generator-ui-start'}" />
|
||||
<Button Name="StopButton" Text="{Loc 'portable-generator-ui-stop'}" />
|
||||
<ProgressBar Name="StartProgress" MaxValue="1" />
|
||||
<Label Name="LabelUnanchored" Text="{Loc 'portable-generator-ui-unanchored'}" />
|
||||
</Control>
|
||||
<!-- Power -->
|
||||
<Label Text="{Loc 'generator-ui-target-power-label'}"/>
|
||||
<Label Text="{Loc 'portable-generator-ui-target-power-label'}"/>
|
||||
<SpinBox Name="TargetPower" HorizontalExpand="True"/>
|
||||
<Label Text="{Loc 'generator-ui-efficiency-label'}"/>
|
||||
<Label Name="Efficiency" Text="???%" HorizontalExpand="True"/>
|
||||
<Label Text="{Loc 'generator-ui-fuel-use-label'}"/>
|
||||
<Label Text="{Loc 'portable-generator-ui-efficiency-label'}"/>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="Efficiency" Text="???%" />
|
||||
<Label Name="Eta" HorizontalExpand="True" Margin="4 0 0 0" />
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc 'portable-generator-ui-fuel-use-label'}"/>
|
||||
<ProgressBar Name="FuelFraction" MinValue="0" MaxValue="1" HorizontalExpand="True"/>
|
||||
<Label Text="{Loc 'generator-ui-fuel-left-label'}"/>
|
||||
<Label Text="{Loc 'portable-generator-ui-fuel-left-label'}"/>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="FuelLeft" Text="0" HorizontalExpand="True"/>
|
||||
<Button Name="FuelEject" Text="{Loc 'portable-generator-ui-eject'}" />
|
||||
</BoxContainer>
|
||||
<Label Name="OutputSwitchLabel" Text="{Loc 'portable-generator-ui-switch'}" Visible="False" />
|
||||
<Button Name="OutputSwitchButton" Visible="False" />
|
||||
</GridContainer>
|
||||
<Label Margin="2 0 0 0" Name="CloggedLabel" FontColorOverride="Red" Text="{Loc 'portable-generator-ui-clogged'}" />
|
||||
</BoxContainer>
|
||||
<cc:VSeparator StyleClasses="LowDivider"/>
|
||||
<PanelContainer Margin="12 0 0 0" StyleClasses="Inset" VerticalAlignment="Center">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.DoAfter;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -8,24 +9,33 @@ namespace Content.Client.Power.Generator;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GeneratorWindow : FancyWindow
|
||||
{
|
||||
private readonly EntityUid _entity;
|
||||
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private readonly FuelGeneratorComponent? _component;
|
||||
private SolidFuelGeneratorComponentBuiState? _lastState;
|
||||
private PortableGeneratorComponentBuiState? _lastState;
|
||||
|
||||
public GeneratorWindow(SolidFuelGeneratorBoundUserInterface bui, EntityUid vis)
|
||||
public GeneratorWindow(PortableGeneratorBoundUserInterface bui, EntityUid entity)
|
||||
{
|
||||
_entity = entity;
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_entityManager.TryGetComponent(vis, out _component);
|
||||
_entityManager.TryGetComponent(entity, out _component);
|
||||
|
||||
EntityView.SetEntity(vis);
|
||||
EntityView.SetEntity(entity);
|
||||
TargetPower.IsValid += IsValid;
|
||||
TargetPower.ValueChanged += (args) =>
|
||||
{
|
||||
bui.SetTargetPower(args.Value);
|
||||
};
|
||||
|
||||
StartButton.OnPressed += _ => bui.Start();
|
||||
StopButton.OnPressed += _ => bui.Stop();
|
||||
OutputSwitchButton.OnPressed += _ => bui.SwitchOutput();
|
||||
FuelEject.OnPressed += _ => bui.EjectFuel();
|
||||
}
|
||||
|
||||
private bool IsValid(int arg)
|
||||
@@ -39,19 +49,76 @@ public sealed partial class GeneratorWindow : FancyWindow
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Update(SolidFuelGeneratorComponentBuiState state)
|
||||
public void Update(PortableGeneratorComponentBuiState state)
|
||||
{
|
||||
if (_component == null)
|
||||
return;
|
||||
|
||||
var oldState = _lastState;
|
||||
_lastState = state;
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (oldState?.TargetPower != state.TargetPower)
|
||||
if (!TargetPower.LineEditControl.HasKeyboardFocus())
|
||||
TargetPower.OverrideValue((int)(state.TargetPower / 1000.0f));
|
||||
Efficiency.Text = SharedGeneratorSystem.CalcFuelEfficiency(state.TargetPower, state.OptimalPower, _component).ToString("P1");
|
||||
var efficiency = SharedGeneratorSystem.CalcFuelEfficiency(state.TargetPower, state.OptimalPower, _component);
|
||||
Efficiency.Text = efficiency.ToString("P1");
|
||||
|
||||
var burnRate = _component.OptimalBurnRate / efficiency;
|
||||
var left = state.RemainingFuel / burnRate;
|
||||
|
||||
Eta.Text = Loc.GetString(
|
||||
"portable-generator-ui-eta",
|
||||
("minutes", Math.Ceiling(left / 60.0)));
|
||||
FuelFraction.Value = state.RemainingFuel - (int) state.RemainingFuel;
|
||||
FuelLeft.Text = ((int) MathF.Floor(state.RemainingFuel)).ToString();
|
||||
|
||||
var progress = 0f;
|
||||
|
||||
var unanchored = !_entityManager.GetComponent<TransformComponent>(_entity).Anchored;
|
||||
var starting = !unanchored && TryGetStartProgress(out progress);
|
||||
var on = !unanchored && !starting && state.On;
|
||||
var off = !unanchored && !starting && !state.On;
|
||||
|
||||
LabelUnanchored.Visible = unanchored;
|
||||
StartProgress.Visible = starting;
|
||||
StopButton.Visible = on;
|
||||
StartButton.Visible = off;
|
||||
|
||||
if (starting)
|
||||
{
|
||||
StatusLabel.Text = _loc.GetString("portable-generator-ui-status-starting");
|
||||
StatusLabel.SetOnlyStyleClass("Caution");
|
||||
|
||||
StartProgress.Value = progress;
|
||||
}
|
||||
else if (on)
|
||||
{
|
||||
StatusLabel.Text = _loc.GetString("portable-generator-ui-status-running");
|
||||
StatusLabel.SetOnlyStyleClass("Good");
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusLabel.Text = _loc.GetString("portable-generator-ui-status-stopped");
|
||||
StatusLabel.SetOnlyStyleClass("Danger");
|
||||
}
|
||||
|
||||
var canSwitch = _entityManager.TryGetComponent(_entity, out PowerSwitchableGeneratorComponent? switchable);
|
||||
OutputSwitchLabel.Visible = canSwitch;
|
||||
OutputSwitchButton.Visible = canSwitch;
|
||||
|
||||
if (canSwitch)
|
||||
{
|
||||
var isHV = switchable!.ActiveOutput == PowerSwitchableGeneratorOutput.HV;
|
||||
OutputSwitchLabel.Text =
|
||||
Loc.GetString(isHV ? "portable-generator-ui-switch-hv" : "portable-generator-ui-switch-mv");
|
||||
OutputSwitchButton.Text =
|
||||
Loc.GetString(isHV ? "portable-generator-ui-switch-to-mv" : "portable-generator-ui-switch-to-hv");
|
||||
OutputSwitchButton.Disabled = state.On;
|
||||
}
|
||||
|
||||
CloggedLabel.Visible = state.Clogged;
|
||||
}
|
||||
|
||||
private bool TryGetStartProgress(out float progress)
|
||||
{
|
||||
var doAfterSystem = _entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
|
||||
return doAfterSystem.TryFindActiveDoAfter<GeneratorStartedEvent>(_entity, out _, out _, out progress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Content.Shared.Power.Generator;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Power.Generator;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class PortableGeneratorBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private GeneratorWindow? _window;
|
||||
|
||||
public PortableGeneratorBoundUserInterface(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 PortableGeneratorComponentBuiState msg)
|
||||
return;
|
||||
|
||||
_window?.Update(msg);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_window?.Dispose();
|
||||
}
|
||||
|
||||
public void SetTargetPower(int target)
|
||||
{
|
||||
SendMessage(new PortableGeneratorSetTargetPowerMessage(target));
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
SendMessage(new PortableGeneratorStartMessage());
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
SendMessage(new PortableGeneratorStopMessage());
|
||||
}
|
||||
|
||||
public void SwitchOutput()
|
||||
{
|
||||
SendMessage(new PortableGeneratorSwitchOutputMessage());
|
||||
}
|
||||
|
||||
public void EjectFuel()
|
||||
{
|
||||
SendMessage(new PortableGeneratorEjectFuelMessage());
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -369,13 +369,13 @@ namespace Content.Client.Stylesheets
|
||||
{
|
||||
BackgroundColor = new Color(0.25f, 0.25f, 0.25f)
|
||||
};
|
||||
progressBarBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 5);
|
||||
progressBarBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 14.5f);
|
||||
|
||||
var progressBarForeground = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = new Color(0.25f, 0.50f, 0.25f)
|
||||
};
|
||||
progressBarForeground.SetContentMarginOverride(StyleBox.Margin.Vertical, 5);
|
||||
progressBarForeground.SetContentMarginOverride(StyleBox.Margin.Vertical, 14.5f);
|
||||
|
||||
// CheckBox
|
||||
var checkBoxTextureChecked = resCache.GetTexture("/Textures/Interface/Nano/checkbox_checked.svg.96dpi.png");
|
||||
|
||||
@@ -52,13 +52,13 @@ namespace Content.Client.Stylesheets
|
||||
{
|
||||
BackgroundColor = new Color(0.25f, 0.25f, 0.25f)
|
||||
};
|
||||
progressBarBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 5);
|
||||
progressBarBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 14.5f);
|
||||
|
||||
var progressBarForeground = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = new Color(0.25f, 0.50f, 0.25f)
|
||||
};
|
||||
progressBarForeground.SetContentMarginOverride(StyleBox.Margin.Vertical, 5);
|
||||
progressBarForeground.SetContentMarginOverride(StyleBox.Margin.Vertical, 14.5f);
|
||||
|
||||
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
@@ -129,4 +130,63 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
|
||||
overflowMaterial = amount - amountToSpawn * materialPerStack;
|
||||
return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eject a material out of this storage. The internal counts are updated.
|
||||
/// Material that cannot be ejected stays in storage. (e.g. only have 50 but a sheet needs 100).
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity with storage to eject from.</param>
|
||||
/// <param name="material">The material prototype to eject.</param>
|
||||
/// <param name="maxAmount">The maximum amount to eject. If not given, as much as possible is ejected.</param>
|
||||
/// <param name="coordinates">The position where to spawn the created sheets. If not given, they're spawned next to the entity.</param>
|
||||
/// <param name="component">The storage component on <paramref name="entity"/>. Resolved automatically if not given.</param>
|
||||
/// <returns>The stack entities that were spawned.</returns>
|
||||
public List<EntityUid> EjectMaterial(
|
||||
EntityUid entity,
|
||||
string material,
|
||||
int? maxAmount = null,
|
||||
EntityCoordinates? coordinates = null,
|
||||
MaterialStorageComponent? component = null)
|
||||
{
|
||||
if (!Resolve(entity, ref component))
|
||||
return new List<EntityUid>();
|
||||
|
||||
coordinates ??= Transform(entity).Coordinates;
|
||||
|
||||
var amount = GetMaterialAmount(entity, material, component);
|
||||
if (maxAmount != null)
|
||||
amount = Math.Min(maxAmount.Value, amount);
|
||||
|
||||
var spawned = SpawnMultipleFromMaterial(amount, material, coordinates.Value, out var overflow);
|
||||
|
||||
TryChangeMaterialAmount(entity, material, -(amount - overflow), component);
|
||||
return spawned;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eject all material stored in an entity, with the same mechanics as <see cref="EjectMaterial"/>.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity with storage to eject from.</param>
|
||||
/// <param name="coordinates">The position where to spawn the created sheets. If not given, they're spawned next to the entity.</param>
|
||||
/// <param name="component">The storage component on <paramref name="entity"/>. Resolved automatically if not given.</param>
|
||||
/// <returns>The stack entities that were spawned.</returns>
|
||||
public List<EntityUid> EjectAllMaterial(
|
||||
EntityUid entity,
|
||||
EntityCoordinates? coordinates = null,
|
||||
MaterialStorageComponent? component = null)
|
||||
{
|
||||
if (!Resolve(entity, ref component))
|
||||
return new List<EntityUid>();
|
||||
|
||||
coordinates ??= Transform(entity).Coordinates;
|
||||
|
||||
var allSpawned = new List<EntityUid>();
|
||||
foreach (var material in component.Storage.Keys.ToArray())
|
||||
{
|
||||
var spawned = EjectMaterial(entity, material, null, coordinates, component);
|
||||
allSpawned.AddRange(spawned);
|
||||
}
|
||||
|
||||
return allSpawned;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server.Power.Components
|
||||
}
|
||||
|
||||
public abstract partial class BaseNetConnectorComponent<TNetType> : Component, IBaseNetConnectorComponent<TNetType>
|
||||
where TNetType : class
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
@@ -46,7 +47,10 @@ namespace Content.Server.Power.Components
|
||||
public void ClearNet()
|
||||
{
|
||||
if (_net != null)
|
||||
{
|
||||
RemoveSelfFromNet(_net);
|
||||
_net = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void AddSelfToNet(TNetType net);
|
||||
|
||||
@@ -4,7 +4,7 @@ using Content.Server.Power.Pow3r;
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class PowerSupplierComponent : BasePowerNetComponent
|
||||
public sealed partial class PowerSupplierComponent : BaseNetConnectorComponent<IBasePowerNet>
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("supplyRate")]
|
||||
@@ -47,12 +47,12 @@ namespace Content.Server.Power.Components
|
||||
[ViewVariables]
|
||||
public PowerState.Supply NetworkSupply { get; } = new();
|
||||
|
||||
protected override void AddSelfToNet(IPowerNet powerNet)
|
||||
protected override void AddSelfToNet(IBasePowerNet powerNet)
|
||||
{
|
||||
powerNet.AddSupplier(this);
|
||||
}
|
||||
|
||||
protected override void RemoveSelfFromNet(IPowerNet powerNet)
|
||||
protected override void RemoveSelfFromNet(IBasePowerNet powerNet)
|
||||
{
|
||||
powerNet.RemoveSupplier(this);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class PowerNetConnectorSystem : EntitySystem
|
||||
BaseNetConnectorInit(component);
|
||||
}
|
||||
|
||||
public void BaseNetConnectorInit<T>(BaseNetConnectorComponent<T> component)
|
||||
public void BaseNetConnectorInit<T>(BaseNetConnectorComponent<T> component) where T : class
|
||||
{
|
||||
if (component.NeedsNet)
|
||||
{
|
||||
|
||||
@@ -395,11 +395,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var consumer in net.Consumers)
|
||||
{
|
||||
netNode.Loads.Add(consumer.NetworkLoad.Id);
|
||||
consumer.NetworkLoad.LinkedNetwork = netNode.Id;
|
||||
}
|
||||
DoReconnectBasePowerNet(net, netNode);
|
||||
|
||||
var batteryQuery = GetEntityQuery<PowerNetworkBatteryComponent>();
|
||||
|
||||
@@ -420,17 +416,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
netNode.BatteryLoads.Clear();
|
||||
netNode.BatterySupplies.Clear();
|
||||
|
||||
foreach (var consumer in net.Consumers)
|
||||
{
|
||||
netNode.Loads.Add(consumer.NetworkLoad.Id);
|
||||
consumer.NetworkLoad.LinkedNetwork = netNode.Id;
|
||||
}
|
||||
|
||||
foreach (var supplier in net.Suppliers)
|
||||
{
|
||||
netNode.Supplies.Add(supplier.NetworkSupply.Id);
|
||||
supplier.NetworkSupply.LinkedNetwork = netNode.Id;
|
||||
}
|
||||
DoReconnectBasePowerNet(net, netNode);
|
||||
|
||||
var batteryQuery = GetEntityQuery<PowerNetworkBatteryComponent>();
|
||||
|
||||
@@ -448,6 +434,22 @@ namespace Content.Server.Power.EntitySystems
|
||||
battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoReconnectBasePowerNet<TNetType>(BasePowerNet<TNetType> net, PowerState.Network netNode)
|
||||
where TNetType : IBasePowerNet
|
||||
{
|
||||
foreach (var consumer in net.Consumers)
|
||||
{
|
||||
netNode.Loads.Add(consumer.NetworkLoad.Id);
|
||||
consumer.NetworkLoad.LinkedNetwork = netNode.Id;
|
||||
}
|
||||
|
||||
foreach (var supplier in net.Suppliers)
|
||||
{
|
||||
netNode.Supplies.Add(supplier.NetworkSupply.Id);
|
||||
supplier.NetworkSupply.LinkedNetwork = netNode.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
@@ -11,14 +12,29 @@ namespace Content.Server.Power.Generator;
|
||||
public sealed partial class ChemicalFuelGeneratorAdapterComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The acceptable list of input entities.
|
||||
/// The reagent to accept as fuel.
|
||||
/// </summary>
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
[DataField("reagent", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Reagent = "WeldingFuel";
|
||||
|
||||
/// <summary>
|
||||
/// The conversion factor for different chems you can put in.
|
||||
/// The solution on the <see cref="SolutionContainerManagerComponent"/> to use.
|
||||
/// </summary>
|
||||
[DataField("chemConversionFactors", required: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer<float, ReagentPrototype>))]
|
||||
public Dictionary<string, float> ChemConversionFactors = default!;
|
||||
[DataField("solution")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "tank";
|
||||
|
||||
/// <summary>
|
||||
/// Value to multiply reagent amount by to get fuel amount.
|
||||
/// </summary>
|
||||
[DataField("multiplier"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Multiplier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// How much reagent (can be fractional) is left in the generator.
|
||||
/// Stored in units of <see cref="FixedPoint2.Epsilon"/>.
|
||||
/// </summary>
|
||||
[DataField("fractionalReagent"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float FractionalReagent;
|
||||
}
|
||||
|
||||
28
Content.Server/Power/Generator/GeneratorExhaustGasSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Power.Generator;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
/// <seealso cref="GeneratorSystem"/>
|
||||
/// <seealso cref="GeneratorExhaustGasComponent"/>
|
||||
public sealed class GeneratorExhaustGasSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GeneratorExhaustGasComponent, GeneratorUseFuel>(FuelUsed);
|
||||
}
|
||||
|
||||
private void FuelUsed(EntityUid uid, GeneratorExhaustGasComponent component, GeneratorUseFuel args)
|
||||
{
|
||||
var exhaustMixture = new GasMixture();
|
||||
exhaustMixture.SetMoles(component.GasType, args.FuelUsed * component.MoleRatio);
|
||||
exhaustMixture.Temperature = component.Temperature;
|
||||
|
||||
var environment = _atmosphere.GetContainingMixture(uid, false, true);
|
||||
if (environment != null)
|
||||
_atmosphere.Merge(environment, exhaustMixture);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,29 @@
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Audio;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Materials;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <seealso cref="FuelGeneratorComponent"/>
|
||||
/// <seealso cref="ChemicalFuelGeneratorAdapterComponent"/>
|
||||
/// <seealso cref="SolidFuelGeneratorAdapterComponent"/>
|
||||
public sealed class GeneratorSystem : SharedGeneratorSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
|
||||
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||
|
||||
private EntityQuery<UpgradePowerSupplierComponent> _upgradeQuery;
|
||||
|
||||
@@ -24,100 +31,239 @@ public sealed class GeneratorSystem : SharedGeneratorSystem
|
||||
{
|
||||
_upgradeQuery = GetEntityQuery<UpgradePowerSupplierComponent>();
|
||||
|
||||
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, InteractUsingEvent>(OnSolidFuelAdapterInteractUsing);
|
||||
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, InteractUsingEvent>(OnChemicalFuelAdapterInteractUsing);
|
||||
SubscribeLocalEvent<FuelGeneratorComponent, SetTargetPowerMessage>(OnTargetPowerSet);
|
||||
UpdatesBefore.Add(typeof(PowerNetSystem));
|
||||
|
||||
SubscribeLocalEvent<FuelGeneratorComponent, PortableGeneratorSetTargetPowerMessage>(OnTargetPowerSet);
|
||||
SubscribeLocalEvent<FuelGeneratorComponent, PortableGeneratorEjectFuelMessage>(OnEjectFuel);
|
||||
SubscribeLocalEvent<FuelGeneratorComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
|
||||
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorGetFuelEvent>(SolidGetFuel);
|
||||
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorUseFuel>(SolidUseFuel);
|
||||
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorEmpty>(SolidEmpty);
|
||||
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorGetFuelEvent>(ChemicalGetFuel);
|
||||
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorUseFuel>(ChemicalUseFuel);
|
||||
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorGetCloggedEvent>(ChemicalGetClogged);
|
||||
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorEmpty>(ChemicalEmpty);
|
||||
}
|
||||
|
||||
private void OnChemicalFuelAdapterInteractUsing(EntityUid uid, ChemicalFuelGeneratorAdapterComponent component, InteractUsingEvent args)
|
||||
private void OnAnchorStateChanged(EntityUid uid, FuelGeneratorComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
// Turn off generator if unanchored while running.
|
||||
|
||||
if (!component.On)
|
||||
return;
|
||||
|
||||
if (!TryComp<SolutionContainerManagerComponent>(args.Used, out var solutions) ||
|
||||
!TryComp<FuelGeneratorComponent>(uid, out var generator))
|
||||
return;
|
||||
|
||||
if (!(component.Whitelist?.IsValid(args.Used) ?? true))
|
||||
return;
|
||||
|
||||
if (TryComp<ChemicalFuelGeneratorDirectSourceComponent>(args.Used, out var source))
|
||||
{
|
||||
if (!solutions.Solutions.ContainsKey(source.Solution))
|
||||
{
|
||||
Log.Error($"Couldn't get solution {source.Solution} on {ToPrettyString(args.Used)}");
|
||||
return;
|
||||
SetFuelGeneratorOn(uid, false, component);
|
||||
}
|
||||
|
||||
var solution = solutions.Solutions[source.Solution];
|
||||
generator.RemainingFuel += ReagentsToFuel(component, solution);
|
||||
solution.RemoveAllSolution();
|
||||
QueueDel(args.Used);
|
||||
private void OnEjectFuel(EntityUid uid, FuelGeneratorComponent component, PortableGeneratorEjectFuelMessage args)
|
||||
{
|
||||
EmptyGenerator(uid);
|
||||
}
|
||||
|
||||
private void SolidEmpty(EntityUid uid, SolidFuelGeneratorAdapterComponent component, GeneratorEmpty args)
|
||||
{
|
||||
_materialStorage.EjectAllMaterial(uid);
|
||||
}
|
||||
|
||||
private void ChemicalEmpty(EntityUid uid, ChemicalFuelGeneratorAdapterComponent component, GeneratorEmpty args)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var solution))
|
||||
return;
|
||||
|
||||
var spillSolution = _solutionContainer.SplitSolution(uid, solution, solution.Volume);
|
||||
_puddle.TrySpillAt(uid, spillSolution, out _);
|
||||
}
|
||||
|
||||
private void ChemicalGetClogged(EntityUid uid, ChemicalFuelGeneratorAdapterComponent component, ref GeneratorGetCloggedEvent args)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var solution))
|
||||
return;
|
||||
|
||||
foreach (var reagentQuantity in solution)
|
||||
{
|
||||
if (reagentQuantity.ReagentId != component.Reagent)
|
||||
{
|
||||
args.Clogged = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float ReagentsToFuel(ChemicalFuelGeneratorAdapterComponent component, Solution solution)
|
||||
private void ChemicalUseFuel(EntityUid uid, ChemicalFuelGeneratorAdapterComponent component, GeneratorUseFuel args)
|
||||
{
|
||||
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)
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var solution))
|
||||
return;
|
||||
|
||||
if (!TryComp<PhysicalCompositionComponent>(args.Used, out var mat) ||
|
||||
!HasComp<MaterialComponent>(args.Used) ||
|
||||
!TryComp<FuelGeneratorComponent>(uid, out var generator))
|
||||
var availableReagent = solution.GetReagentQuantity(component.Reagent).Value;
|
||||
var toRemove = RemoveFractionalFuel(
|
||||
ref component.FractionalReagent,
|
||||
args.FuelUsed,
|
||||
component.Multiplier * FixedPoint2.Epsilon.Float(),
|
||||
availableReagent);
|
||||
|
||||
solution.RemoveReagent(component.Reagent, FixedPoint2.FromCents(toRemove));
|
||||
}
|
||||
|
||||
private void ChemicalGetFuel(
|
||||
EntityUid uid,
|
||||
ChemicalFuelGeneratorAdapterComponent component,
|
||||
ref GeneratorGetFuelEvent args)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(uid, component.Solution, out var solution))
|
||||
return;
|
||||
|
||||
if (!mat.MaterialComposition.ContainsKey(component.FuelMaterial))
|
||||
var reagent = component.FractionalReagent * FixedPoint2.Epsilon.Float()
|
||||
+ solution.GetReagentQuantity(component.Reagent).Float();
|
||||
args.Fuel = reagent * component.Multiplier;
|
||||
}
|
||||
|
||||
private void SolidUseFuel(EntityUid uid, SolidFuelGeneratorAdapterComponent component, GeneratorUseFuel args)
|
||||
{
|
||||
var availableMaterial = _materialStorage.GetMaterialAmount(uid, component.FuelMaterial);
|
||||
var toRemove = RemoveFractionalFuel(
|
||||
ref component.FractionalMaterial,
|
||||
args.FuelUsed,
|
||||
component.Multiplier,
|
||||
availableMaterial);
|
||||
|
||||
_materialStorage.TryChangeMaterialAmount(uid, component.FuelMaterial, -toRemove);
|
||||
}
|
||||
|
||||
private int RemoveFractionalFuel(ref float fractional, float fuelUsed, float multiplier, int availableQuantity)
|
||||
{
|
||||
fractional -= fuelUsed / multiplier;
|
||||
if (fractional >= 0)
|
||||
return 0;
|
||||
|
||||
// worst (unrealistic) case: -5.5 -> -6.0 -> 6
|
||||
var toRemove = -(int) MathF.Floor(fractional);
|
||||
toRemove = Math.Min(availableQuantity, toRemove);
|
||||
|
||||
fractional = Math.Max(0, fractional + toRemove);
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
private void SolidGetFuel(
|
||||
EntityUid uid,
|
||||
SolidFuelGeneratorAdapterComponent component,
|
||||
ref GeneratorGetFuelEvent args)
|
||||
{
|
||||
var material = component.FractionalMaterial + _materialStorage.GetMaterialAmount(uid, component.FuelMaterial);
|
||||
args.Fuel = material * component.Multiplier;
|
||||
}
|
||||
|
||||
private void OnTargetPowerSet(EntityUid uid, FuelGeneratorComponent component,
|
||||
PortableGeneratorSetTargetPowerMessage args)
|
||||
{
|
||||
component.TargetPower = Math.Clamp(
|
||||
args.TargetPower,
|
||||
component.MinTargetPower / 1000,
|
||||
component.MaxTargetPower / 1000) * 1000;
|
||||
}
|
||||
|
||||
public void SetFuelGeneratorOn(EntityUid uid, bool on, FuelGeneratorComponent? generator = null)
|
||||
{
|
||||
if (!Resolve(uid, ref generator))
|
||||
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;
|
||||
if (on && !Transform(uid).Anchored)
|
||||
{
|
||||
// Generator must be anchored to start.
|
||||
return;
|
||||
}
|
||||
|
||||
generator.On = on;
|
||||
UpdateState(uid, generator);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<FuelGeneratorComponent, PowerSupplierComponent, TransformComponent>();
|
||||
var query = EntityQueryEnumerator<FuelGeneratorComponent, PowerSupplierComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var gen, out var supplier, out var xform))
|
||||
while (query.MoveNext(out var uid, out var gen, out var supplier))
|
||||
{
|
||||
supplier.Enabled = gen.RemainingFuel > 0.0f && xform.Anchored;
|
||||
if (!gen.On)
|
||||
continue;
|
||||
|
||||
var fuel = GetFuel(uid);
|
||||
if (fuel <= 0)
|
||||
{
|
||||
SetFuelGeneratorOn(uid, false, gen);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetIsClogged(uid))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("generator-clogged", ("generator", uid)), uid, PopupType.SmallCaution);
|
||||
SetFuelGeneratorOn(uid, false, gen);
|
||||
continue;
|
||||
}
|
||||
|
||||
supplier.Enabled = true;
|
||||
|
||||
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);
|
||||
var consumption = gen.OptimalBurnRate * frameTime * eff;
|
||||
RaiseLocalEvent(uid, new GeneratorUseFuel(consumption));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUi(EntityUid uid, FuelGeneratorComponent comp)
|
||||
public float GetFuel(EntityUid generator)
|
||||
{
|
||||
if (!_uiSystem.IsUiOpen(uid, GeneratorComponentUiKey.Key))
|
||||
return;
|
||||
GeneratorGetFuelEvent getFuelEvent = default;
|
||||
RaiseLocalEvent(generator, ref getFuelEvent);
|
||||
return getFuelEvent.Fuel;
|
||||
}
|
||||
|
||||
_uiSystem.TrySetUiState(uid, GeneratorComponentUiKey.Key, new SolidFuelGeneratorComponentBuiState(comp));
|
||||
public bool GetIsClogged(EntityUid generator)
|
||||
{
|
||||
GeneratorGetCloggedEvent getCloggedEvent = default;
|
||||
RaiseLocalEvent(generator, ref getCloggedEvent);
|
||||
return getCloggedEvent.Clogged;
|
||||
}
|
||||
|
||||
public void EmptyGenerator(EntityUid generator)
|
||||
{
|
||||
RaiseLocalEvent(generator, GeneratorEmpty.Instance);
|
||||
}
|
||||
|
||||
private void UpdateState(EntityUid generator, FuelGeneratorComponent component)
|
||||
{
|
||||
_appearance.SetData(generator, GeneratorVisuals.Running, component.On);
|
||||
_ambientSound.SetAmbience(generator, component.On);
|
||||
if (!component.On)
|
||||
Comp<PowerSupplierComponent>(generator).Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="GeneratorSystem"/> to calculate the amount of remaining fuel in the generator.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GeneratorGetFuelEvent(float Fuel);
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="GeneratorSystem"/> to check if a generator is "clogged".
|
||||
/// For example there's bad chemicals in the fuel tank that prevent starting it.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GeneratorGetCloggedEvent(bool Clogged);
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="GeneratorSystem"/> to draw fuel from its adapters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations are expected to round fuel consumption up if the used fuel value is too small (e.g. reagent units).
|
||||
/// </remarks>
|
||||
public record struct GeneratorUseFuel(float FuelUsed);
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="GeneratorSystem"/> to empty a generator of its fuel contents.
|
||||
/// </summary>
|
||||
public sealed class GeneratorEmpty
|
||||
{
|
||||
public static readonly GeneratorEmpty Instance = new();
|
||||
}
|
||||
|
||||
190
Content.Server/Power/Generator/PortableGeneratorSystem.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Implements logic for portable generators (the PACMAN). Primarily UI & power switching behavior.
|
||||
/// </summary>
|
||||
/// <seealso cref="PortableGeneratorComponent"/>
|
||||
public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GeneratorSystem _generator = default!;
|
||||
[Dependency] private readonly PowerSwitchableGeneratorSystem _switchableGenerator = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Update UI after main system runs.
|
||||
UpdatesAfter.Add(typeof(GeneratorSystem));
|
||||
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, GetVerbsEvent<AlternativeVerb>>(GetAlternativeVerb);
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, GeneratorStartedEvent>(GeneratorTugged);
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, PortableGeneratorStartMessage>(GeneratorStartMessage);
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, PortableGeneratorStopMessage>(GeneratorStopMessage);
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, PortableGeneratorSwitchOutputMessage>(GeneratorSwitchOutputMessage);
|
||||
}
|
||||
|
||||
private void GeneratorSwitchOutputMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorSwitchOutputMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
var fuelGenerator = Comp<FuelGeneratorComponent>(uid);
|
||||
if (fuelGenerator.On)
|
||||
return;
|
||||
|
||||
_switchableGenerator.ToggleActiveOutput(uid, args.Session.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void GeneratorStopMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorStopMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
StopGenerator(uid, component, args.Session.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void GeneratorStartMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorStartMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
StartGenerator(uid, component, args.Session.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void StartGenerator(EntityUid uid, PortableGeneratorComponent component, EntityUid user)
|
||||
{
|
||||
var fuelGenerator = Comp<FuelGeneratorComponent>(uid);
|
||||
if (fuelGenerator.On || !Transform(uid).Anchored)
|
||||
return;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(user, component.StartTime, new GeneratorStartedEvent(), uid, uid)
|
||||
{
|
||||
BreakOnDamage = true, BreakOnTargetMove = true, BreakOnUserMove = true, RequireCanInteract = true,
|
||||
NeedHand = true
|
||||
});
|
||||
}
|
||||
|
||||
private void StopGenerator(EntityUid uid, PortableGeneratorComponent component, EntityUid user)
|
||||
{
|
||||
_generator.SetFuelGeneratorOn(uid, false);
|
||||
}
|
||||
|
||||
private void GeneratorTugged(EntityUid uid, PortableGeneratorComponent component, GeneratorStartedEvent args)
|
||||
{
|
||||
if (args.Cancelled || !Transform(uid).Anchored)
|
||||
return;
|
||||
|
||||
var fuelGenerator = Comp<FuelGeneratorComponent>(uid);
|
||||
|
||||
var empty = _generator.GetFuel(uid) == 0;
|
||||
var clogged = _generator.GetIsClogged(uid);
|
||||
|
||||
var sound = empty ? component.StartSoundEmpty : component.StartSound;
|
||||
_audio.Play(sound, Filter.Pvs(uid), uid, true);
|
||||
|
||||
if (!clogged && !empty && _random.Prob(component.StartChance))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("portable-generator-start-success"), uid, args.User);
|
||||
_generator.SetFuelGeneratorOn(uid, true, fuelGenerator);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("portable-generator-start-fail"), uid, args.User);
|
||||
// Try again bozo
|
||||
args.Repeat = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetAlternativeVerb(EntityUid uid, PortableGeneratorComponent component,
|
||||
GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
var fuelGenerator = Comp<FuelGeneratorComponent>(uid);
|
||||
if (fuelGenerator.On)
|
||||
{
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
StopGenerator(uid, component, args.User);
|
||||
},
|
||||
Disabled = false,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")),
|
||||
Text = Loc.GetString("portable-generator-verb-stop"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
var reliable = component.StartChance == 1;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
StartGenerator(uid, component, args.User);
|
||||
},
|
||||
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")),
|
||||
Text = Loc.GetString("portable-generator-verb-start"),
|
||||
};
|
||||
|
||||
if (!Transform(uid).Anchored)
|
||||
{
|
||||
verb.Disabled = true;
|
||||
verb.Message = Loc.GetString("portable-generator-verb-start-msg-unanchored");
|
||||
}
|
||||
else
|
||||
{
|
||||
verb.Message = Loc.GetString(reliable
|
||||
? "portable-generator-verb-start-msg-reliable"
|
||||
: "portable-generator-verb-start-msg-unreliable");
|
||||
}
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<PortableGeneratorComponent, FuelGeneratorComponent, AppearanceComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var portGen, out var fuelGen, out var xform))
|
||||
{
|
||||
UpdateUI(uid, portGen, fuelGen);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUI(EntityUid uid, PortableGeneratorComponent comp, FuelGeneratorComponent fuelComp)
|
||||
{
|
||||
if (!_uiSystem.IsUiOpen(uid, GeneratorComponentUiKey.Key))
|
||||
return;
|
||||
|
||||
var fuel = _generator.GetFuel(uid);
|
||||
var clogged = _generator.GetIsClogged(uid);
|
||||
|
||||
_uiSystem.TrySetUiState(
|
||||
uid,
|
||||
GeneratorComponentUiKey.Key,
|
||||
new PortableGeneratorComponentBuiState(fuelComp, fuel, clogged));
|
||||
}
|
||||
}
|
||||
111
Content.Server/Power/Generator/PowerSwitchableGeneratorSystem.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.Nodes;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Implements power-switchable generators.
|
||||
/// </summary>
|
||||
/// <seealso cref="PowerSwitchableGeneratorComponent"/>
|
||||
/// <seealso cref="PortableGeneratorSystem"/>
|
||||
/// <seealso cref="GeneratorSystem"/>
|
||||
public sealed class PowerSwitchableGeneratorSystem : SharedPowerSwitchableGeneratorSystem
|
||||
{
|
||||
[Dependency] private readonly NodeGroupSystem _nodeGroup = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PowerSwitchableGeneratorComponent, GetVerbsEvent<InteractionVerb>>(GetInteractionVerbs);
|
||||
}
|
||||
|
||||
private void GetInteractionVerbs(
|
||||
EntityUid uid,
|
||||
PowerSwitchableGeneratorComponent component,
|
||||
GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
var isCurrentlyHV = component.ActiveOutput == PowerSwitchableGeneratorOutput.HV;
|
||||
var msg = isCurrentlyHV ? "power-switchable-generator-verb-mv" : "power-switchable-generator-verb-hv";
|
||||
|
||||
var isOn = TryComp(uid, out FuelGeneratorComponent? fuelGenerator) && fuelGenerator.On;
|
||||
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
|
||||
var verbIsOn = TryComp(uid, out FuelGeneratorComponent? verbFuelGenerator) && verbFuelGenerator.On;
|
||||
if (verbIsOn)
|
||||
return;
|
||||
|
||||
ToggleActiveOutput(uid, args.User, component);
|
||||
},
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")),
|
||||
Text = Loc.GetString(msg),
|
||||
};
|
||||
|
||||
if (isOn)
|
||||
{
|
||||
verb.Message = Loc.GetString("power-switchable-generator-verb-disable-on");
|
||||
verb.Disabled = true;
|
||||
}
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
public void ToggleActiveOutput(EntityUid uid, EntityUid user, PowerSwitchableGeneratorComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var supplier = Comp<PowerSupplierComponent>(uid);
|
||||
var nodeContainer = Comp<NodeContainerComponent>(uid);
|
||||
var outputMV = (CableDeviceNode) nodeContainer.Nodes[component.NodeOutputMV];
|
||||
var outputHV = (CableDeviceNode) nodeContainer.Nodes[component.NodeOutputHV];
|
||||
|
||||
if (component.ActiveOutput == PowerSwitchableGeneratorOutput.HV)
|
||||
{
|
||||
component.ActiveOutput = PowerSwitchableGeneratorOutput.MV;
|
||||
supplier.Voltage = Voltage.Medium;
|
||||
|
||||
// Switching around the voltage on the power supplier is "enough",
|
||||
// but we also want to disconnect the cable nodes so it doesn't show up in power monitors etc.
|
||||
outputMV.Enabled = true;
|
||||
outputHV.Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.ActiveOutput = PowerSwitchableGeneratorOutput.HV;
|
||||
supplier.Voltage = Voltage.High;
|
||||
|
||||
outputMV.Enabled = false;
|
||||
outputHV.Enabled = true;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(
|
||||
Loc.GetString("power-switchable-generator-switched-output"),
|
||||
uid,
|
||||
user);
|
||||
|
||||
_audio.Play(component.SwitchSound, Filter.Pvs(uid), uid, true);
|
||||
|
||||
Dirty(uid, component);
|
||||
|
||||
_nodeGroup.QueueReflood(outputMV);
|
||||
_nodeGroup.QueueReflood(outputHV);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,40 @@
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for allowing you to insert fuel into gens.
|
||||
/// Fuels a <see cref="FuelGeneratorComponent"/> through solid materials.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Must be accompanied with a <see cref="MaterialStorageComponent"/> to store the actual material and handle insertion logic.
|
||||
/// You should set a whitelist there for the fuel material.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The component itself stores a "fractional" fuel value to allow stack materials to be gradually consumed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[RegisterComponent, Access(typeof(GeneratorSystem))]
|
||||
public sealed partial class SolidFuelGeneratorAdapterComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The material to accept as fuel.
|
||||
/// </summary>
|
||||
[DataField("fuelMaterial", customTypeSerializer: typeof(PrototypeIdSerializer<MaterialPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("fuelMaterial", customTypeSerializer: typeof(PrototypeIdSerializer<MaterialPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string FuelMaterial = "Plasma";
|
||||
|
||||
/// <summary>
|
||||
/// How much fuel that material should count for.
|
||||
/// How much material (can be fractional) is left in the generator.
|
||||
/// </summary>
|
||||
[DataField("fractionalMaterial"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float FractionalMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// Value to multiply material amount by to get fuel amount.
|
||||
/// </summary>
|
||||
[DataField("multiplier"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Multiplier = 1.0f;
|
||||
public float Multiplier;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Power.NodeGroups
|
||||
{
|
||||
@@ -24,13 +22,12 @@ namespace Content.Server.Power.NodeGroups
|
||||
|
||||
[NodeGroup(NodeGroupID.Apc)]
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ApcNet : BaseNetConnectorNodeGroup<IApcNet>, IApcNet
|
||||
public sealed partial class ApcNet : BasePowerNet<IApcNet>, IApcNet
|
||||
{
|
||||
private PowerNetSystem? _powerNetSystem;
|
||||
|
||||
[ViewVariables] public readonly List<ApcComponent> Apcs = new();
|
||||
[ViewVariables] public readonly List<ApcPowerProviderComponent> Providers = new();
|
||||
[ViewVariables] public readonly List<PowerConsumerComponent> Consumers = new();
|
||||
|
||||
//Debug property
|
||||
[ViewVariables] private int TotalReceivers => Providers.Sum(provider => provider.LinkedReceivers.Count);
|
||||
@@ -39,9 +36,6 @@ namespace Content.Server.Power.NodeGroups
|
||||
private IEnumerable<ApcPowerReceiverComponent> AllReceivers =>
|
||||
Providers.SelectMany(provider => provider.LinkedReceivers);
|
||||
|
||||
[ViewVariables]
|
||||
public PowerState.Network NetworkNode { get; } = new();
|
||||
|
||||
public override void Initialize(Node sourceNode, IEntityManager entMan)
|
||||
{
|
||||
base.Initialize(sourceNode, entMan);
|
||||
@@ -89,21 +83,7 @@ namespace Content.Server.Power.NodeGroups
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void AddConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
consumer.NetworkLoad.LinkedNetwork = default;
|
||||
Consumers.Add(consumer);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void RemoveConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
consumer.NetworkLoad.LinkedNetwork = default;
|
||||
Consumers.Remove(consumer);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void QueueNetworkReconnect()
|
||||
public override void QueueNetworkReconnect()
|
||||
{
|
||||
_powerNetSystem?.QueueReconnectApcNet(this);
|
||||
}
|
||||
|
||||
51
Content.Server/Power/NodeGroups/BasePowerNet.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Power.NodeGroups;
|
||||
|
||||
public abstract class BasePowerNet<TNetType> : BaseNetConnectorNodeGroup<TNetType>, IBasePowerNet
|
||||
where TNetType : IBasePowerNet
|
||||
{
|
||||
[ViewVariables] public readonly List<PowerConsumerComponent> Consumers = new();
|
||||
[ViewVariables] public readonly List<PowerSupplierComponent> Suppliers = new();
|
||||
|
||||
[ViewVariables]
|
||||
public PowerState.Network NetworkNode { get; } = new();
|
||||
|
||||
public void AddConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
DebugTools.Assert(consumer.NetworkLoad.LinkedNetwork == default);
|
||||
consumer.NetworkLoad.LinkedNetwork = default;
|
||||
Consumers.Add(consumer);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void RemoveConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
// Linked network can be default if it was re-connected twice in one tick.
|
||||
DebugTools.Assert(consumer.NetworkLoad.LinkedNetwork == default || consumer.NetworkLoad.LinkedNetwork == NetworkNode.Id);
|
||||
consumer.NetworkLoad.LinkedNetwork = default;
|
||||
Consumers.Remove(consumer);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void AddSupplier(PowerSupplierComponent supplier)
|
||||
{
|
||||
DebugTools.Assert(supplier.NetworkSupply.LinkedNetwork == default);
|
||||
supplier.NetworkSupply.LinkedNetwork = default;
|
||||
Suppliers.Add(supplier);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void RemoveSupplier(PowerSupplierComponent supplier)
|
||||
{
|
||||
// Linked network can be default if it was re-connected twice in one tick.
|
||||
DebugTools.Assert(supplier.NetworkSupply.LinkedNetwork == default || supplier.NetworkSupply.LinkedNetwork == NetworkNode.Id);
|
||||
supplier.NetworkSupply.LinkedNetwork = default;
|
||||
Suppliers.Remove(supplier);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public abstract void QueueNetworkReconnect();
|
||||
}
|
||||
@@ -9,6 +9,10 @@ namespace Content.Server.Power.NodeGroups
|
||||
|
||||
void RemoveConsumer(PowerConsumerComponent consumer);
|
||||
|
||||
void AddSupplier(PowerSupplierComponent supplier);
|
||||
|
||||
void RemoveSupplier(PowerSupplierComponent supplier);
|
||||
|
||||
PowerState.Network NetworkNode { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
@@ -11,10 +10,6 @@ namespace Content.Server.Power.NodeGroups
|
||||
{
|
||||
public interface IPowerNet : IBasePowerNet
|
||||
{
|
||||
void AddSupplier(PowerSupplierComponent supplier);
|
||||
|
||||
void RemoveSupplier(PowerSupplierComponent supplier);
|
||||
|
||||
void AddDischarger(BatteryDischargerComponent discharger);
|
||||
|
||||
void RemoveDischarger(BatteryDischargerComponent discharger);
|
||||
@@ -26,19 +21,14 @@ namespace Content.Server.Power.NodeGroups
|
||||
|
||||
[NodeGroup(NodeGroupID.HVPower, NodeGroupID.MVPower)]
|
||||
[UsedImplicitly]
|
||||
public sealed partial class PowerNet : BaseNetConnectorNodeGroup<IPowerNet>, IPowerNet
|
||||
public sealed partial class PowerNet : BasePowerNet<IPowerNet>, IPowerNet
|
||||
{
|
||||
private PowerNetSystem? _powerNetSystem;
|
||||
private IEntityManager? _entMan;
|
||||
|
||||
[ViewVariables] public readonly List<PowerSupplierComponent> Suppliers = new();
|
||||
[ViewVariables] public readonly List<PowerConsumerComponent> Consumers = new();
|
||||
[ViewVariables] public readonly List<BatteryChargerComponent> Chargers = new();
|
||||
[ViewVariables] public readonly List<BatteryDischargerComponent> Dischargers = new();
|
||||
|
||||
[ViewVariables]
|
||||
public PowerState.Network NetworkNode { get; } = new();
|
||||
|
||||
public override void Initialize(Node sourceNode, IEntityManager entMan)
|
||||
{
|
||||
base.Initialize(sourceNode, entMan);
|
||||
@@ -61,38 +51,6 @@ namespace Content.Server.Power.NodeGroups
|
||||
netConnectorComponent.Net = this;
|
||||
}
|
||||
|
||||
public void AddSupplier(PowerSupplierComponent supplier)
|
||||
{
|
||||
DebugTools.Assert(supplier.NetworkSupply.LinkedNetwork == default);
|
||||
supplier.NetworkSupply.LinkedNetwork = default;
|
||||
Suppliers.Add(supplier);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
}
|
||||
|
||||
public void RemoveSupplier(PowerSupplierComponent supplier)
|
||||
{
|
||||
DebugTools.Assert(supplier.NetworkSupply.LinkedNetwork == NetworkNode.Id);
|
||||
supplier.NetworkSupply.LinkedNetwork = default;
|
||||
Suppliers.Remove(supplier);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
}
|
||||
|
||||
public void AddConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
DebugTools.Assert(consumer.NetworkLoad.LinkedNetwork == default);
|
||||
consumer.NetworkLoad.LinkedNetwork = default;
|
||||
Consumers.Add(consumer);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
}
|
||||
|
||||
public void RemoveConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
DebugTools.Assert(consumer.NetworkLoad.LinkedNetwork == NetworkNode.Id);
|
||||
consumer.NetworkLoad.LinkedNetwork = default;
|
||||
Consumers.Remove(consumer);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
}
|
||||
|
||||
public void AddDischarger(BatteryDischargerComponent discharger)
|
||||
{
|
||||
if (_entMan == null)
|
||||
@@ -102,7 +60,7 @@ namespace Content.Server.Power.NodeGroups
|
||||
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkDischarging == default);
|
||||
battery.NetworkBattery.LinkedNetworkDischarging = default;
|
||||
Dischargers.Add(discharger);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void RemoveDischarger(BatteryDischargerComponent discharger)
|
||||
@@ -113,12 +71,13 @@ namespace Content.Server.Power.NodeGroups
|
||||
// Can be missing if the entity is being deleted, not a big deal.
|
||||
if (_entMan.TryGetComponent(discharger.Owner, out PowerNetworkBatteryComponent? battery))
|
||||
{
|
||||
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkDischarging == NetworkNode.Id);
|
||||
// Linked network can be default if it was re-connected twice in one tick.
|
||||
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkDischarging == default || battery.NetworkBattery.LinkedNetworkDischarging == NetworkNode.Id);
|
||||
battery.NetworkBattery.LinkedNetworkDischarging = default;
|
||||
}
|
||||
|
||||
Dischargers.Remove(discharger);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void AddCharger(BatteryChargerComponent charger)
|
||||
@@ -130,7 +89,7 @@ namespace Content.Server.Power.NodeGroups
|
||||
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkCharging == default);
|
||||
battery.NetworkBattery.LinkedNetworkCharging = default;
|
||||
Chargers.Add(charger);
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public void RemoveCharger(BatteryChargerComponent charger)
|
||||
@@ -141,11 +100,17 @@ namespace Content.Server.Power.NodeGroups
|
||||
// Can be missing if the entity is being deleted, not a big deal.
|
||||
if (_entMan.TryGetComponent(charger.Owner, out PowerNetworkBatteryComponent? battery))
|
||||
{
|
||||
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkCharging == NetworkNode.Id);
|
||||
// Linked network can be default if it was re-connected twice in one tick.
|
||||
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkCharging == default || battery.NetworkBattery.LinkedNetworkCharging == NetworkNode.Id);
|
||||
battery.NetworkBattery.LinkedNetworkCharging = default;
|
||||
}
|
||||
|
||||
Chargers.Remove(charger);
|
||||
QueueNetworkReconnect();
|
||||
}
|
||||
|
||||
public override void QueueNetworkReconnect()
|
||||
{
|
||||
_powerNetSystem?.QueueReconnectPowerNet(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Power.Nodes
|
||||
@@ -12,6 +12,24 @@ namespace Content.Server.Power.Nodes
|
||||
[Virtual]
|
||||
public partial class CableDeviceNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// If disabled, this cable device will never connect.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you change this,
|
||||
/// you must manually call <see cref="NodeGroupSystem.QueueReflood"/> to update the node connections.
|
||||
/// </remarks>
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public override bool Connectable(IEntityManager entMan, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Enabled)
|
||||
return false;
|
||||
|
||||
return base.Connectable(entMan, xform);
|
||||
}
|
||||
|
||||
public override IEnumerable<Node> GetReachableNodes(TransformComponent xform,
|
||||
EntityQuery<NodeContainerComponent> nodeQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.FixedPoint
|
||||
{
|
||||
@@ -13,14 +14,23 @@ namespace Content.Shared.FixedPoint
|
||||
{
|
||||
public int Value { get; private set; }
|
||||
private const int Shift = 2;
|
||||
private const int ShiftConstant = 100; // Must be equal to pow(10, Shift)
|
||||
|
||||
public static FixedPoint2 MaxValue { get; } = new(int.MaxValue);
|
||||
public static FixedPoint2 Epsilon { get; } = new(1);
|
||||
public static FixedPoint2 Zero { get; } = new(0);
|
||||
|
||||
#if DEBUG
|
||||
static FixedPoint2()
|
||||
{
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
DebugTools.Assert(Math.Pow(10, Shift) == ShiftConstant, "ShiftConstant must be equal to pow(10, Shift)");
|
||||
}
|
||||
#endif
|
||||
|
||||
private readonly double ShiftDown()
|
||||
{
|
||||
return Value / Math.Pow(10, Shift);
|
||||
return Value / (double) ShiftConstant;
|
||||
}
|
||||
|
||||
private FixedPoint2(int value)
|
||||
@@ -30,24 +40,27 @@ namespace Content.Shared.FixedPoint
|
||||
|
||||
public static FixedPoint2 New(int value)
|
||||
{
|
||||
return new(value * (int) Math.Pow(10, Shift));
|
||||
return new(value * ShiftConstant);
|
||||
}
|
||||
|
||||
public static FixedPoint2 FromCents(int value) => new(value);
|
||||
|
||||
public static FixedPoint2 New(float value)
|
||||
{
|
||||
return new(FromFloat(value));
|
||||
return new((int) MathF.Round(value * ShiftConstant, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
private static int FromFloat(float value)
|
||||
/// <summary>
|
||||
/// Create the closest <see cref="FixedPoint2"/> for a float value, always rounding up.
|
||||
/// </summary>
|
||||
public static FixedPoint2 NewCeiling(float value)
|
||||
{
|
||||
return (int) MathF.Round(value * MathF.Pow(10, Shift), MidpointRounding.AwayFromZero);
|
||||
return new((int) MathF.Ceiling(value * ShiftConstant));
|
||||
}
|
||||
|
||||
public static FixedPoint2 New(double value)
|
||||
{
|
||||
return new((int) Math.Round(value * Math.Pow(10, Shift), MidpointRounding.AwayFromZero));
|
||||
return new((int) Math.Round(value * ShiftConstant, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
public static FixedPoint2 New(string value)
|
||||
@@ -72,7 +85,7 @@ namespace Content.Shared.FixedPoint
|
||||
|
||||
public static FixedPoint2 operator *(FixedPoint2 a, FixedPoint2 b)
|
||||
{
|
||||
return new((int) MathF.Round(b.Value * a.Value / MathF.Pow(10, Shift), MidpointRounding.AwayFromZero));
|
||||
return new((int) MathF.Round(b.Value * a.Value / (float) ShiftConstant, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
public static FixedPoint2 operator *(FixedPoint2 a, float b)
|
||||
@@ -92,7 +105,7 @@ namespace Content.Shared.FixedPoint
|
||||
|
||||
public static FixedPoint2 operator /(FixedPoint2 a, FixedPoint2 b)
|
||||
{
|
||||
return new((int) MathF.Round((MathF.Pow(10, Shift) * a.Value) / b.Value, MidpointRounding.AwayFromZero));
|
||||
return new((int) MathF.Round((ShiftConstant * a.Value) / (float) b.Value, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
public static FixedPoint2 operator /(FixedPoint2 a, float b)
|
||||
@@ -253,7 +266,7 @@ namespace Content.Shared.FixedPoint
|
||||
if (value == "MaxValue")
|
||||
Value = int.MaxValue;
|
||||
else
|
||||
Value = FromFloat(FloatFromString(value));
|
||||
this = New(FloatFromString(value));
|
||||
}
|
||||
|
||||
public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
|
||||
|
||||
@@ -183,6 +183,29 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to set the amount of material in the storage to a specific value.
|
||||
/// Still respects the filters in place.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity to change the material storage on.</param>
|
||||
/// <param name="materialId">The ID of the material to change.</param>
|
||||
/// <param name="volume">The stored material volume to set the storage to.</param>
|
||||
/// <param name="component">The storage component on <paramref name="uid"/>. Resolved automatically if not given.</param>
|
||||
/// <returns>True if it was successful (enough space etc).</returns>
|
||||
public bool TrySetMaterialAmount(
|
||||
EntityUid uid,
|
||||
string materialId,
|
||||
int volume,
|
||||
MaterialStorageComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
var curAmount = GetMaterialAmount(uid, materialId, component);
|
||||
var delta = volume - curAmount;
|
||||
return TryChangeMaterialAmount(uid, materialId, delta, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert an entity into the material storage.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for generators that run off some kind of fuel.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedGeneratorSystem))]
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Generators must be anchored to be able to run.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="SharedGeneratorSystem"/>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedGeneratorSystem))]
|
||||
public sealed partial class FuelGeneratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of fuel left in the generator.
|
||||
/// Is the generator currently running?
|
||||
/// </summary>
|
||||
[DataField("remainingFuel"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RemainingFuel;
|
||||
[DataField("on"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public bool On;
|
||||
|
||||
/// <summary>
|
||||
/// The generator's target power.
|
||||
@@ -27,6 +32,15 @@ public sealed partial class FuelGeneratorComponent : Component
|
||||
[DataField("maxTargetPower"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxTargetPower = 30_000.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum target power.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to any value above 0 means that the generator can't idle without consuming some amount of fuel.
|
||||
/// </remarks>
|
||||
[DataField("minTargetPower"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MinTargetPower = 1_000;
|
||||
|
||||
/// <summary>
|
||||
/// The "optimal" power at which the generator is considered to be at 100% efficiency.
|
||||
/// </summary>
|
||||
@@ -45,43 +59,3 @@ public sealed partial class FuelGeneratorComponent : Component
|
||||
[DataField("fuelEfficiencyConstant")]
|
||||
public float FuelEfficiencyConstant = 1.3f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to adjust the targeted power level.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SetTargetPowerMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public int TargetPower;
|
||||
|
||||
public SetTargetPowerMessage(int targetPower)
|
||||
{
|
||||
TargetPower = targetPower;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains network state for FuelGeneratorComponent.
|
||||
/// </summary>
|
||||
[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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Makes a generator emit a gas into the atmosphere when running.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The amount of gas produced is linear with the amount of fuel used.
|
||||
/// </remarks>
|
||||
/// <seealso cref="SharedGeneratorSystem"/>
|
||||
/// <seealso cref="FuelGeneratorComponent"/>
|
||||
[RegisterComponent]
|
||||
public sealed partial class GeneratorExhaustGasComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of gas that will be emitted by the generator.
|
||||
/// </summary>
|
||||
[DataField("gasType"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public Gas GasType = Gas.CarbonDioxide;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of moles of gas that should be produced when one unit of fuel is burned.
|
||||
/// </summary>
|
||||
[DataField("moleRatio"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MoleRatio = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The temperature of created gas.
|
||||
/// </summary>
|
||||
[DataField("temperature"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Temperature = Atmospherics.T0C + 100;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Enables a generator to switch between HV and MV output.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must have <c>CableDeviceNode</c>s for both <see cref="NodeOutputMV"/> and <see cref="NodeOutputHV"/>, and also a <c>PowerSupplierComponent</c>.
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedPowerSwitchableGeneratorSystem))]
|
||||
public sealed partial class PowerSwitchableGeneratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Which output the portable generator is currently connected to.
|
||||
/// </summary>
|
||||
[DataField("activeOutput")]
|
||||
[AutoNetworkedField]
|
||||
public PowerSwitchableGeneratorOutput ActiveOutput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when the output is switched.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DataField("switchSound")]
|
||||
public SoundSpecifier? SwitchSound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which node is the MV output?
|
||||
/// </summary>
|
||||
[DataField("nodeOutputMV")]
|
||||
public string NodeOutputMV { get; set; } = "output_mv";
|
||||
|
||||
/// <summary>
|
||||
/// Which node is the HV output?
|
||||
/// </summary>
|
||||
[DataField("nodeOutputHV")]
|
||||
public string NodeOutputHV { get; set; } = "output_hv";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible power output for power-switchable generators.
|
||||
/// </summary>
|
||||
/// <seealso cref="PowerSwitchableGeneratorComponent"/>
|
||||
[Serializable, NetSerializable]
|
||||
public enum PowerSwitchableGeneratorOutput : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The generator is set to connect to a high-voltage power network.
|
||||
/// </summary>
|
||||
HV,
|
||||
|
||||
/// <summary>
|
||||
/// The generator is set to connect to a medium-voltage power network.
|
||||
/// </summary>
|
||||
MV
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/// <summary>
|
||||
/// This handles small, portable generators that run off a material fuel.
|
||||
/// </summary>
|
||||
/// <seealso cref="FuelGeneratorComponent"/>
|
||||
public abstract class SharedGeneratorSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for power output switching & UI logic on portable generators.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A portable generator is expected to have the following components: <c>SolidFuelGeneratorAdapterComponent</c> <see cref="FuelGeneratorComponent"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="SharedPortableGeneratorSystem"/>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SharedPortableGeneratorSystem))]
|
||||
public sealed partial class PortableGeneratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Chance that this generator will start. If it fails, the user has to try again.
|
||||
/// </summary>
|
||||
[DataField("startChance")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float StartChance { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of time it takes to attempt to start the generator.
|
||||
/// </summary>
|
||||
[DataField("startTime")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan StartTime { get; set; } = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when attempting to start this generator.
|
||||
/// </summary>
|
||||
[DataField("startSound")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier? StartSound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when attempting to start this generator.
|
||||
/// Plays instead of <see cref="StartSound"/> if the generator has no fuel (dumbass).
|
||||
/// </summary>
|
||||
[DataField("startSoundEmpty")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier? StartSoundEmpty { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to adjust the targeted power level of a portable generator.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PortableGeneratorSetTargetPowerMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public int TargetPower;
|
||||
|
||||
public PortableGeneratorSetTargetPowerMessage(int targetPower)
|
||||
{
|
||||
TargetPower = targetPower;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to try to start a portable generator.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PortableGeneratorStartMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to try to stop a portable generator.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PortableGeneratorStopMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to try to change the power output of a power-switchable portable generator.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PortableGeneratorSwitchOutputMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to try to eject all fuel stored in a portable generator.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PortableGeneratorEjectFuelMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains network state for the portable generator.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PortableGeneratorComponentBuiState : BoundUserInterfaceState
|
||||
{
|
||||
public float RemainingFuel;
|
||||
public bool Clogged;
|
||||
public float TargetPower;
|
||||
public float MaximumPower;
|
||||
public float OptimalPower;
|
||||
public bool On;
|
||||
|
||||
public PortableGeneratorComponentBuiState(FuelGeneratorComponent component, float remainingFuel, bool clogged)
|
||||
{
|
||||
RemainingFuel = remainingFuel;
|
||||
Clogged = clogged;
|
||||
TargetPower = component.TargetPower;
|
||||
MaximumPower = component.MaxTargetPower;
|
||||
OptimalPower = component.OptimalPower;
|
||||
On = component.On;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum GeneratorComponentUiKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sprite layers for generator prototypes.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum GeneratorVisualLayers : byte
|
||||
{
|
||||
Body,
|
||||
Unlit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appearance keys for generators.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum GeneratorVisuals : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean: is the generator running?
|
||||
/// </summary>
|
||||
Running,
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Shared logic for portable generators.
|
||||
/// </summary>
|
||||
/// <seealso cref="PortableGeneratorComponent"/>
|
||||
public abstract class SharedPortableGeneratorSystem : EntitySystem
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to start a portable generator.
|
||||
/// </summary>
|
||||
/// <seealso cref="SharedPortableGeneratorSystem"/>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class GeneratorStartedEvent : DoAfterEvent
|
||||
{
|
||||
public override DoAfterEvent Clone()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Examine;
|
||||
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Shared logic for power-switchable generators.
|
||||
/// </summary>
|
||||
/// <seealso cref="PowerSwitchableGeneratorComponent"/>
|
||||
public abstract class SharedPowerSwitchableGeneratorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PowerSwitchableGeneratorComponent, ExaminedEvent>(GeneratorExamined);
|
||||
}
|
||||
|
||||
private void GeneratorExamined(EntityUid uid, PowerSwitchableGeneratorComponent component, ExaminedEvent args)
|
||||
{
|
||||
// Show which output is currently selected.
|
||||
args.PushMarkup(Loc.GetString(
|
||||
"power-switchable-generator-examine",
|
||||
("output", component.ActiveOutput.ToString())));
|
||||
}
|
||||
}
|
||||
@@ -175,5 +175,15 @@ namespace Content.Tests.Shared.Chemistry
|
||||
Assert.That(parameter.Equals(comparison), Is.EqualTo(comparison.Equals(parameter)));
|
||||
Assert.That(comparison.Equals(parameter), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(1.001f, "1.01")]
|
||||
[TestCase(2f, "2")]
|
||||
[TestCase(2.5f, "2.5")]
|
||||
public void NewCeilingTest(float value, string expected)
|
||||
{
|
||||
var result = FixedPoint2.NewCeiling(value);
|
||||
Assert.That($"{result}", Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,3 +32,8 @@
|
||||
license: "CC0-1.0"
|
||||
copyright: "Created by BasedUser#2215 on discord"
|
||||
source: "https://discord.com/channels/310555209753690112/536955542913024015/1066824680188690452"
|
||||
|
||||
- files: ["generator-tug-1.ogg", "generator-tug-1-empty.ogg", "generator-tug-2.ogg", "generator-tug-2-empty.ogg", "generator-tug-3.ogg", "generator-tug-3-empty.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Modified from https://freesound.org/people/Pagey1969/sounds/566048/"
|
||||
source: "https://github.com/space-wizards/ss14-raw-assets/tree/99fcffb02d9953f031991fc8f9980f0c3abd3803/Audio"
|
||||
|
||||
BIN
Resources/Audio/Machines/generator-tug-1-empty.ogg
Normal file
BIN
Resources/Audio/Machines/generator-tug-1.ogg
Normal file
BIN
Resources/Audio/Machines/generator-tug-2-empty.ogg
Normal file
BIN
Resources/Audio/Machines/generator-tug-2.ogg
Normal file
BIN
Resources/Audio/Machines/generator-tug-3-empty.ogg
Normal file
BIN
Resources/Audio/Machines/generator-tug-3.ogg
Normal file
@@ -9,6 +9,7 @@ guide-entry-networking = Networking
|
||||
guide-entry-network-configurator = Network Configurator
|
||||
guide-entry-access-configurator = Access Configurator
|
||||
guide-entry-power = Power
|
||||
guide-entry-portable-generator = Portable Generators
|
||||
guide-entry-ame = Antimatter Engine (AME)
|
||||
guide-entry-singularity = Singularity
|
||||
guide-entry-teg = Thermo-electric Generator (TEG)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
node-container-component-on-examine-details-hvpower = It has a connector for [color=orange]HV cables[/color].
|
||||
node-container-component-on-examine-details-mvpower = It has a connector for [color=yellow]MV cables[/color].
|
||||
node-container-component-on-examine-details-apc = It has a connector for [color=green]APC cables[/color].
|
||||
node-container-component-on-examine-details-apc = It has a connector for [color=green]LV cables[/color].
|
||||
|
||||
@@ -1,7 +1,40 @@
|
||||
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-clogged = {THE($generator)} shuts off abruptly!
|
||||
|
||||
generator-insert-material = Inserted {THE($item)} into {THE($generator)}...
|
||||
portable-generator-verb-start = Start generator
|
||||
portable-generator-verb-start-msg-unreliable = Start the generator. This may take a few tries.
|
||||
portable-generator-verb-start-msg-reliable = Start the generator.
|
||||
portable-generator-verb-start-msg-unanchored = The generator must be anchored first!
|
||||
portable-generator-verb-stop = Stop generator
|
||||
portable-generator-start-fail = You tug the cord, but it didn't start.
|
||||
portable-generator-start-success = You tug the cord, and it whirrs to life.
|
||||
|
||||
portable-generator-ui-title = Portable Generator
|
||||
portable-generator-ui-status-stopped = Stopped:
|
||||
portable-generator-ui-status-starting = Starting:
|
||||
portable-generator-ui-status-running = Running:
|
||||
portable-generator-ui-start = Start
|
||||
portable-generator-ui-stop = Stop
|
||||
portable-generator-ui-target-power-label = Target Power (kW):
|
||||
portable-generator-ui-efficiency-label = Efficiency:
|
||||
portable-generator-ui-fuel-use-label = Fuel use:
|
||||
portable-generator-ui-fuel-left-label = Fuel left:
|
||||
portable-generator-ui-clogged = Contaminants detected in fuel tank!
|
||||
portable-generator-ui-eject = Eject
|
||||
portable-generator-ui-eta = (~{ $minutes } min)
|
||||
portable-generator-ui-unanchored = Unanchored
|
||||
|
||||
power-switchable-generator-examine = The power output is set to { $output ->
|
||||
[HV] [color=orange]HV[/color]
|
||||
*[MV] [color=yellow]MV[/color]
|
||||
}.
|
||||
|
||||
portable-generator-ui-switch-hv = Current output: HV
|
||||
portable-generator-ui-switch-mv = Current output: MV
|
||||
|
||||
portable-generator-ui-switch-to-hv = Switch to HV
|
||||
portable-generator-ui-switch-to-mv = Switch to MV
|
||||
|
||||
power-switchable-generator-verb-hv = Switch output to HV
|
||||
power-switchable-generator-verb-mv = Switch output to MV
|
||||
power-switchable-generator-verb-disable-on = Turn the generator off first!
|
||||
power-switchable-generator-switched-output = Output switched!
|
||||
|
||||
@@ -599,14 +599,14 @@
|
||||
ExamineName: Woodwind Instrument
|
||||
|
||||
- type: entity
|
||||
id: GeneratorPlasmaMachineCircuitboard
|
||||
id: PortableGeneratorPacmanMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
name: generator (plasma) machine board
|
||||
name: P.A.C.M.A.N.-type portable generator machine board
|
||||
components:
|
||||
- type: Sprite
|
||||
state: engineering
|
||||
- type: MachineBoard
|
||||
prototype: GeneratorPlasma
|
||||
prototype: PortableGeneratorPacman
|
||||
requirements:
|
||||
Capacitor: 1
|
||||
materialRequirements:
|
||||
@@ -643,14 +643,14 @@
|
||||
Glass: 2
|
||||
|
||||
- type: entity
|
||||
id: GeneratorUraniumMachineCircuitboard
|
||||
id: PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
name: generator (uranium) machine board
|
||||
name: S.U.P.E.R.P.A.C.M.A.N.-type portable generator machine board
|
||||
components:
|
||||
- type: Sprite
|
||||
state: engineering
|
||||
- type: MachineBoard
|
||||
prototype: GeneratorUranium
|
||||
prototype: PortableGeneratorSuperPacman
|
||||
requirements:
|
||||
Capacitor: 2
|
||||
materialRequirements:
|
||||
@@ -661,6 +661,25 @@
|
||||
chemicalComposition:
|
||||
Silicon: 20
|
||||
|
||||
- type: entity
|
||||
id: PortableGeneratorJrPacmanMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
name: J.R.P.A.C.M.A.N.-type portable generator machine board
|
||||
components:
|
||||
- type: Sprite
|
||||
state: engineering
|
||||
- type: MachineBoard
|
||||
prototype: PortableGeneratorJrPacman
|
||||
requirements:
|
||||
Capacitor: 1
|
||||
materialRequirements:
|
||||
Cable: 10
|
||||
- type: PhysicalComposition
|
||||
materialComposition:
|
||||
Glass: 200
|
||||
chemicalComposition:
|
||||
Silicon: 20
|
||||
|
||||
- type: entity
|
||||
id: ReagentGrinderMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
|
||||
@@ -340,8 +340,9 @@
|
||||
- HonkerTargetingElectronics
|
||||
- HamtrCentralElectronics
|
||||
- HamtrPeripheralsElectronics
|
||||
- GeneratorPlasmaMachineCircuitboard
|
||||
- GeneratorUraniumMachineCircuitboard
|
||||
- PortableGeneratorPacmanMachineCircuitboard
|
||||
- PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
- PortableGeneratorJrPacmanMachineCircuitboard
|
||||
- WallmountGeneratorElectronics
|
||||
- WallmountGeneratorAPUElectronics
|
||||
- WallmountSubstationElectronics
|
||||
|
||||
@@ -175,67 +175,6 @@
|
||||
- type: PowerSupplier
|
||||
supplyRate: 15000
|
||||
|
||||
- type: entity
|
||||
parent: [ BaseGenerator, ConstructibleMachine ]
|
||||
id: GeneratorPlasma
|
||||
suffix: Plasma, 20kW
|
||||
components:
|
||||
- type: PowerSupplier
|
||||
- type: Sprite
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
state: portgen0_1
|
||||
- type: WiresPanel
|
||||
- type: Wires
|
||||
BoardName: "GeneratorPlasma"
|
||||
LayoutId: GeneratorPlasma
|
||||
- type: Machine
|
||||
board: GeneratorPlasmaMachineCircuitboard
|
||||
- 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, 30kW
|
||||
components:
|
||||
- type: PowerSupplier
|
||||
- type: Sprite
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
state: portgen1_1
|
||||
- type: WiresPanel
|
||||
- type: Wires
|
||||
BoardName: "GeneratorUranium"
|
||||
LayoutId: GeneratorUranium
|
||||
- type: Machine
|
||||
board: GeneratorUraniumMachineCircuitboard
|
||||
- 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
|
||||
id: GeneratorWallmountBasic
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
#
|
||||
# You can use this Desmos sheet to calculate fuel burn rate values:
|
||||
# https://www.desmos.com/calculator/qcektq5dqs
|
||||
#
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: PortableGeneratorBase
|
||||
parent: [ BaseMachine, ConstructibleMachine ]
|
||||
components:
|
||||
# Basic properties
|
||||
- type: Transform
|
||||
anchored: False
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: StaticPrice
|
||||
price: 500
|
||||
- type: AmbientSound
|
||||
range: 5
|
||||
volume: -5
|
||||
sound:
|
||||
path: /Audio/Ambience/Objects/engine_hum.ogg
|
||||
enabled: false
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.40,-0.40,0.40,0.40"
|
||||
# It has wheels
|
||||
density: 45
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- MachineLayer
|
||||
|
||||
# Visuals
|
||||
- type: Appearance
|
||||
- type: Sprite
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
|
||||
# Construction, interaction
|
||||
- type: WiresPanel
|
||||
- type: Wires
|
||||
BoardName: "Generator"
|
||||
LayoutId: Generator
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.GeneratorComponentUiKey.Key
|
||||
type: PortableGeneratorBoundUserInterface
|
||||
- type: ActivatableUI
|
||||
key: enum.GeneratorComponentUiKey.Key
|
||||
- type: Electrified
|
||||
onHandInteract: false
|
||||
onInteractUsing: false
|
||||
onBump: false
|
||||
requirePower: true
|
||||
highVoltageNode: output
|
||||
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 200
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
path: /Audio/Effects/metalbreak.ogg
|
||||
- !type:ChangeConstructionNodeBehavior
|
||||
node: machineFrame
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- type: GuideHelp
|
||||
guides: [ PortableGenerator, Power ]
|
||||
|
||||
# Core functionality
|
||||
- type: PortableGenerator
|
||||
startSoundEmpty: { collection: GeneratorTugEmpty }
|
||||
startSound: { collection: GeneratorTug }
|
||||
- type: FuelGenerator
|
||||
- type: PowerSupplier
|
||||
supplyRate: 3000
|
||||
supplyRampRate: 500
|
||||
supplyRampTolerance: 500
|
||||
enabled: false
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: PortableGeneratorBase
|
||||
id: PortableGeneratorSwitchableBase
|
||||
components:
|
||||
- type: PowerSwitchableGenerator
|
||||
switchSound:
|
||||
path: /Audio/Machines/button.ogg
|
||||
- type: NodeContainer
|
||||
examinable: true
|
||||
nodes:
|
||||
output_hv:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
output_mv:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: MVPower
|
||||
enabled: false
|
||||
|
||||
- type: entity
|
||||
name: P.A.C.M.A.N.-type portable generator
|
||||
description: |-
|
||||
A flexible backup generator for powering a variety of equipment.
|
||||
Runs off solid plasma sheets and is rated for up to 30 kW.
|
||||
parent: PortableGeneratorSwitchableBase
|
||||
id: PortableGeneratorPacman
|
||||
suffix: Plasma, 30 kW
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: portgen0
|
||||
map: [ "enum.GeneratorVisualLayers.Body" ]
|
||||
- state: portgen_on_unlit
|
||||
map: [ "enum.GeneratorVisualLayers.Unlit" ]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.GeneratorVisuals.Running:
|
||||
enum.GeneratorVisualLayers.Body:
|
||||
True: { state: portgen0on }
|
||||
False: { state: portgen0 }
|
||||
enum.GeneratorVisualLayers.Unlit:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
|
||||
- type: Machine
|
||||
board: PortableGeneratorPacmanMachineCircuitboard
|
||||
- type: FuelGenerator
|
||||
minTargetPower: 5000
|
||||
maxTargetPower: 30000
|
||||
targetPower: 30000
|
||||
optimalPower: 30000
|
||||
# 15 minutes at max output
|
||||
optimalBurnRate: 0.0333333
|
||||
# a decent curve that goes up to about an hour at 5 kW.
|
||||
fuelEfficiencyConstant: 0.75
|
||||
- type: SolidFuelGeneratorAdapter
|
||||
fuelMaterial: Plasma
|
||||
multiplier: 0.01
|
||||
- type: MaterialStorage
|
||||
storageLimit: 3000
|
||||
materialWhiteList: [Plasma]
|
||||
- type: PortableGenerator
|
||||
startChance: 0.8
|
||||
- type: UpgradePowerSupplier
|
||||
powerSupplyMultiplier: 1.25
|
||||
scaling: Exponential
|
||||
- type: GeneratorExhaustGas
|
||||
gasType: CarbonDioxide
|
||||
# 2 moles of gas for every sheet of plasma.
|
||||
moleRatio: 2
|
||||
|
||||
- type: entity
|
||||
name: S.U.P.E.R.P.A.C.M.A.N.-type portable generator
|
||||
description: |-
|
||||
An advanced generator for powering departments.
|
||||
Runs off uranium sheets and is rated for up to 50 kW.
|
||||
parent: PortableGeneratorSwitchableBase
|
||||
id: PortableGeneratorSuperPacman
|
||||
suffix: Uranium, 50 kW
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: portgen1
|
||||
map: [ "enum.GeneratorVisualLayers.Body" ]
|
||||
- state: portgen_on_unlit
|
||||
map: [ "enum.GeneratorVisualLayers.Unlit" ]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.GeneratorVisuals.Running:
|
||||
enum.GeneratorVisualLayers.Body:
|
||||
True: { state: portgen1on }
|
||||
False: { state: portgen1 }
|
||||
enum.GeneratorVisualLayers.Unlit:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
|
||||
- type: Machine
|
||||
board: PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
- type: FuelGenerator
|
||||
minTargetPower: 10000
|
||||
maxTargetPower: 50000
|
||||
targetPower: 50000
|
||||
optimalPower: 50000
|
||||
# 30 minutes at full power
|
||||
optimalBurnRate: 0.016666666
|
||||
# Barely save any fuel from reducing power output
|
||||
fuelEfficiencyConstant: 0.1
|
||||
- type: SolidFuelGeneratorAdapter
|
||||
fuelMaterial: Uranium
|
||||
multiplier: 0.01
|
||||
- type: MaterialStorage
|
||||
storageLimit: 3000
|
||||
materialWhiteList: [Uranium]
|
||||
- type: PortableGenerator
|
||||
- type: UpgradePowerSupplier
|
||||
powerSupplyMultiplier: 1.25
|
||||
scaling: Exponential
|
||||
|
||||
- type: entity
|
||||
name: J.R.P.A.C.M.A.N.-type portable generator
|
||||
description: |-
|
||||
A small generator capable of powering individual rooms, in case of emergencies.
|
||||
Runs off welding fuel and is rated for up to 5 kW.
|
||||
Rated ages 3 and up.
|
||||
parent: PortableGeneratorBase
|
||||
id: PortableGeneratorJrPacman
|
||||
suffix: Welding Fuel, 5 kW
|
||||
components:
|
||||
- type: AmbientSound
|
||||
range: 4
|
||||
volume: -8
|
||||
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.30,-0.30,0.30,0.30"
|
||||
# It has wheels
|
||||
density: 30
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- MachineLayer
|
||||
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: portgen3
|
||||
map: [ "enum.GeneratorVisualLayers.Body" ]
|
||||
- state: portgen3on_unlit
|
||||
map: [ "enum.GeneratorVisualLayers.Unlit" ]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.GeneratorVisuals.Running:
|
||||
enum.GeneratorVisualLayers.Body:
|
||||
True: { state: portgen3on }
|
||||
False: { state: portgen3 }
|
||||
enum.GeneratorVisualLayers.Unlit:
|
||||
True: { visible: true }
|
||||
False: { visible: false }
|
||||
|
||||
- type: Machine
|
||||
board: PortableGeneratorJrPacmanMachineCircuitboard
|
||||
- type: FuelGenerator
|
||||
targetPower: 2000
|
||||
minTargetPower: 1000
|
||||
optimalPower: 5000
|
||||
maxTargetPower: 5000
|
||||
# 7.5 minutes at full tank.
|
||||
optimalBurnRate: 0.11111111
|
||||
# Shallow curve that allows you to just barely eek out 12 minutes at lowest.
|
||||
fuelEfficiencyConstant: 0.3
|
||||
- type: ChemicalFuelGeneratorAdapter
|
||||
solution: tank
|
||||
reagent: WeldingFuel
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
tank:
|
||||
maxVol: 50
|
||||
- type: RefillableSolution
|
||||
solution: tank
|
||||
- type: PortableGenerator
|
||||
# Unreliable bugger
|
||||
startChance: 0.5
|
||||
- type: NodeContainer
|
||||
examinable: true
|
||||
nodes:
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: Apc
|
||||
- type: PowerSupplier
|
||||
# No ramping needed on this bugger.
|
||||
voltage: Apc
|
||||
supplyRampTolerance: 2000
|
||||
- type: GeneratorExhaustGas
|
||||
gasType: CarbonDioxide
|
||||
# Full tank is 25 moles of gas
|
||||
moleRatio: 0.5
|
||||
- type: Explosive
|
||||
explosionType: Default
|
||||
tileBreakScale: 0
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 200
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:SpillBehavior
|
||||
solution: tank
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
path: /Audio/Effects/metalbreak.ogg
|
||||
- !type:ChangeConstructionNodeBehavior
|
||||
node: machineFrame
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- trigger:
|
||||
!type:DamageTypeTrigger
|
||||
damageType: Piercing
|
||||
damage: 75
|
||||
behaviors:
|
||||
- !type:SolutionExplosionBehavior
|
||||
solution: tank
|
||||
@@ -61,6 +61,7 @@
|
||||
name: guide-entry-power
|
||||
text: "/ServerInfo/Guidebook/Engineering/Power.xml"
|
||||
children:
|
||||
- PortableGenerator
|
||||
- AME
|
||||
- Singularity
|
||||
- TEG
|
||||
@@ -79,3 +80,8 @@
|
||||
id: TEG
|
||||
name: guide-entry-teg
|
||||
text: "/ServerInfo/Guidebook/Engineering/TEG.xml"
|
||||
|
||||
- type: guideEntry
|
||||
id: PortableGenerator
|
||||
name: guide-entry-portable-generator
|
||||
text: "/ServerInfo/Guidebook/Engineering/PortableGenerator.xml"
|
||||
|
||||
@@ -438,16 +438,24 @@
|
||||
Glass: 900
|
||||
|
||||
- type: latheRecipe
|
||||
id: GeneratorPlasmaMachineCircuitboard
|
||||
result: GeneratorPlasmaMachineCircuitboard
|
||||
id: PortableGeneratorPacmanMachineCircuitboard
|
||||
result: PortableGeneratorPacmanMachineCircuitboard
|
||||
completetime: 4
|
||||
materials:
|
||||
Steel: 50
|
||||
Glass: 350
|
||||
|
||||
- type: latheRecipe
|
||||
id: GeneratorUraniumMachineCircuitboard
|
||||
result: GeneratorUraniumMachineCircuitboard
|
||||
id: PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
result: PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
completetime: 4
|
||||
materials:
|
||||
Steel: 50
|
||||
Glass: 350
|
||||
|
||||
- type: latheRecipe
|
||||
id: PortableGeneratorJrPacmanMachineCircuitboard
|
||||
result: PortableGeneratorJrPacmanMachineCircuitboard
|
||||
completetime: 4
|
||||
materials:
|
||||
Steel: 50
|
||||
|
||||
@@ -60,13 +60,14 @@
|
||||
name: research-technology-power-generation
|
||||
icon:
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
state: portgen0_1
|
||||
state: portgen0
|
||||
discipline: Industrial
|
||||
tier: 1
|
||||
cost: 7500
|
||||
recipeUnlocks:
|
||||
- GeneratorPlasmaMachineCircuitboard
|
||||
- GeneratorUraniumMachineCircuitboard
|
||||
- PortableGeneratorPacmanMachineCircuitboard
|
||||
- PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
- PortableGeneratorJrPacmanMachineCircuitboard
|
||||
- PowerComputerCircuitboard #the actual solar panel itself should be in here
|
||||
- SolarControlComputerCircuitboard
|
||||
- SolarTrackerElectronics
|
||||
|
||||
15
Resources/Prototypes/SoundCollections/generator.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
- type: soundCollection
|
||||
# Plays when you try to start a generator.
|
||||
# You know, lawnmower cord pull sound.
|
||||
id: GeneratorTug
|
||||
files:
|
||||
- /Audio/Machines/generator-tug-1.ogg
|
||||
- /Audio/Machines/generator-tug-2.ogg
|
||||
- /Audio/Machines/generator-tug-3.ogg
|
||||
|
||||
- type: soundCollection
|
||||
id: GeneratorTugEmpty
|
||||
files:
|
||||
- /Audio/Machines/generator-tug-1-empty.ogg
|
||||
- /Audio/Machines/generator-tug-2-empty.ogg
|
||||
- /Audio/Machines/generator-tug-3-empty.ogg
|
||||
@@ -0,0 +1,42 @@
|
||||
<Document>
|
||||
# Portable Generators
|
||||
|
||||
Need power? No engine running? The "P.A.C.M.A.N." line of portable generators has you covered.
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="PortableGeneratorJrPacman" Caption="J.R.P.A.C.M.A.N." />
|
||||
<GuideEntityEmbed Entity="PortableGeneratorPacman" Caption="P.A.C.M.A.N." />
|
||||
<GuideEntityEmbed Entity="PortableGeneratorSuperPacman" Caption="S.U.P.E.R.P.A.C.M.A.N." />
|
||||
</Box>
|
||||
|
||||
# The Junior
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="PortableGeneratorJrPacman" Caption="J.R.P.A.C.M.A.N." />
|
||||
<GuideEntityEmbed Entity="WeldingFuelTank" />
|
||||
</Box>
|
||||
|
||||
The J.R.P.A.C.M.A.N. can be found across the station in maintenance areas, and is ideal for crew to set up themselves whenever there are power issues.
|
||||
Setup is incredibly easy: wrench it down above an [color=green]LV[/color] power cable, give it some welding fuel, and start it up.
|
||||
|
||||
Welding fuel should be plentiful to find around the station. In a pinch, you can even transfer some from the big tanks with a soda can. Just remember to empty the soda can first, I don't think it likes soda as fuel.
|
||||
|
||||
# The Big Ones
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="PortableGeneratorPacman" Caption="P.A.C.M.A.N." />
|
||||
<GuideEntityEmbed Entity="SheetPlasma" />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="PortableGeneratorSuperPacman" Caption="S.U.P.E.R.P.A.C.M.A.N." />
|
||||
<GuideEntityEmbed Entity="SheetUranium" />
|
||||
</Box>
|
||||
|
||||
The (S.U.P.E.R.)P.A.C.M.A.N. is intended for usage by engineering for advanced power scenarios. Bootstrapping the engine, powering departments, and so on.
|
||||
|
||||
The S.U.P.E.R.P.A.C.M.A.N. boasts larger power output and longer runtime at maximum output, but scales down to lower outputs less efficiently.
|
||||
|
||||
They connect directly to [color=yellow]MV[/color] or [color=orange]HV[/color] power cables, able to switch between them for flexibility.
|
||||
|
||||
</Document>
|
||||
@@ -10,7 +10,7 @@ Shuttle construction is simple and easy, albeit rather expensive and hard to pul
|
||||
<GuideEntityEmbed Entity="Gyroscope"/>
|
||||
<GuideEntityEmbed Entity="ComputerShuttle"/>
|
||||
<GuideEntityEmbed Entity="SubstationBasic"/>
|
||||
<GuideEntityEmbed Entity="GeneratorPlasma"/>
|
||||
<GuideEntityEmbed Entity="PortableGeneratorPacman" Caption="P.A.C.M.A.N." />
|
||||
</Box>
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="CableHVStack"/>
|
||||
|
||||
@@ -1,94 +1,92 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/99ed48ce8d3ba7e2d26d68519e05eae277c94685, animations modified by Peptide90",
|
||||
"copyright": "Taken from https://github.com/Baystation12/Baystation12/blob/caa635edb97c58301ccdc64757eba323b6673cf3/icons/obj/structures/portgen.dmi. Adapted to SS14 and portgen3 sprites made by PJB3005.",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "portgen0_0"
|
||||
"name": "portgen0"
|
||||
},
|
||||
{
|
||||
"name": "portgen1_0"
|
||||
"name": "portgen0on",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "portgen2_0"
|
||||
"name": "portgen1"
|
||||
},
|
||||
{
|
||||
"name": "portgen0_1",
|
||||
"name": "portgen1on",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "portgen1rad",
|
||||
"delays": [
|
||||
[
|
||||
0.05,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "portgen1_1",
|
||||
"name": "portgen2"
|
||||
},
|
||||
{
|
||||
"name": "portgen2on",
|
||||
"delays": [
|
||||
[
|
||||
0.05,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.05
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "portgen2_1",
|
||||
"name": "portgen3"
|
||||
},
|
||||
{
|
||||
"name": "portgen3on",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "portgen3on_unlit",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "portgen_on_unlit",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
|
||||
|
After Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 773 B |
|
Before Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 760 B |
|
Before Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 629 B |
|
After Width: | Height: | Size: 886 B |
|
After Width: | Height: | Size: 178 B |
|
After Width: | Height: | Size: 297 B |
@@ -81,3 +81,9 @@ SyringeSpaceacillin: null
|
||||
|
||||
# 2023-08-13
|
||||
AirlockPainter: SprayPainter
|
||||
|
||||
# 2023-08-19
|
||||
GeneratorPlasma: PortableGeneratorPacman
|
||||
GeneratorUranium: PortableGeneratorSuperPacman
|
||||
GeneratorPlasmaMachineCircuitboard: PortableGeneratorPacmanMachineCircuitboard
|
||||
GeneratorUraniumMachineCircuitboard: PortableGeneratorSuperPacmanMachineCircuitboard
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GD/@EntryIndexedValue">GD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HV/@EntryIndexedValue">HV</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HW/@EntryIndexedValue">HW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IC/@EntryIndexedValue">IC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IL/@EntryIndexedValue">IL</s:String>
|
||||
@@ -63,6 +64,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KHR/@EntryIndexedValue">KHR</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MMI/@EntryIndexedValue">MMI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MS/@EntryIndexedValue">MS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MV/@EntryIndexedValue">MV</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OGL/@EntryIndexedValue">OGL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OOC/@EntryIndexedValue">OOC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
|
||||
|
||||