diff --git a/Content.Client/Power/Generator/GeneratorWindow.xaml.cs b/Content.Client/Power/Generator/GeneratorWindow.xaml.cs index d3949807b7..0b8f94ceae 100644 --- a/Content.Client/Power/Generator/GeneratorWindow.xaml.cs +++ b/Content.Client/Power/Generator/GeneratorWindow.xaml.cs @@ -14,6 +14,7 @@ public sealed partial class GeneratorWindow : FancyWindow [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly ILocalizationManager _loc = default!; + private readonly SharedPowerSwitchableSystem _switchable; private readonly FuelGeneratorComponent? _component; private PortableGeneratorComponentBuiState? _lastState; @@ -24,6 +25,7 @@ public sealed partial class GeneratorWindow : FancyWindow IoCManager.InjectDependencies(this); _entityManager.TryGetComponent(entity, out _component); + _switchable = _entityManager.System(); EntityView.SetEntity(entity); TargetPower.IsValid += IsValid; @@ -99,17 +101,16 @@ public sealed partial class GeneratorWindow : FancyWindow StatusLabel.SetOnlyStyleClass("Danger"); } - var canSwitch = _entityManager.TryGetComponent(_entity, out PowerSwitchableGeneratorComponent? switchable); + var canSwitch = _entityManager.TryGetComponent(_entity, out PowerSwitchableComponent? switchable); OutputSwitchLabel.Visible = canSwitch; OutputSwitchButton.Visible = canSwitch; - if (canSwitch) + if (switchable != null) { - 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"); + var voltage = _switchable.VoltageString(_switchable.GetVoltage(_entity, switchable)); + OutputSwitchLabel.Text = Loc.GetString("portable-generator-ui-current-output", ("voltage", voltage)); + var nextVoltage = _switchable.VoltageString(_switchable.GetNextVoltage(_entity, switchable)); + OutputSwitchButton.Text = Loc.GetString("power-switchable-switch-voltage", ("voltage", nextVoltage)); OutputSwitchButton.Disabled = state.On; } diff --git a/Content.Client/Power/Generator/PowerSwitchableSystem.cs b/Content.Client/Power/Generator/PowerSwitchableSystem.cs new file mode 100644 index 0000000000..b235dee77d --- /dev/null +++ b/Content.Client/Power/Generator/PowerSwitchableSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Power.Generator; + +namespace Content.Client.Power.Generator; + +public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem +{ +} diff --git a/Content.Server/Power/Generator/PortableGeneratorSystem.cs b/Content.Server/Power/Generator/PortableGeneratorSystem.cs index 416f509978..1180665ad1 100644 --- a/Content.Server/Power/Generator/PortableGeneratorSystem.cs +++ b/Content.Server/Power/Generator/PortableGeneratorSystem.cs @@ -22,7 +22,7 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly GeneratorSystem _generator = default!; - [Dependency] private readonly PowerSwitchableGeneratorSystem _switchableGenerator = default!; + [Dependency] private readonly PowerSwitchableSystem _switchable = default!; public override void Initialize() { @@ -36,6 +36,8 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem SubscribeLocalEvent(GeneratorStartMessage); SubscribeLocalEvent(GeneratorStopMessage); SubscribeLocalEvent(GeneratorSwitchOutputMessage); + + SubscribeLocalEvent(OnSwitchPowerCheck); } private void GeneratorSwitchOutputMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorSwitchOutputMessage args) @@ -47,7 +49,7 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem if (fuelGenerator.On) return; - _switchableGenerator.ToggleActiveOutput(uid, args.Session.AttachedEntity.Value); + _switchable.Cycle(uid, args.Session.AttachedEntity.Value); } private void GeneratorStopMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorStopMessage args) @@ -164,6 +166,12 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem } } + private void OnSwitchPowerCheck(EntityUid uid, FuelGeneratorComponent comp, ref SwitchPowerCheckEvent args) + { + if (comp.On) + args.DisableMessage = Loc.GetString("fuel-generator-verb-disable-on"); + } + public override void Update(float frameTime) { var query = EntityQueryEnumerator(); diff --git a/Content.Server/Power/Generator/PowerSwitchableGeneratorSystem.cs b/Content.Server/Power/Generator/PowerSwitchableGeneratorSystem.cs deleted file mode 100644 index b81d77ea9f..0000000000 --- a/Content.Server/Power/Generator/PowerSwitchableGeneratorSystem.cs +++ /dev/null @@ -1,111 +0,0 @@ -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; - -/// -/// Implements power-switchable generators. -/// -/// -/// -/// -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>(GetInteractionVerbs); - } - - private void GetInteractionVerbs( - EntityUid uid, - PowerSwitchableGeneratorComponent component, - GetVerbsEvent 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(uid); - var nodeContainer = Comp(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); - } -} diff --git a/Content.Server/Power/Generator/PowerSwitchableSystem.cs b/Content.Server/Power/Generator/PowerSwitchableSystem.cs new file mode 100644 index 0000000000..ae7960cf8f --- /dev/null +++ b/Content.Server/Power/Generator/PowerSwitchableSystem.cs @@ -0,0 +1,123 @@ +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.Timing; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Server.Power.Generator; + +/// +/// Implements server logic for power-switchable devices. +/// +/// +/// +/// +public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem +{ + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly NodeGroupSystem _nodeGroup = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly UseDelaySystem _useDelay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(GetVerbs); + } + + private void GetVerbs(EntityUid uid, PowerSwitchableComponent comp, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var voltage = VoltageColor(GetNextVoltage(uid, comp)); + var msg = Loc.GetString("power-switchable-switch-voltage", ("voltage", voltage)); + + InteractionVerb verb = new() + { + Act = () => + { + // don't need to check it again since if its disabled server wont let the verb act + Cycle(uid, args.User, comp); + }, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")), + Text = msg + }; + + var ev = new SwitchPowerCheckEvent(); + RaiseLocalEvent(uid, ref ev); + if (ev.DisableMessage != null) + { + verb.Message = ev.DisableMessage; + verb.Disabled = true; + } + + args.Verbs.Add(verb); + } + + /// + /// Cycles voltage then updates nodes and optionally power supplier to match it. + /// + public void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return; + + // no sound spamming + if (TryComp(uid, out var useDelay) && _useDelay.ActiveDelay(uid)) + return; + + comp.ActiveIndex = NextIndex(uid, comp); + Dirty(uid, comp); + + var voltage = GetVoltage(uid, comp); + + if (TryComp(uid, out var supplier)) + { + // convert to nodegroupid (goofy server Voltage enum is just alias for it) + switch (voltage) + { + case SwitchableVoltage.HV: + supplier.Voltage = Voltage.High; + break; + case SwitchableVoltage.MV: + supplier.Voltage = Voltage.Medium; + break; + case SwitchableVoltage.LV: + supplier.Voltage = Voltage.Apc; + break; + } + } + + // 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. + var nodeContainer = Comp(uid); + foreach (var cable in comp.Cables) + { + var node = (CableDeviceNode) nodeContainer.Nodes[cable.Node]; + node.Enabled = cable.Voltage == voltage; + _nodeGroup.QueueReflood(node); + } + + var popup = Loc.GetString(comp.SwitchText, ("voltage", VoltageString(voltage))); + _popup.PopupEntity(popup, uid, user); + + _audio.PlayPvs(comp.SwitchSound, uid); + + _useDelay.BeginDelay(uid, useDelay); + } +} + +/// +/// Raised on a to see if its verb should work. +/// If is non-null, the verb is disabled with that as the message. +/// +[ByRefEvent] +public record struct SwitchPowerCheckEvent(string? DisableMessage = null); diff --git a/Content.Shared/Power/Generator/PowerSwitchableComponent.cs b/Content.Shared/Power/Generator/PowerSwitchableComponent.cs new file mode 100644 index 0000000000..39118b21e5 --- /dev/null +++ b/Content.Shared/Power/Generator/PowerSwitchableComponent.cs @@ -0,0 +1,82 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Power.Generator; + +/// +/// Enables a device to switch between HV, MV and LV connectors. +/// For generators this means changing output wires. +/// +/// +/// Must have CableDeviceNodes for each output in . +/// If its a generator PowerSupplierComponent is also required. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedPowerSwitchableSystem))] +public sealed partial class PowerSwitchableComponent : Component +{ + /// + /// Index into that the device is currently using. + /// + [DataField, AutoNetworkedField] + public int ActiveIndex; + + /// + /// Sound that plays when the cable is switched. + /// + [DataField] + public SoundSpecifier? SwitchSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); + + /// + /// Locale id for text shown when examined. + /// It is given "voltage" as a colored voltage string. + /// + [DataField(required: true)] + public string ExamineText = string.Empty; + + /// + /// Locale id for the popup shown when switching voltages. + /// It is given "voltage" as a colored voltage string. + /// + [DataField(required: true)] + public string SwitchText = string.Empty; + + /// + /// Cable voltages and their nodes which can be cycled between. + /// Each node name must match a cable node in its NodeContainer. + /// + [DataField(required: true)] + public List Cables = new(); +} + +/// +/// Cable voltage and node name for cycling. +/// +[DataDefinition] +public sealed partial class PowerSwitchableCable +{ + /// + /// Voltage that the cable uses. + /// + [DataField(required: true)] + public SwitchableVoltage Voltage; + + /// + /// Name of the node for the cable. + /// Must be a CableDeviceNode + /// + [DataField(required: true)] + public string Node = string.Empty; +} + +/// +/// Cable voltage to cycle between. +/// +[Serializable, NetSerializable] +public enum SwitchableVoltage : byte +{ + HV, + MV, + LV +} diff --git a/Content.Shared/Power/Generator/PowerSwitchableGeneratorComponent.cs b/Content.Shared/Power/Generator/PowerSwitchableGeneratorComponent.cs deleted file mode 100644 index 464538f507..0000000000 --- a/Content.Shared/Power/Generator/PowerSwitchableGeneratorComponent.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Power.Generator; - -/// -/// Enables a generator to switch between HV and MV output. -/// -/// -/// Must have CableDeviceNodes for both and , and also a PowerSupplierComponent. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -[Access(typeof(SharedPowerSwitchableGeneratorSystem))] -public sealed partial class PowerSwitchableGeneratorComponent : Component -{ - /// - /// Which output the portable generator is currently connected to. - /// - [DataField("activeOutput")] - [AutoNetworkedField] - public PowerSwitchableGeneratorOutput ActiveOutput { get; set; } - - /// - /// Sound that plays when the output is switched. - /// - /// - [DataField("switchSound")] - public SoundSpecifier? SwitchSound { get; set; } - - /// - /// Which node is the MV output? - /// - [DataField("nodeOutputMV")] - public string NodeOutputMV { get; set; } = "output_mv"; - - /// - /// Which node is the HV output? - /// - [DataField("nodeOutputHV")] - public string NodeOutputHV { get; set; } = "output_hv"; -} - -/// -/// Possible power output for power-switchable generators. -/// -/// -[Serializable, NetSerializable] -public enum PowerSwitchableGeneratorOutput : byte -{ - /// - /// The generator is set to connect to a high-voltage power network. - /// - HV, - - /// - /// The generator is set to connect to a medium-voltage power network. - /// - MV -} - diff --git a/Content.Shared/Power/Generator/SharedPowerSwitchableGeneratorSystem.cs b/Content.Shared/Power/Generator/SharedPowerSwitchableGeneratorSystem.cs deleted file mode 100644 index 5d3793c471..0000000000 --- a/Content.Shared/Power/Generator/SharedPowerSwitchableGeneratorSystem.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Examine; - -namespace Content.Shared.Power.Generator; - -/// -/// Shared logic for power-switchable generators. -/// -/// -public abstract class SharedPowerSwitchableGeneratorSystem : EntitySystem -{ - public override void Initialize() - { - SubscribeLocalEvent(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()))); - } -} diff --git a/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs b/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs new file mode 100644 index 0000000000..c1787b6078 --- /dev/null +++ b/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs @@ -0,0 +1,72 @@ +using Content.Shared.Examine; + +namespace Content.Shared.Power.Generator; + +/// +/// Shared logic for power-switchable devices. +/// +/// +public abstract class SharedPowerSwitchableSystem : EntitySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, PowerSwitchableComponent comp, ExaminedEvent args) + { + // Show which voltage is currently selected. + var voltage = VoltageColor(GetVoltage(uid, comp)); + args.PushMarkup(Loc.GetString(comp.ExamineText, ("voltage", voltage))); + } + + /// + /// Helper to get the colored markup string for a voltage type. + /// + public string VoltageColor(SwitchableVoltage voltage) + { + return Loc.GetString("power-switchable-voltage", ("voltage", VoltageString(voltage))); + } + + /// + /// Converts from "hv" to "HV" since for some reason the enum gets made lowercase??? + /// + public string VoltageString(SwitchableVoltage voltage) + { + return voltage.ToString().ToUpper(); + } + + /// + /// Returns index of the next cable type index to cycle to. + /// + public int NextIndex(EntityUid uid, PowerSwitchableComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return 0; + + // loop back at the end + return (comp.ActiveIndex + 1) % comp.Cables.Count; + } + + /// + /// Returns the current cable voltage being used by a power-switchable device. + /// + public SwitchableVoltage GetVoltage(EntityUid uid, PowerSwitchableComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return default; + + return comp.Cables[comp.ActiveIndex].Voltage; + } + + /// + /// Returns the cable's next voltage to cycle to being used by a power-switchable device. + /// + public SwitchableVoltage GetNextVoltage(EntityUid uid, PowerSwitchableComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return default; + + return comp.Cables[NextIndex(uid, comp)].Voltage; + } +} diff --git a/Resources/Locale/en-US/power/components/generator.ftl b/Resources/Locale/en-US/power/components/generator.ftl index 2a8016287f..c66db1086b 100644 --- a/Resources/Locale/en-US/power/components/generator.ftl +++ b/Resources/Locale/en-US/power/components/generator.ftl @@ -22,19 +22,16 @@ 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 +portable-generator-ui-current-output = Current output: {$voltage} -power-switchable-generator-examine = The power output is set to { $output -> -[HV] [color=orange]HV[/color] -*[MV] [color=yellow]MV[/color] - }. +power-switchable-generator-examine = The power output is set to {$voltage}. +power-switchable-generator-switched = Switched output to {$voltage}! -portable-generator-ui-switch-hv = Current output: HV -portable-generator-ui-switch-mv = Current output: MV +power-switchable-voltage = { $voltage -> + [HV] [color=orange]HV[/color] + [MV] [color=yellow]MV[/color] + *[LV] [color=green]LV[/color] +} +power-switchable-switch-voltage = Switch to {$voltage} -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! +fuel-generator-verb-disable-on = Turn the generator off first! diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml index 261b9a344c..c49497b774 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml @@ -96,19 +96,26 @@ 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: PowerSwitchable + examineText: power-switchable-generator-examine + switchText: power-switchable-generator-switched + cables: + - voltage: HV + node: output_hv + - voltage: MV + node: output_mv + - type: UseDelay + delay: 1 + - 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