using Content.Shared.Anomaly;
-using Content.Shared.Gravity;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.Anomaly.Ui;
using Content.Shared.Gravity;
+using Content.Shared.Power;
using Robust.Client.GameObjects;
namespace Content.Client.Gravity;
if (args.Sprite == null)
return;
- if (_appearanceSystem.TryGetData<GravityGeneratorStatus>(uid, GravityGeneratorVisuals.State, out var state, args.Component))
+ if (_appearanceSystem.TryGetData<PowerChargeStatus>(uid, PowerChargeVisuals.State, out var state, args.Component))
{
if (comp.SpriteMap.TryGetValue(state, out var spriteState))
{
}
}
- if (_appearanceSystem.TryGetData<float>(uid, GravityGeneratorVisuals.Charge, out var charge, args.Component))
+ if (_appearanceSystem.TryGetData<float>(uid, PowerChargeVisuals.Charge, out var charge, args.Component))
{
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
switch (charge)
+++ /dev/null
-using Content.Shared.Gravity;
-using JetBrains.Annotations;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.Gravity.UI
-{
- [UsedImplicitly]
- public sealed class GravityGeneratorBoundUserInterface : BoundUserInterface
- {
- [ViewVariables]
- private GravityGeneratorWindow? _window;
-
- public GravityGeneratorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
- {
- }
-
- protected override void Open()
- {
- base.Open();
-
- _window = this.CreateWindow<GravityGeneratorWindow>();
- _window.SetEntity(Owner);
- }
-
- protected override void UpdateState(BoundUserInterfaceState state)
- {
- base.UpdateState(state);
-
- var castState = (SharedGravityGeneratorComponent.GeneratorState) state;
- _window?.UpdateState(castState);
- }
-
- public void SetPowerSwitch(bool on)
- {
- SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
- }
- }
-}
+++ /dev/null
-using Content.Shared.Gravity;
-using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
-
-namespace Content.Client.Gravity.UI
-{
- [GenerateTypedNameReferences]
- public sealed partial class GravityGeneratorWindow : FancyWindow
- {
- private readonly ButtonGroup _buttonGroup = new();
-
- public event Action<bool>? OnPowerSwitch;
-
- public GravityGeneratorWindow()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- OnButton.Group = _buttonGroup;
- OffButton.Group = _buttonGroup;
-
- OnButton.OnPressed += _ => OnPowerSwitch?.Invoke(true);
- OffButton.OnPressed += _ => OnPowerSwitch?.Invoke(false);
- }
-
- public void SetEntity(EntityUid uid)
- {
- EntityView.SetEntity(uid);
- }
-
- public void UpdateState(SharedGravityGeneratorComponent.GeneratorState state)
- {
- if (state.On)
- OnButton.Pressed = true;
- else
- OffButton.Pressed = true;
-
- PowerLabel.Text = Loc.GetString(
- "gravity-generator-window-power-label",
- ("draw", state.PowerDraw),
- ("max", state.PowerDrawMax));
-
- PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution");
-
- ChargeBar.Value = state.Charge;
- ChargeText.Text = (state.Charge / 255f).ToString("P0");
- StatusLabel.Text = Loc.GetString(state.PowerStatus switch
- {
- GravityGeneratorPowerStatus.Off => "gravity-generator-window-status-off",
- GravityGeneratorPowerStatus.Discharging => "gravity-generator-window-status-discharging",
- GravityGeneratorPowerStatus.Charging => "gravity-generator-window-status-charging",
- GravityGeneratorPowerStatus.FullyCharged => "gravity-generator-window-status-fully-charged",
- _ => throw new ArgumentOutOfRangeException()
- });
-
- StatusLabel.SetOnlyStyleClass(state.PowerStatus switch
- {
- GravityGeneratorPowerStatus.Off => "Danger",
- GravityGeneratorPowerStatus.Discharging => "Caution",
- GravityGeneratorPowerStatus.Charging => "Caution",
- GravityGeneratorPowerStatus.FullyCharged => "Good",
- _ => throw new ArgumentOutOfRangeException()
- });
-
- EtaLabel.Text = state.EtaSeconds >= 0
- ? Loc.GetString("gravity-generator-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds)))
- : Loc.GetString("gravity-generator-window-eta-none");
-
- EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled");
- }
- }
-}
--- /dev/null
+using Content.Shared.Power;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Power.PowerCharge;
+
+public sealed class PowerChargeBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private PowerChargeWindow? _window;
+
+ public PowerChargeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ public void SetPowerSwitch(bool on)
+ {
+ SendMessage(new SwitchChargingMachineMessage(on));
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ if (!EntMan.TryGetComponent(Owner, out PowerChargeComponent? component))
+ return;
+
+ _window = this.CreateWindow<PowerChargeWindow>();
+ _window.UpdateWindow(this, Loc.GetString(component.WindowTitle));
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is not PowerChargeState chargeState)
+ return;
+
+ _window?.UpdateState(chargeState);
+ }
+}
--- /dev/null
+using Content.Shared.Power;
+
+namespace Content.Client.Power.PowerCharge;
+
+/// <inheritdoc cref="Content.Shared.Power.SharedPowerChargeComponent" />
+[RegisterComponent]
+public sealed partial class PowerChargeComponent : SharedPowerChargeComponent
+{
+
+}
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
- Title="{Loc 'gravity-generator-window-title'}"
MinSize="270 130"
SetSize="360 180">
<BoxContainer Margin="4 0" Orientation="Horizontal">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<GridContainer Margin="2 0 0 0" Columns="2">
<!-- Power -->
- <Label Text="{Loc 'gravity-generator-window-power'}" HorizontalExpand="True" StyleClasses="StatusFieldTitle" />
+ <Label Text="{Loc 'power-charge-window-power'}" HorizontalExpand="True" StyleClasses="StatusFieldTitle" />
<BoxContainer Orientation="Horizontal" MinWidth="120">
- <Button Name="OnButton" Text="{Loc 'gravity-generator-window-power-on'}" StyleClasses="OpenRight" />
- <Button Name="OffButton" Text="{Loc 'gravity-generator-window-power-off'}" StyleClasses="OpenLeft" />
+ <Button Name="OnButton" Text="{Loc 'power-charge-window-power-on'}" StyleClasses="OpenRight" />
+ <Button Name="OffButton" Text="{Loc 'power-charge-window-power-off'}" StyleClasses="OpenLeft" />
</BoxContainer>
<Control /> <!-- Empty control to act as a spacer in the grid. -->
<Label Name="PowerLabel" Text="0 / 0 W" />
<!-- Status -->
- <Label Text="{Loc 'gravity-generator-window-status'}" StyleClasses="StatusFieldTitle" />
- <Label Name="StatusLabel" Text="{Loc 'gravity-generator-window-status-fully-charged'}" />
+ <Label Text="{Loc 'power-charge-window-status'}" StyleClasses="StatusFieldTitle" />
+ <Label Name="StatusLabel" Text="{Loc 'power-charge-window-status-fully-charged'}" />
<!-- ETA -->
- <Label Text="{Loc 'gravity-generator-window-eta'}" StyleClasses="StatusFieldTitle" />
+ <Label Text="{Loc 'power-charge-window-eta'}" StyleClasses="StatusFieldTitle" />
<Label Name="EtaLabel" Text="N/A" />
<!-- Charge -->
- <Label Text="{Loc 'gravity-generator-window-charge'}" StyleClasses="StatusFieldTitle" />
+ <Label Text="{Loc 'power-charge-window-charge'}" StyleClasses="StatusFieldTitle" />
<ProgressBar Name="ChargeBar" MaxValue="255">
<Label Name="ChargeText" Margin="4 0" Text="0 %" />
</ProgressBar>
<SpriteView Name="EntityView" SetSize="96 96" OverrideDirection="South" />
</PanelContainer>
</BoxContainer>
-
</controls:FancyWindow>
--- /dev/null
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Power;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Power.PowerCharge;
+
+[GenerateTypedNameReferences]
+public sealed partial class PowerChargeWindow : FancyWindow
+{
+ private readonly ButtonGroup _buttonGroup = new();
+
+ public PowerChargeWindow()
+ {
+ RobustXamlLoader.Load(this);
+
+ OnButton.Group = _buttonGroup;
+ OffButton.Group = _buttonGroup;
+ }
+
+ public void UpdateWindow(PowerChargeBoundUserInterface bui, string title)
+ {
+ Title = title;
+
+ OnButton.OnPressed += _ => bui.SetPowerSwitch(true);
+ OffButton.OnPressed += _ => bui.SetPowerSwitch(false);
+
+ EntityView.SetEntity(bui.Owner);
+ }
+
+ public void UpdateState(PowerChargeState state)
+ {
+ if (state.On)
+ OnButton.Pressed = true;
+ else
+ OffButton.Pressed = true;
+
+ PowerLabel.Text = Loc.GetString(
+ "power-charge-window-power-label",
+ ("draw", state.PowerDraw),
+ ("max", state.PowerDrawMax));
+
+ PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution");
+
+ ChargeBar.Value = state.Charge;
+ ChargeText.Text = (state.Charge / 255f).ToString("P0");
+ StatusLabel.Text = Loc.GetString(state.PowerStatus switch
+ {
+ PowerChargePowerStatus.Off => "power-charge-window-status-off",
+ PowerChargePowerStatus.Discharging => "power-charge-window-status-discharging",
+ PowerChargePowerStatus.Charging => "power-charge-window-status-charging",
+ PowerChargePowerStatus.FullyCharged => "power-charge-window-status-fully-charged",
+ _ => throw new ArgumentOutOfRangeException()
+ });
+
+ StatusLabel.SetOnlyStyleClass(state.PowerStatus switch
+ {
+ PowerChargePowerStatus.Off => "Danger",
+ PowerChargePowerStatus.Discharging => "Caution",
+ PowerChargePowerStatus.Charging => "Caution",
+ PowerChargePowerStatus.FullyCharged => "Good",
+ _ => throw new ArgumentOutOfRangeException()
+ });
+
+ EtaLabel.Text = state.EtaSeconds >= 0
+ ? Loc.GetString("power-charge-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds)))
+ : Loc.GetString("power-charge-window-eta-none");
+
+ EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled");
+ }
+}
id: WeightlessGravityGeneratorDummy
components:
- type: GravityGenerator
+ - type: PowerCharge
+ windowTitle: gravity-generator-window-title
+ idlePower: 50
chargeRate: 1000000000 # Set this really high so it discharges in a single tick.
activePower: 500
- type: ApcPowerReceiver
id: GridGravityGeneratorDummy
components:
- type: GravityGenerator
+ - type: PowerCharge
+ windowTitle: gravity-generator-window-title
+ idlePower: 50
chargeRate: 1000000000 # Set this really high so it discharges in a single tick.
activePower: 500
- type: ApcPowerReceiver
[Access(typeof(GravityGeneratorSystem))]
public sealed partial class GravityGeneratorComponent : SharedGravityGeneratorComponent
{
- // 1% charge per second.
- [ViewVariables(VVAccess.ReadWrite)] [DataField("chargeRate")] public float ChargeRate { get; set; } = 0.01f;
- // The gravity generator has two power values.
- // Idle power is assumed to be the power needed to run the control systems and interface.
- [DataField("idlePower")] public float IdlePowerUse { get; set; }
- // Active power is the power needed to keep the gravity field stable.
- [DataField("activePower")] public float ActivePowerUse { get; set; }
[DataField("lightRadiusMin")] public float LightRadiusMin { get; set; }
[DataField("lightRadiusMax")] public float LightRadiusMax { get; set; }
-
- /// <summary>
- /// Is the power switch on?
- /// </summary>
- [DataField("switchedOn")]
- public bool SwitchedOn { get; set; } = true;
-
- /// <summary>
- /// Is the gravity generator intact?
- /// </summary>
- [DataField("intact")]
- public bool Intact { get; set; } = true;
-
- [DataField("maxCharge")]
- public float MaxCharge { get; set; } = 1;
-
- // 0 -> 1
- [ViewVariables(VVAccess.ReadWrite)] [DataField("charge")] public float Charge { get; set; } = 1;
-
/// <summary>
/// Is the gravity generator currently "producing" gravity?
/// </summary>
[ViewVariables]
public bool GravityActive { get; set; } = false;
-
- // Do we need a UI update even if the charge doesn't change? Used by power button.
- [ViewVariables] public bool NeedUIUpdate { get; set; }
}
}
-using Content.Server.Administration.Logs;
-using Content.Server.Audio;
using Content.Server.Power.Components;
-using Content.Shared.Database;
+using Content.Server.Power.EntitySystems;
using Content.Shared.Gravity;
-using Content.Shared.Interaction;
-using Robust.Server.GameObjects;
-using Robust.Shared.Player;
-namespace Content.Server.Gravity
-{
- public sealed class GravityGeneratorSystem : EntitySystem
- {
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!;
- [Dependency] private readonly GravitySystem _gravitySystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedPointLightSystem _lights = default!;
- [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<GravityGeneratorComponent, ComponentInit>(OnCompInit);
- SubscribeLocalEvent<GravityGeneratorComponent, ComponentShutdown>(OnComponentShutdown);
- SubscribeLocalEvent<GravityGeneratorComponent, EntParentChangedMessage>(OnParentChanged); // Or just anchor changed?
- SubscribeLocalEvent<GravityGeneratorComponent, InteractHandEvent>(OnInteractHand);
- SubscribeLocalEvent<GravityGeneratorComponent, SharedGravityGeneratorComponent.SwitchGeneratorMessage>(
- OnSwitchGenerator);
- }
-
- private void OnParentChanged(EntityUid uid, GravityGeneratorComponent component, ref EntParentChangedMessage args)
- {
- if (component.GravityActive && TryComp(args.OldParent, out GravityComponent? gravity))
- {
- _gravitySystem.RefreshGravity(args.OldParent.Value, gravity);
- }
- }
-
- private void OnComponentShutdown(EntityUid uid, GravityGeneratorComponent component, ComponentShutdown args)
- {
- if (component.GravityActive &&
- TryComp(uid, out TransformComponent? xform) &&
- TryComp(xform.ParentUid, out GravityComponent? gravity))
- {
- component.GravityActive = false;
- _gravitySystem.RefreshGravity(xform.ParentUid, gravity);
- }
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator<GravityGeneratorComponent, ApcPowerReceiverComponent>();
- while (query.MoveNext(out var uid, out var gravGen, out var powerReceiver))
- {
- var ent = (uid, gravGen, powerReceiver);
- if (!gravGen.Intact)
- continue;
-
- // Calculate charge rate based on power state and such.
- // Negative charge rate means discharging.
- float chargeRate;
- if (gravGen.SwitchedOn)
- {
- if (powerReceiver.Powered)
- {
- chargeRate = gravGen.ChargeRate;
- }
- else
- {
- // Scale discharge rate such that if we're at 25% active power we discharge at 75% rate.
- var receiving = powerReceiver.PowerReceived;
- var mainSystemPower = Math.Max(0, receiving - gravGen.IdlePowerUse);
- var ratio = 1 - mainSystemPower / (gravGen.ActivePowerUse - gravGen.IdlePowerUse);
- chargeRate = -(ratio * gravGen.ChargeRate);
- }
- }
- else
- {
- chargeRate = -gravGen.ChargeRate;
- }
-
- var active = gravGen.GravityActive;
- var lastCharge = gravGen.Charge;
- gravGen.Charge = Math.Clamp(gravGen.Charge + frameTime * chargeRate, 0, gravGen.MaxCharge);
- if (chargeRate > 0)
- {
- // Charging.
- if (MathHelper.CloseTo(gravGen.Charge, gravGen.MaxCharge) && !gravGen.GravityActive)
- {
- gravGen.GravityActive = true;
- }
- }
- else
- {
- // Discharging
- if (MathHelper.CloseTo(gravGen.Charge, 0) && gravGen.GravityActive)
- {
- gravGen.GravityActive = false;
- }
- }
-
- var updateUI = gravGen.NeedUIUpdate;
- if (!MathHelper.CloseTo(lastCharge, gravGen.Charge))
- {
- UpdateState(ent);
- updateUI = true;
- }
-
- if (updateUI)
- UpdateUI(ent, chargeRate);
-
- if (active != gravGen.GravityActive &&
- TryComp(uid, out TransformComponent? xform) &&
- TryComp<GravityComponent>(xform.ParentUid, out var gravity))
- {
- // Force it on in the faster path.
- if (gravGen.GravityActive)
- {
- _gravitySystem.EnableGravity(xform.ParentUid, gravity);
- }
- else
- {
- _gravitySystem.RefreshGravity(xform.ParentUid, gravity);
- }
- }
- }
- }
-
- private void SetSwitchedOn(EntityUid uid, GravityGeneratorComponent component, bool on,
- ApcPowerReceiverComponent? powerReceiver = null, EntityUid? user = null)
- {
- if (!Resolve(uid, ref powerReceiver))
- return;
-
- if (user != null)
- _adminLogger.Add(LogType.Action, on ? LogImpact.Medium : LogImpact.High, $"{ToPrettyString(user)} set ${ToPrettyString(uid):target} to {(on ? "on" : "off")}");
-
- component.SwitchedOn = on;
- UpdatePowerState(component, powerReceiver);
- component.NeedUIUpdate = true;
- }
-
- private static void UpdatePowerState(
- GravityGeneratorComponent component,
- ApcPowerReceiverComponent powerReceiver)
- {
- powerReceiver.Load = component.SwitchedOn ? component.ActivePowerUse : component.IdlePowerUse;
- }
-
- private void UpdateUI(Entity<GravityGeneratorComponent, ApcPowerReceiverComponent> ent, float chargeRate)
- {
- var (_, component, powerReceiver) = ent;
- if (!_uiSystem.IsUiOpen(ent.Owner, SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key))
- return;
-
- var chargeTarget = chargeRate < 0 ? 0 : component.MaxCharge;
- short chargeEta;
- var atTarget = false;
- if (MathHelper.CloseTo(component.Charge, chargeTarget))
- {
- chargeEta = short.MinValue; // N/A
- atTarget = true;
- }
- else
- {
- var diff = chargeTarget - component.Charge;
- chargeEta = (short) Math.Abs(diff / chargeRate);
- }
+namespace Content.Server.Gravity;
- var status = chargeRate switch
- {
- > 0 when atTarget => GravityGeneratorPowerStatus.FullyCharged,
- < 0 when atTarget => GravityGeneratorPowerStatus.Off,
- > 0 => GravityGeneratorPowerStatus.Charging,
- < 0 => GravityGeneratorPowerStatus.Discharging,
- _ => throw new ArgumentOutOfRangeException()
- };
-
- var state = new SharedGravityGeneratorComponent.GeneratorState(
- component.SwitchedOn,
- (byte) (component.Charge * 255),
- status,
- (short) Math.Round(powerReceiver.PowerReceived),
- (short) Math.Round(powerReceiver.Load),
- chargeEta
- );
-
- _uiSystem.SetUiState(
- ent.Owner,
- SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key,
- state);
-
- component.NeedUIUpdate = false;
- }
-
- private void OnCompInit(Entity<GravityGeneratorComponent> ent, ref ComponentInit args)
- {
- ApcPowerReceiverComponent? powerReceiver = null;
- if (!Resolve(ent, ref powerReceiver, false))
- return;
-
- UpdatePowerState(ent, powerReceiver);
- UpdateState((ent, ent.Comp, powerReceiver));
- }
-
- private void OnInteractHand(EntityUid uid, GravityGeneratorComponent component, InteractHandEvent args)
- {
- ApcPowerReceiverComponent? powerReceiver = default!;
- if (!Resolve(uid, ref powerReceiver))
- return;
-
- // Do not allow opening UI if broken or unpowered.
- if (!component.Intact || powerReceiver.PowerReceived < component.IdlePowerUse)
- return;
-
- _uiSystem.OpenUi(uid, SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key, args.User);
- component.NeedUIUpdate = true;
- }
-
- public void UpdateState(Entity<GravityGeneratorComponent, ApcPowerReceiverComponent> ent)
- {
- var (uid, grav, powerReceiver) = ent;
- var appearance = EntityManager.GetComponentOrNull<AppearanceComponent>(uid);
- _appearance.SetData(uid, GravityGeneratorVisuals.Charge, grav.Charge, appearance);
-
- if (_lights.TryGetLight(uid, out var pointLight))
- {
- _lights.SetEnabled(uid, grav.Charge > 0, pointLight);
- _lights.SetRadius(uid, MathHelper.Lerp(grav.LightRadiusMin, grav.LightRadiusMax, grav.Charge), pointLight);
- }
-
- if (!grav.Intact)
- {
- MakeBroken((uid, grav), appearance);
- }
- else if (powerReceiver.PowerReceived < grav.IdlePowerUse)
- {
- MakeUnpowered((uid, grav), appearance);
- }
- else if (!grav.SwitchedOn)
- {
- MakeOff((uid, grav), appearance);
- }
- else
- {
- MakeOn((uid, grav), appearance);
- }
- }
+public sealed class GravityGeneratorSystem : EntitySystem
+{
+ [Dependency] private readonly GravitySystem _gravitySystem = default!;
+ [Dependency] private readonly SharedPointLightSystem _lights = default!;
- private void MakeBroken(Entity<GravityGeneratorComponent> ent, AppearanceComponent? appearance)
- {
- _ambientSoundSystem.SetAmbience(ent, false);
+ public override void Initialize()
+ {
+ base.Initialize();
- _appearance.SetData(ent, GravityGeneratorVisuals.State, GravityGeneratorStatus.Broken);
- }
+ SubscribeLocalEvent<GravityGeneratorComponent, EntParentChangedMessage>(OnParentChanged);
+ SubscribeLocalEvent<GravityGeneratorComponent, ChargedMachineActivatedEvent>(OnActivated);
+ SubscribeLocalEvent<GravityGeneratorComponent, ChargedMachineDeactivatedEvent>(OnDeactivated);
+ }
- private void MakeUnpowered(Entity<GravityGeneratorComponent> ent, AppearanceComponent? appearance)
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ var query = EntityQueryEnumerator<GravityGeneratorComponent, PowerChargeComponent>();
+ while (query.MoveNext(out var uid, out var grav, out var charge))
{
- _ambientSoundSystem.SetAmbience(ent, false);
+ if (!_lights.TryGetLight(uid, out var pointLight))
+ continue;
- _appearance.SetData(ent, GravityGeneratorVisuals.State, GravityGeneratorStatus.Unpowered, appearance);
+ _lights.SetEnabled(uid, charge.Charge > 0, pointLight);
+ _lights.SetRadius(uid, MathHelper.Lerp(grav.LightRadiusMin, grav.LightRadiusMax, charge.Charge),
+ pointLight);
}
+ }
- private void MakeOff(Entity<GravityGeneratorComponent> ent, AppearanceComponent? appearance)
+ private void OnActivated(Entity<GravityGeneratorComponent> ent, ref ChargedMachineActivatedEvent args)
+ {
+ ent.Comp.GravityActive = true;
+ if (TryComp<TransformComponent>(ent, out var xform) &&
+ TryComp(xform.ParentUid, out GravityComponent? gravity))
{
- _ambientSoundSystem.SetAmbience(ent, false);
-
- _appearance.SetData(ent, GravityGeneratorVisuals.State, GravityGeneratorStatus.Off, appearance);
+ _gravitySystem.EnableGravity(xform.ParentUid, gravity);
}
+ }
- private void MakeOn(Entity<GravityGeneratorComponent> ent, AppearanceComponent? appearance)
+ private void OnDeactivated(Entity<GravityGeneratorComponent> ent, ref ChargedMachineDeactivatedEvent args)
+ {
+ ent.Comp.GravityActive = false;
+ if (TryComp<TransformComponent>(ent, out var xform) &&
+ TryComp(xform.ParentUid, out GravityComponent? gravity))
{
- _ambientSoundSystem.SetAmbience(ent, true);
-
- _appearance.SetData(ent, GravityGeneratorVisuals.State, GravityGeneratorStatus.On, appearance);
+ _gravitySystem.RefreshGravity(xform.ParentUid, gravity);
}
+ }
- private void OnSwitchGenerator(
- EntityUid uid,
- GravityGeneratorComponent component,
- SharedGravityGeneratorComponent.SwitchGeneratorMessage args)
+ private void OnParentChanged(EntityUid uid, GravityGeneratorComponent component, ref EntParentChangedMessage args)
+ {
+ if (component.GravityActive && TryComp(args.OldParent, out GravityComponent? gravity))
{
- SetSwitchedOn(uid, component, args.On, user: args.Actor);
+ _gravitySystem.RefreshGravity(args.OldParent.Value, gravity);
}
}
}
--- /dev/null
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Power;
+
+namespace Content.Server.Power.Components;
+
+/// <inheritdoc cref="Content.Shared.Power.SharedPowerChargeComponent" />
+[RegisterComponent]
+[Access(typeof(PowerChargeSystem))]
+public sealed partial class PowerChargeComponent : SharedPowerChargeComponent
+{
+ /// <summary>
+ /// Change in charge per second.
+ /// </summary>
+ [DataField]
+ public float ChargeRate { get; set; } = 0.01f;
+
+ /// <summary>
+ /// Baseline power that this machine consumes.
+ /// </summary>
+ [DataField("idlePower")]
+ public float IdlePowerUse { get; set; }
+
+ /// <summary>
+ /// Power consumed when <see cref="SwitchedOn"/> is true.
+ /// </summary>
+ [DataField("activePower")]
+ public float ActivePowerUse { get; set; }
+
+ /// <summary>
+ /// Is the gravity generator intact?
+ /// </summary>
+ [DataField]
+ public bool Intact { get; set; } = true;
+
+ /// <summary>
+ /// Is the power switch on?
+ /// </summary>
+ [DataField]
+ public bool SwitchedOn { get; set; } = true;
+
+ /// <summary>
+ /// Whether or not the power is switched on and the entity has charged up.
+ /// </summary>
+ [DataField]
+ public bool Active { get; set; }
+
+ [DataField]
+ public float MaxCharge { get; set; } = 1;
+
+ /// <summary>
+ /// The UI key of the UI that's used with this machine.<br/>
+ /// This is used to allow machine power charging to be integrated into any ui
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadOnly)]
+ public Enum UiKey { get; set; } = PowerChargeUiKey.Key;
+
+ /// <summary>
+ /// Current charge value.
+ /// Goes from 0 to 1.
+ /// </summary>
+ [DataField]
+ public float Charge { get; set; } = 1;
+
+ [ViewVariables]
+ public bool NeedUIUpdate { get; set; }
+}
--- /dev/null
+using Content.Server.Administration.Logs;
+using Content.Server.Audio;
+using Content.Server.Power.Components;
+using Content.Shared.Database;
+using Content.Shared.Power;
+using Content.Shared.UserInterface;
+using Robust.Server.GameObjects;
+using Robust.Shared.Player;
+
+namespace Content.Server.Power.EntitySystems;
+
+public sealed class PowerChargeSystem : EntitySystem
+{
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<PowerChargeComponent, MapInitEvent>(OnMapInit);
+ SubscribeLocalEvent<PowerChargeComponent, ComponentShutdown>(OnComponentShutdown);
+ SubscribeLocalEvent<PowerChargeComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
+ SubscribeLocalEvent<PowerChargeComponent, AfterActivatableUIOpenEvent>(OnAfterUiOpened);
+ SubscribeLocalEvent<PowerChargeComponent, AnchorStateChangedEvent>(OnAnchorStateChange);
+
+ // This needs to be ui key agnostic
+ SubscribeLocalEvent<PowerChargeComponent, SwitchChargingMachineMessage>(OnSwitchGenerator);
+ }
+
+ private void OnAnchorStateChange(EntityUid uid, PowerChargeComponent component, AnchorStateChangedEvent args)
+ {
+ if (args.Anchored || !TryComp<ApcPowerReceiverComponent>(uid, out var powerReceiverComponent))
+ return;
+
+ component.Active = false;
+ component.Charge = 0;
+ UpdateState(new Entity<PowerChargeComponent, ApcPowerReceiverComponent>(uid, component, powerReceiverComponent));
+ }
+
+ private void OnAfterUiOpened(EntityUid uid, PowerChargeComponent component, AfterActivatableUIOpenEvent args)
+ {
+ if (!TryComp<ApcPowerReceiverComponent>(uid, out var apcPowerReceiver))
+ return;
+
+ UpdateUI((uid, component, apcPowerReceiver), component.ChargeRate);
+ }
+
+ private void OnSwitchGenerator(EntityUid uid, PowerChargeComponent component, SwitchChargingMachineMessage args)
+ {
+ SetSwitchedOn(uid, component, args.On, user: args.Actor);
+ }
+
+ private void OnUIOpenAttempt(EntityUid uid, PowerChargeComponent component, ActivatableUIOpenAttemptEvent args)
+ {
+ if (!component.Intact)
+ args.Cancel();
+ }
+
+ private void OnComponentShutdown(EntityUid uid, PowerChargeComponent component, ComponentShutdown args)
+ {
+ if (!component.Active)
+ return;
+
+ component.Active = false;
+
+ var eventArgs = new ChargedMachineDeactivatedEvent();
+ RaiseLocalEvent(uid, ref eventArgs);
+ }
+
+ private void OnMapInit(Entity<PowerChargeComponent> ent, ref MapInitEvent args)
+ {
+ ApcPowerReceiverComponent? powerReceiver = null;
+ if (!Resolve(ent, ref powerReceiver, false))
+ return;
+
+ UpdatePowerState(ent, powerReceiver);
+ UpdateState((ent, ent.Comp, powerReceiver));
+ }
+
+ private void SetSwitchedOn(EntityUid uid, PowerChargeComponent component, bool on,
+ ApcPowerReceiverComponent? powerReceiver = null, EntityUid? user = null)
+ {
+ if (!Resolve(uid, ref powerReceiver))
+ return;
+
+ if (user is { } )
+ _adminLogger.Add(LogType.Action, on ? LogImpact.Medium : LogImpact.High, $"{ToPrettyString(user):player} set ${ToPrettyString(uid):target} to {(on ? "on" : "off")}");
+
+ component.SwitchedOn = on;
+ UpdatePowerState(component, powerReceiver);
+ component.NeedUIUpdate = true;
+ }
+
+ private static void UpdatePowerState(PowerChargeComponent component, ApcPowerReceiverComponent powerReceiver)
+ {
+ powerReceiver.Load = component.SwitchedOn ? component.ActivePowerUse : component.IdlePowerUse;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<PowerChargeComponent, ApcPowerReceiverComponent>();
+ while (query.MoveNext(out var uid, out var chargingMachine, out var powerReceiver))
+ {
+ var ent = (uid, gravGen: chargingMachine, powerReceiver);
+ if (!chargingMachine.Intact)
+ continue;
+
+ // Calculate charge rate based on power state and such.
+ // Negative charge rate means discharging.
+ float chargeRate;
+ if (chargingMachine.SwitchedOn)
+ {
+ if (powerReceiver.Powered)
+ {
+ chargeRate = chargingMachine.ChargeRate;
+ }
+ else
+ {
+ // Scale discharge rate such that if we're at 25% active power we discharge at 75% rate.
+ var receiving = powerReceiver.PowerReceived;
+ var mainSystemPower = Math.Max(0, receiving - chargingMachine.IdlePowerUse);
+ var ratio = 1 - mainSystemPower / (chargingMachine.ActivePowerUse - chargingMachine.IdlePowerUse);
+ chargeRate = -(ratio * chargingMachine.ChargeRate);
+ }
+ }
+ else
+ {
+ chargeRate = -chargingMachine.ChargeRate;
+ }
+
+ var active = chargingMachine.Active;
+ var lastCharge = chargingMachine.Charge;
+ chargingMachine.Charge = Math.Clamp(chargingMachine.Charge + frameTime * chargeRate, 0, chargingMachine.MaxCharge);
+ if (chargeRate > 0)
+ {
+ // Charging.
+ if (MathHelper.CloseTo(chargingMachine.Charge, chargingMachine.MaxCharge) && !chargingMachine.Active)
+ {
+ chargingMachine.Active = true;
+ }
+ }
+ else
+ {
+ // Discharging
+ if (MathHelper.CloseTo(chargingMachine.Charge, 0) && chargingMachine.Active)
+ {
+ chargingMachine.Active = false;
+ }
+ }
+
+ var updateUI = chargingMachine.NeedUIUpdate;
+ if (!MathHelper.CloseTo(lastCharge, chargingMachine.Charge))
+ {
+ UpdateState(ent);
+ updateUI = true;
+ }
+
+ if (updateUI)
+ UpdateUI(ent, chargeRate);
+
+ if (active == chargingMachine.Active)
+ continue;
+
+ if (chargingMachine.Active)
+ {
+ var eventArgs = new ChargedMachineActivatedEvent();
+ RaiseLocalEvent(uid, ref eventArgs);
+ }
+ else
+ {
+ var eventArgs = new ChargedMachineDeactivatedEvent();
+ RaiseLocalEvent(uid, ref eventArgs);
+ }
+ }
+ }
+
+ private void UpdateUI(Entity<PowerChargeComponent, ApcPowerReceiverComponent> ent, float chargeRate)
+ {
+ var (_, component, powerReceiver) = ent;
+ if (!_uiSystem.IsUiOpen(ent.Owner, component.UiKey))
+ return;
+
+ var chargeTarget = chargeRate < 0 ? 0 : component.MaxCharge;
+ short chargeEta;
+ var atTarget = false;
+ if (MathHelper.CloseTo(component.Charge, chargeTarget))
+ {
+ chargeEta = short.MinValue; // N/A
+ atTarget = true;
+ }
+ else
+ {
+ var diff = chargeTarget - component.Charge;
+ chargeEta = (short) Math.Abs(diff / chargeRate);
+ }
+
+ var status = chargeRate switch
+ {
+ > 0 when atTarget => PowerChargePowerStatus.FullyCharged,
+ < 0 when atTarget => PowerChargePowerStatus.Off,
+ > 0 => PowerChargePowerStatus.Charging,
+ < 0 => PowerChargePowerStatus.Discharging,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ var state = new PowerChargeState(
+ component.SwitchedOn,
+ (byte) (component.Charge * 255),
+ status,
+ (short) Math.Round(powerReceiver.PowerReceived),
+ (short) Math.Round(powerReceiver.Load),
+ chargeEta
+ );
+
+ _uiSystem.SetUiState(
+ ent.Owner,
+ component.UiKey,
+ state);
+
+ component.NeedUIUpdate = false;
+ }
+
+ private void UpdateState(Entity<PowerChargeComponent, ApcPowerReceiverComponent> ent)
+ {
+ var (uid, machine, powerReceiver) = ent;
+ var appearance = EntityManager.GetComponentOrNull<AppearanceComponent>(uid);
+ _appearance.SetData(uid, PowerChargeVisuals.Charge, machine.Charge, appearance);
+ _appearance.SetData(uid, PowerChargeVisuals.Active, machine.Active);
+
+
+ if (!machine.Intact)
+ {
+ MakeBroken((uid, machine), appearance);
+ }
+ else if (powerReceiver.PowerReceived < machine.IdlePowerUse)
+ {
+ MakeUnpowered((uid, machine), appearance);
+ }
+ else if (!machine.SwitchedOn)
+ {
+ MakeOff((uid, machine), appearance);
+ }
+ else
+ {
+ MakeOn((uid, machine), appearance);
+ }
+ }
+
+ private void MakeBroken(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
+ {
+ _ambientSoundSystem.SetAmbience(ent, false);
+
+ _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.Broken, appearance);
+ }
+
+ private void MakeUnpowered(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
+ {
+ _ambientSoundSystem.SetAmbience(ent, false);
+
+ _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.Unpowered, appearance);
+ }
+
+ private void MakeOff(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
+ {
+ _ambientSoundSystem.SetAmbience(ent, false);
+
+ _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.Off, appearance);
+ }
+
+ private void MakeOn(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
+ {
+ _ambientSoundSystem.SetAmbience(ent, true);
+
+ _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.On, appearance);
+ }
+}
+
+[ByRefEvent] public record struct ChargedMachineActivatedEvent;
+[ByRefEvent] public record struct ChargedMachineDeactivatedEvent;
--- /dev/null
+using Content.Server.Shuttles.Systems;
+
+namespace Content.Server.Shuttles.Components;
+
+[RegisterComponent]
+[Access(typeof(StationAnchorSystem))]
+public sealed partial class StationAnchorComponent : Component
+{
+ [DataField("switchedOn")]
+ public bool SwitchedOn { get; set; } = true;
+}
--- /dev/null
+using Content.Server.Popups;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Shuttles.Components;
+using Content.Shared.Construction.Components;
+using Content.Shared.Popups;
+
+namespace Content.Server.Shuttles.Systems;
+
+public sealed class StationAnchorSystem : EntitySystem
+{
+ [Dependency] private readonly ShuttleSystem _shuttleSystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<StationAnchorComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
+ SubscribeLocalEvent<StationAnchorComponent, AnchorStateChangedEvent>(OnAnchorStationChange);
+
+ SubscribeLocalEvent<StationAnchorComponent, ChargedMachineActivatedEvent>(OnActivated);
+ SubscribeLocalEvent<StationAnchorComponent, ChargedMachineDeactivatedEvent>(OnDeactivated);
+
+ SubscribeLocalEvent<StationAnchorComponent, MapInitEvent>(OnMapInit);
+ }
+
+ private void OnMapInit(Entity<StationAnchorComponent> ent, ref MapInitEvent args)
+ {
+ if (!ent.Comp.SwitchedOn)
+ return;
+
+ SetStatus(ent, true);
+ }
+
+ private void OnActivated(Entity<StationAnchorComponent> ent, ref ChargedMachineActivatedEvent args)
+ {
+ SetStatus(ent, true);
+ }
+
+ private void OnDeactivated(Entity<StationAnchorComponent> ent, ref ChargedMachineDeactivatedEvent args)
+ {
+ SetStatus(ent, false);
+ }
+
+ /// <summary>
+ /// Prevent unanchoring when anchor is active
+ /// </summary>
+ private void OnUnanchorAttempt(Entity<StationAnchorComponent> ent, ref UnanchorAttemptEvent args)
+ {
+ if (!ent.Comp.SwitchedOn)
+ return;
+
+ _popupSystem.PopupEntity(
+ Loc.GetString("station-anchor-unanchoring-failed"),
+ ent,
+ args.User,
+ PopupType.Medium);
+
+ args.Cancel();
+ }
+
+ private void OnAnchorStationChange(Entity<StationAnchorComponent> ent, ref AnchorStateChangedEvent args)
+ {
+ if (!args.Anchored)
+ SetStatus(ent, false);
+ }
+
+ private void SetStatus(Entity<StationAnchorComponent> ent, bool enabled, ShuttleComponent? shuttleComponent = default)
+ {
+ var transform = Transform(ent);
+ var grid = transform.GridUid;
+ if (!grid.HasValue || !transform.Anchored && enabled || !Resolve(grid.Value, ref shuttleComponent))
+ return;
+
+ if (enabled)
+ {
+ _shuttleSystem.Disable(grid.Value);
+ }
+ else
+ {
+ _shuttleSystem.Enable(grid.Value);
+ }
+
+ shuttleComponent.Enabled = !enabled;
+ ent.Comp.SwitchedOn = enabled;
+ }
+}
+using Content.Shared.Power;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-namespace Content.Shared.Gravity
-{
- [NetworkedComponent()]
- [Virtual]
- public partial class SharedGravityGeneratorComponent : Component
- {
- /// <summary>
- /// A map of the sprites used by the gravity generator given its status.
- /// </summary>
- [DataField("spriteMap")]
- [Access(typeof(SharedGravitySystem))]
- public Dictionary<GravityGeneratorStatus, string> SpriteMap = new();
-
- /// <summary>
- /// The sprite used by the core of the gravity generator when the gravity generator is starting up.
- /// </summary>
- [DataField("coreStartupState")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string CoreStartupState = "startup";
-
- /// <summary>
- /// The sprite used by the core of the gravity generator when the gravity generator is idle.
- /// </summary>
- [DataField("coreIdleState")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string CoreIdleState = "idle";
-
- /// <summary>
- /// The sprite used by the core of the gravity generator when the gravity generator is activating.
- /// </summary>
- [DataField("coreActivatingState")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string CoreActivatingState = "activating";
-
- /// <summary>
- /// The sprite used by the core of the gravity generator when the gravity generator is active.
- /// </summary>
- [DataField("coreActivatedState")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string CoreActivatedState = "activated";
-
- /// <summary>
- /// Sent to the server to set whether the generator should be on or off
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class SwitchGeneratorMessage : BoundUserInterfaceMessage
- {
- public bool On;
-
- public SwitchGeneratorMessage(bool on)
- {
- On = on;
- }
- }
+namespace Content.Shared.Gravity;
- [Serializable, NetSerializable]
- public sealed class GeneratorState : BoundUserInterfaceState
- {
- public bool On;
- // 0 -> 255
- public byte Charge;
- public GravityGeneratorPowerStatus PowerStatus;
- public short PowerDraw;
- public short PowerDrawMax;
- public short EtaSeconds;
-
- public GeneratorState(
- bool on,
- byte charge,
- GravityGeneratorPowerStatus powerStatus,
- short powerDraw,
- short powerDrawMax,
- short etaSeconds)
- {
- On = on;
- Charge = charge;
- PowerStatus = powerStatus;
- PowerDraw = powerDraw;
- PowerDrawMax = powerDrawMax;
- EtaSeconds = etaSeconds;
- }
- }
-
- [Serializable, NetSerializable]
- public enum GravityGeneratorUiKey
- {
- Key
- }
- }
-
- [Serializable, NetSerializable]
- public enum GravityGeneratorVisuals
- {
- State,
- Charge
- }
-
- [Serializable, NetSerializable]
- public enum GravityGeneratorStatus
- {
- Broken,
- Unpowered,
- Off,
- On
- }
-
- [Serializable, NetSerializable]
- public enum GravityGeneratorPowerStatus : byte
- {
- Off,
- Discharging,
- Charging,
- FullyCharged
- }
+[NetworkedComponent()]
+[Virtual]
+public partial class SharedGravityGeneratorComponent : Component
+{
+ /// <summary>
+ /// A map of the sprites used by the gravity generator given its status.
+ /// </summary>
+ [DataField("spriteMap")]
+ [Access(typeof(SharedGravitySystem))]
+ public Dictionary<PowerChargeStatus, string> SpriteMap = new();
+
+ /// <summary>
+ /// The sprite used by the core of the gravity generator when the gravity generator is starting up.
+ /// </summary>
+ [DataField("coreStartupState")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string CoreStartupState = "startup";
+
+ /// <summary>
+ /// The sprite used by the core of the gravity generator when the gravity generator is idle.
+ /// </summary>
+ [DataField("coreIdleState")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string CoreIdleState = "idle";
+
+ /// <summary>
+ /// The sprite used by the core of the gravity generator when the gravity generator is activating.
+ /// </summary>
+ [DataField("coreActivatingState")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string CoreActivatingState = "activating";
+
+ /// <summary>
+ /// The sprite used by the core of the gravity generator when the gravity generator is active.
+ /// </summary>
+ [DataField("coreActivatedState")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string CoreActivatedState = "activated";
}
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power;
+
+/// <summary>
+/// Sent to the server to set whether the machine should be on or off
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class SwitchChargingMachineMessage : BoundUserInterfaceMessage
+{
+ public bool On;
+
+ public SwitchChargingMachineMessage(bool on)
+ {
+ On = on;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class PowerChargeState : BoundUserInterfaceState
+{
+ public bool On;
+ // 0 -> 255
+ public byte Charge;
+ public PowerChargePowerStatus PowerStatus;
+ public short PowerDraw;
+ public short PowerDrawMax;
+ public short EtaSeconds;
+
+ public PowerChargeState(
+ bool on,
+ byte charge,
+ PowerChargePowerStatus powerStatus,
+ short powerDraw,
+ short powerDrawMax,
+ short etaSeconds)
+ {
+ On = on;
+ Charge = charge;
+ PowerStatus = powerStatus;
+ PowerDraw = powerDraw;
+ PowerDrawMax = powerDrawMax;
+ EtaSeconds = etaSeconds;
+ }
+}
+
+[Serializable, NetSerializable]
+public enum PowerChargeUiKey
+{
+ Key
+}
+
+[Serializable, NetSerializable]
+public enum PowerChargeVisuals
+{
+ State,
+ Charge,
+ Active
+}
+
+[Serializable, NetSerializable]
+public enum PowerChargeStatus
+{
+ Broken,
+ Unpowered,
+ Off,
+ On
+}
+
+[Serializable, NetSerializable]
+public enum PowerChargePowerStatus : byte
+{
+ Off,
+ Discharging,
+ Charging,
+ FullyCharged
+}
--- /dev/null
+namespace Content.Shared.Power;
+
+/// <summary>
+/// Component for a powered machine that slowly powers on and off over a period of time.
+/// </summary>
+public abstract partial class SharedPowerChargeComponent : Component
+{
+ /// <summary>
+ /// The title used for the default charged machine window if used
+ /// </summary>
+ [DataField]
+ public LocId WindowTitle { get; set; } = string.Empty;
+
+}
using Content.Shared.Whitelist;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
if (!Resolve(gridUid, ref physics))
return true;
- if (physics.Mass < 10f)
+ if (physics.BodyType != BodyType.Static && physics.Mass < 10f)
{
return false;
}
--- /dev/null
+station-anchor-unanchoring-failed = Can't unanchor an active station anchor
+station-anchor-window-title = Station Anchor
--- /dev/null
+## UI field names
+
+power-charge-window-status = Status:
+power-charge-window-power = Power:
+power-charge-window-eta = ETA:
+power-charge-window-charge = Charge:
+
+## UI statuses
+power-charge-window-status-fully-charged = Fully Charged
+power-charge-window-status-off = Off
+power-charge-window-status-charging = Charging
+power-charge-window-status-discharging = Discharging
+
+## UI Power Buttons
+power-charge-window-power-on = On
+power-charge-window-power-off = Off
+power-charge-window-power-label = { $draw } / { $max } W
+
+## UI ETA label
+
+power-charge-window-eta-none = N/A
+power-charge-window-eta-value = { TOSTRING($left, "m\\:ss") }
CableHV: 5
Uranium: 2
+- type: entity
+ parent: BaseMachineCircuitboard
+ id: StationAnchorCircuitboard
+ name: station anchor machine board
+ description: A machine printed circuit board for a station anchor.
+ components:
+ - type: MachineBoard
+ prototype: StationAnchor
+ stackRequirements:
+ Capacitor: 4
+ MatterBin: 3
+ Steel: 10
+ Glass: 5
+ CableHV: 8
+ Uranium: 2
+
- type: entity
parent: BaseMachineCircuitboard
id: ReagentGrinderIndustrialMachineCircuitboard
behaviors:
- !type:DoActsBehavior
acts: ["Breakage"]
- - type: GravityGenerator
+ - type: PowerCharge
+ windowTitle: gravity-generator-window-title
idlePower: 50
activePower: 2500
+ - type: GravityGenerator
lightRadiusMin: 0.75
lightRadiusMax: 2.5
spriteMap:
unpowered: "off"
off: "off"
on: "on"
+ - type: ActivatableUI
+ key: enum.PowerChargeUiKey.Key
+ - type: ActivatableUIRequiresPower
- type: UserInterface
interfaces:
- enum.GravityGeneratorUiKey.Key:
- type: GravityGeneratorBoundUserInterface
+ enum.PowerChargeUiKey.Key:
+ type: PowerChargeBoundUserInterface
- type: Appearance
- type: PointLight
radius: 2.5
board: MiniGravityGeneratorCircuitboard
- type: ApcPowerReceiver
powerLoad: 500
- - type: GravityGenerator
+ - type: PowerCharge
idlePower: 15
activePower: 500
+ - type: GravityGenerator
lightRadiusMin: 0.75
lightRadiusMax: 2.5
- type: StaticPrice
- SodaDispenserMachineCircuitboard
- SpaceHeaterMachineCircuitBoard
- CutterMachineCircuitboard
+ - StationAnchorCircuitboard
dynamicRecipes:
- ThermomachineFreezerMachineCircuitBoard
- HellfireFreezerMachineCircuitBoard
--- /dev/null
+- type: entity
+ id: StationAnchorBase
+ abstract: true
+ name: station anchor
+ description: Prevents stations from moving
+ placement:
+ mode: AlignTileAny
+ components:
+ - type: StationAnchor
+ - type: Transform
+ anchored: true
+ - type: Physics
+ bodyType: Static
+ - type: AmbientSound
+ enabled: false
+ range: 4
+ volume: -4
+ sound:
+ path: /Audio/Effects/shuttle_thruster.ogg
+ - type: InteractionOutline
+ - type: Sprite
+ sprite: Structures/Machines/station_anchor.rsi
+ layers:
+ - state: station_anchor
+ map: ["base"]
+ - state: station_anchor_unlit
+ shader: unshaded
+ map: ["unlit"]
+ - type: GenericVisualizer
+ visuals:
+ enum.PowerChargeVisuals.Active:
+ unlit:
+ True: { visible: True }
+ False: { visible: False }
+ - type: Appearance
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.7,-0.8,0.7,0.8"
+ density: 190
+ mask:
+ - LargeMobMask
+ layer:
+ - WallLayer
+
+
+- type: entity
+ id: StationAnchorIndestructible
+ parent: StationAnchorBase
+ suffix: Indestructible, Unpowered
+
+- type: entity
+ id: StationAnchor
+ parent: [StationAnchorBase, BaseMachinePowered, ConstructibleMachine]
+ name: station anchor
+ description: Prevents stations from moving
+ placement:
+ mode: AlignTileAny
+ components:
+ - type: PowerCharge
+ windowTitle: station-anchor-window-title
+ idlePower: 50
+ activePower: 2500
+ chargeRate: 0.5
+ - type: ActivatableUI
+ key: enum.PowerChargeUiKey.Key
+ - type: ActivatableUIRequiresPower
+ - type: Anchorable
+ - type: ApcPowerReceiver
+ powerLoad: 2500
+ - type: ExtensionCableReceiver
+ - type: Damageable
+ damageContainer: Inorganic
+ damageModifierSet: Metallic
+ - type: Repairable
+ fuelCost: 10
+ doAfterDelay: 5
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 150
+ behaviors:
+ - !type:DoActsBehavior
+ acts: [ "Breakage" ]
+ - trigger:
+ !type:DamageTrigger
+ damage: 600
+ behaviors:
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+ - !type:PlaySoundBehavior
+ sound:
+ collection: MetalBreak
+ - type: StaticPrice
+ price: 10000
+ - type: Machine
+ board: StationAnchorCircuitboard
+ - type: ContainerContainer
+ containers:
+ machine_board: !type:Container
+ machine_parts: !type:Container
+ - type: Construction
+ containers:
+ - machine_parts
+ - machine_board
+ - type: UserInterface
+ interfaces:
+ enum.PowerChargeUiKey.Key:
+ type: PowerChargeBoundUserInterface
completetime: 6
materials:
Steel: 100
-
Glass: 500
- type: latheRecipe
Glass: 500
Gold: 100
+- type: latheRecipe
+ id: StationAnchorCircuitboard
+ result: StationAnchorCircuitboard
+ category: Circuitry
+ completetime: 8
+ materials:
+ Steel: 100
+ Glass: 900
+ Gold: 100
+
- type: latheRecipe
id: ReagentGrinderIndustrialMachineCircuitboard
result: ReagentGrinderIndustrialMachineCircuitboard
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made and posted by ubaser on the SS14 discord.",
+ "size": {
+ "x": 64,
+ "y": 64
+ },
+ "states": [
+ {
+ "name": "station_anchor"
+ },
+ {
+ "name": "station_anchor_unlit"
+ }
+ ]
+}