diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs index bf6b65a9697b5a..d5bc764b348c8e 100644 --- a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs +++ b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs @@ -1,4 +1,5 @@ using Content.Shared.Doors.Components; +using Content.Shared.Electrocution; using Content.Shared.Silicons.StationAi; using Robust.Shared.Utility; @@ -6,25 +7,69 @@ namespace Content.Client.Silicons.StationAi; public sealed partial class StationAiSystem { + private readonly ResPath _aiActionsRsi = new ResPath("/Textures/Interface/Actions/actions_ai.rsi"); + private void InitializeAirlock() { SubscribeLocalEvent(OnDoorBoltGetRadial); + SubscribeLocalEvent(OnEmergencyAccessGetRadial); + SubscribeLocalEvent(OnDoorElectrifiedGetRadial); } private void OnDoorBoltGetRadial(Entity ent, ref GetStationAiRadialEvent args) { - args.Actions.Add(new StationAiRadial() - { - Sprite = ent.Comp.BoltsDown ? - new SpriteSpecifier.Rsi( - new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") : - new SpriteSpecifier.Rsi( - new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"), - Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"), - Event = new StationAiBoltEvent() + args.Actions.Add( + new StationAiRadial + { + Sprite = ent.Comp.BoltsDown + ? new SpriteSpecifier.Rsi(_aiActionsRsi, "unbolt_door") + : new SpriteSpecifier.Rsi(_aiActionsRsi, "bolt_door"), + Tooltip = ent.Comp.BoltsDown + ? Loc.GetString("bolt-open") + : Loc.GetString("bolt-close"), + Event = new StationAiBoltEvent + { + Bolted = !ent.Comp.BoltsDown, + } + } + ); + } + + private void OnEmergencyAccessGetRadial(Entity ent, ref GetStationAiRadialEvent args) + { + args.Actions.Add( + new StationAiRadial + { + Sprite = ent.Comp.EmergencyAccess + ? new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_off") + : new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_on"), + Tooltip = ent.Comp.EmergencyAccess + ? Loc.GetString("emergency-access-off") + : Loc.GetString("emergency-access-on"), + Event = new StationAiEmergencyAccessEvent + { + EmergencyAccess = !ent.Comp.EmergencyAccess, + } + } + ); + } + + private void OnDoorElectrifiedGetRadial(Entity ent, ref GetStationAiRadialEvent args) + { + args.Actions.Add( + new StationAiRadial { - Bolted = !ent.Comp.BoltsDown, + Sprite = ent.Comp.Enabled + ? new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_off") + : new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_on"), + Tooltip = ent.Comp.Enabled + ? Loc.GetString("electrify-door-off") + : Loc.GetString("electrify-door-on"), + Event = new StationAiElectrifiedEvent + { + Electrified = !ent.Comp.Enabled, + } } - }); + ); } } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index fef8a031d9da9a..56cf2878506060 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -90,22 +90,22 @@ private void AddTricksVerbs(GetVerbsEvent args) args.Verbs.Add(bolt); } - if (TryComp(args.Target, out var airlock)) + if (TryComp(args.Target, out var airlockComp)) { Verb emergencyAccess = new() { - Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On", + Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On", Category = VerbCategory.Tricks, Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")), Act = () => { - _airlockSystem.ToggleEmergencyAccess(args.Target, airlock); + _airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess); }, Impact = LogImpact.Medium, - Message = Loc.GetString(airlock.EmergencyAccess + Message = Loc.GetString(airlockComp.EmergencyAccess ? "admin-trick-emergency-access-off-description" : "admin-trick-emergency-access-on-description"), - Priority = (int) (airlock.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn), + Priority = (int) (airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn), }; args.Verbs.Add(emergencyAccess); } diff --git a/Content.Server/Construction/Completions/AttemptElectrocute.cs b/Content.Server/Construction/Completions/AttemptElectrocute.cs index 05f0977b662788..5c97d5e90fec98 100644 --- a/Content.Server/Construction/Completions/AttemptElectrocute.cs +++ b/Content.Server/Construction/Completions/AttemptElectrocute.cs @@ -1,4 +1,5 @@ using Content.Server.Electrocution; +using Content.Shared.Electrocution; using Content.Shared.Construction; namespace Content.Server.Construction.Completions; diff --git a/Content.Server/Doors/WireActions/DoorBoltWireAction.cs b/Content.Server/Doors/WireActions/DoorBoltWireAction.cs index fc1cf50cd87d5c..80555f68f9b63a 100644 --- a/Content.Server/Doors/WireActions/DoorBoltWireAction.cs +++ b/Content.Server/Doors/WireActions/DoorBoltWireAction.cs @@ -2,7 +2,6 @@ using Content.Server.Wires; using Content.Shared.Doors; using Content.Shared.Doors.Components; -using Content.Shared.Doors.Systems; using Content.Shared.Wires; namespace Content.Server.Doors; diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 67e60c9de4660c..88404c4aa96052 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -488,4 +488,15 @@ private void PlayElectrocutionSound(EntityUid targetUid, EntityUid sourceUid, El } _audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume)); } + + public void SetElectrifiedWireCut(Entity ent, bool value) + { + if (ent.Comp.IsWireCut == value) + { + return; + } + + ent.Comp.IsWireCut = value; + Dirty(ent); + } } diff --git a/Content.Server/Power/PowerWireAction.cs b/Content.Server/Power/PowerWireAction.cs index ac34966036cc97..cebb7de8ec8685 100644 --- a/Content.Server/Power/PowerWireAction.cs +++ b/Content.Server/Power/PowerWireAction.cs @@ -1,4 +1,5 @@ using Content.Server.Electrocution; +using Content.Shared.Electrocution; using Content.Server.Power.Components; using Content.Server.Wires; using Content.Shared.Power; @@ -104,6 +105,7 @@ private void SetElectrified(EntityUid used, bool setting, ElectrifiedComponent? && !EntityManager.TryGetComponent(used, out electrified)) return; + _electrocutionSystem.SetElectrifiedWireCut((used, electrified), setting); electrified.Enabled = setting; } diff --git a/Content.Server/Remotes/DoorRemoteSystem.cs b/Content.Server/Remotes/DoorRemoteSystem.cs index 67160650871d4f..de327bd084038a 100644 --- a/Content.Server/Remotes/DoorRemoteSystem.cs +++ b/Content.Server/Remotes/DoorRemoteSystem.cs @@ -74,7 +74,7 @@ private void OnBeforeInteract(Entity entity, ref BeforeRang case OperatingMode.ToggleEmergencyAccess: if (airlockComp != null) { - _airlock.ToggleEmergencyAccess(args.Target.Value, airlockComp); + _airlock.SetEmergencyAccess((args.Target.Value, airlockComp), !airlockComp.EmergencyAccess); _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}"); } diff --git a/Content.Shared/Doors/Components/AirlockComponent.cs b/Content.Shared/Doors/Components/AirlockComponent.cs index 6577b1942ac927..6b3fcfad7e436d 100644 --- a/Content.Shared/Doors/Components/AirlockComponent.cs +++ b/Content.Shared/Doors/Components/AirlockComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.DeviceLinking; using Content.Shared.Doors.Systems; +using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -23,6 +24,18 @@ public sealed partial class AirlockComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField] public bool EmergencyAccess = false; + + /// + /// Sound to play when the airlock emergency access is turned on. + /// + [DataField] + public SoundSpecifier EmergencyOnSound = new SoundPathSpecifier("/Audio/Machines/airlock_emergencyon.ogg"); + + /// + /// Sound to play when the airlock emergency access is turned off. + /// + [DataField] + public SoundSpecifier EmergencyOffSound = new SoundPathSpecifier("/Audio/Machines/airlock_emergencyoff.ogg"); /// /// Pry modifier for a powered airlock. diff --git a/Content.Shared/Doors/Systems/SharedAirlockSystem.cs b/Content.Shared/Doors/Systems/SharedAirlockSystem.cs index 5a9cde74ee10fd..e404a91bdd74ff 100644 --- a/Content.Shared/Doors/Systems/SharedAirlockSystem.cs +++ b/Content.Shared/Doors/Systems/SharedAirlockSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Doors.Components; +using Robust.Shared.Audio.Systems; using Content.Shared.Popups; using Content.Shared.Prying.Components; using Content.Shared.Wires; @@ -10,7 +11,9 @@ public abstract class SharedAirlockSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; + [Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedDoorSystem DoorSystem = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] private readonly SharedWiresSystem _wiresSystem = default!; public override void Initialize() @@ -131,11 +134,23 @@ public void UpdateEmergencyLightStatus(EntityUid uid, AirlockComponent component Appearance.SetData(uid, DoorVisuals.EmergencyLights, component.EmergencyAccess); } - public void ToggleEmergencyAccess(EntityUid uid, AirlockComponent component) + public void SetEmergencyAccess(Entity ent, bool value, EntityUid? user = null, bool predicted = false) { - component.EmergencyAccess = !component.EmergencyAccess; - Dirty(uid, component); // This only runs on the server apparently so we need this. - UpdateEmergencyLightStatus(uid, component); + if(!ent.Comp.Powered) + return; + + if (ent.Comp.EmergencyAccess == value) + return; + + ent.Comp.EmergencyAccess = value; + Dirty(ent, ent.Comp); // This only runs on the server apparently so we need this. + UpdateEmergencyLightStatus(ent, ent.Comp); + + var sound = ent.Comp.EmergencyAccess ? ent.Comp.EmergencyOnSound : ent.Comp.EmergencyOffSound; + if (predicted) + Audio.PlayPredicted(sound, ent, user: user); + else + Audio.PlayPvs(sound, ent); } public void SetAutoCloseDelayModifier(AirlockComponent component, float value) diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.Bolts.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.Bolts.cs index 35681bfd822931..13050616e1ba5e 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.Bolts.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.Bolts.cs @@ -77,8 +77,20 @@ public void SetBoltLightsEnabled(Entity ent, bool value) public void SetBoltsDown(Entity ent, bool value, EntityUid? user = null, bool predicted = false) { + TrySetBoltDown(ent, value, user, predicted); + } + + public bool TrySetBoltDown( + Entity ent, + bool value, + EntityUid? user = null, + bool predicted = false + ) + { + if (!_powerReceiver.IsPowered(ent.Owner)) + return false; if (ent.Comp.BoltsDown == value) - return; + return false; ent.Comp.BoltsDown = value; Dirty(ent, ent.Comp); @@ -89,6 +101,7 @@ public void SetBoltsDown(Entity ent, bool value, EntityUid? u Audio.PlayPredicted(sound, ent, user: user); else Audio.PlayPvs(sound, ent); + return true; } private void OnStateChanged(Entity entity, ref DoorStateChangedEvent args) diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index 2319d5e916b2e5..3d9a721473516f 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Power.EntitySystems; using Content.Shared.Prying.Components; using Content.Shared.Prying.Systems; using Content.Shared.Stunnable; @@ -42,6 +43,7 @@ public abstract partial class SharedDoorSystem : EntitySystem [Dependency] private readonly PryingSystem _pryingSystem = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!; [ValidatePrototypeId] diff --git a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs b/Content.Shared/Electrocution/Components/ElectrifiedComponent.cs similarity index 65% rename from Content.Server/Electrocution/Components/ElectrifiedComponent.cs rename to Content.Shared/Electrocution/Components/ElectrifiedComponent.cs index 5755e98091b578..52eb76ca541354 100644 --- a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs +++ b/Content.Shared/Electrocution/Components/ElectrifiedComponent.cs @@ -1,121 +1,131 @@ +using Robust.Shared.GameStates; using Robust.Shared.Audio; -namespace Content.Server.Electrocution; +namespace Content.Shared.Electrocution; /// /// Component for things that shock users on touch. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class ElectrifiedComponent : Component { - [DataField("enabled")] + [DataField, AutoNetworkedField] public bool Enabled = true; /// /// Should player get damage on collide /// - [DataField("onBump")] + [DataField, AutoNetworkedField] public bool OnBump = true; /// /// Should player get damage on attack /// - [DataField("onAttacked")] + [DataField, AutoNetworkedField] public bool OnAttacked = true; /// /// When true - disables power if a window is present in the same tile /// - [DataField("noWindowInTile")] + [DataField, AutoNetworkedField] public bool NoWindowInTile = false; /// /// Should player get damage on interact with empty hand /// - [DataField("onHandInteract")] + [DataField, AutoNetworkedField] public bool OnHandInteract = true; /// /// Should player get damage on interact while holding an object in their hand /// - [DataField("onInteractUsing")] + [DataField, AutoNetworkedField] public bool OnInteractUsing = true; /// /// Indicates if the entity requires power to function /// - [DataField("requirePower")] + [DataField, AutoNetworkedField] public bool RequirePower = true; /// /// Indicates if the entity uses APC power /// - [DataField("usesApcPower")] + [DataField, AutoNetworkedField] public bool UsesApcPower = false; /// /// Identifier for the high voltage node. /// - [DataField("highVoltageNode")] + [DataField, AutoNetworkedField] public string? HighVoltageNode; /// /// Identifier for the medium voltage node. /// - [DataField("mediumVoltageNode")] + [DataField, AutoNetworkedField] public string? MediumVoltageNode; /// /// Identifier for the low voltage node. /// - [DataField("lowVoltageNode")] + [DataField, AutoNetworkedField] public string? LowVoltageNode; /// /// Damage multiplier for HV electrocution /// - [DataField] + [DataField, AutoNetworkedField] public float HighVoltageDamageMultiplier = 3f; /// /// Shock time multiplier for HV electrocution /// - [DataField] + [DataField, AutoNetworkedField] public float HighVoltageTimeMultiplier = 1.5f; /// /// Damage multiplier for MV electrocution /// - [DataField] + [DataField, AutoNetworkedField] public float MediumVoltageDamageMultiplier = 2f; /// /// Shock time multiplier for MV electrocution /// - [DataField] + [DataField, AutoNetworkedField] public float MediumVoltageTimeMultiplier = 1.25f; - [DataField("shockDamage")] + [DataField, AutoNetworkedField] public float ShockDamage = 7.5f; /// /// Shock time, in seconds. /// - [DataField("shockTime")] + [DataField, AutoNetworkedField] public float ShockTime = 8f; - [DataField("siemensCoefficient")] + [DataField, AutoNetworkedField] public float SiemensCoefficient = 1f; - [DataField("shockNoises")] + [DataField, AutoNetworkedField] public SoundSpecifier ShockNoises = new SoundCollectionSpecifier("sparks"); - [DataField("playSoundOnShock")] + [DataField, AutoNetworkedField] + public SoundPathSpecifier AirlockElectrifyDisabled = new("/Audio/Machines/airlock_electrify_on.ogg"); + + [DataField, AutoNetworkedField] + public SoundPathSpecifier AirlockElectrifyEnabled = new("/Audio/Machines/airlock_electrify_off.ogg"); + + [DataField, AutoNetworkedField] public bool PlaySoundOnShock = true; - [DataField("shockVolume")] + [DataField, AutoNetworkedField] public float ShockVolume = 20; - [DataField] + [DataField, AutoNetworkedField] public float Probability = 1f; + + [DataField, AutoNetworkedField] + public bool IsWireCut = false; } diff --git a/Content.Shared/Electrocution/SharedElectrocutionSystem.cs b/Content.Shared/Electrocution/SharedElectrocutionSystem.cs index b228a987af4af6..e36e4a804b7ee1 100644 --- a/Content.Shared/Electrocution/SharedElectrocutionSystem.cs +++ b/Content.Shared/Electrocution/SharedElectrocutionSystem.cs @@ -23,6 +23,20 @@ public void SetInsulatedSiemensCoefficient(EntityUid uid, float siemensCoefficie Dirty(uid, insulated); } + /// + /// Sets electrified value of component and marks dirty if required. + /// + public void SetElectrified(Entity ent, bool value) + { + if (ent.Comp.Enabled == value) + { + return; + } + + ent.Comp.Enabled = value; + Dirty(ent, ent.Comp); + } + /// Entity being electrocuted. /// Source entity of the electrocution. /// How much shock damage the entity takes. diff --git a/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs index 2bc2af78314352..b7ba2a31c5d94e 100644 --- a/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs +++ b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using Content.Shared.Examine; using Content.Shared.Power.Components; namespace Content.Shared.Power.EntitySystems; @@ -8,6 +7,9 @@ public abstract class SharedPowerReceiverSystem : EntitySystem { public abstract bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component); + /// + /// Checks if entity is APC-powered device, and if it have power. + /// public bool IsPowered(Entity entity) { if (!ResolveApc(entity.Owner, ref entity.Comp)) diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs index ff6fc1ece07967..37e5cd6e6aed9c 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs @@ -1,5 +1,6 @@ using Content.Shared.Doors.Components; using Robust.Shared.Serialization; +using Content.Shared.Electrocution; namespace Content.Shared.Silicons.StationAi; @@ -10,16 +11,84 @@ public abstract partial class SharedStationAiSystem private void InitializeAirlock() { SubscribeLocalEvent(OnAirlockBolt); + SubscribeLocalEvent(OnAirlockEmergencyAccess); + SubscribeLocalEvent(OnElectrified); } + /// + /// Attempts to bolt door. If wire was cut (AI or for bolts) or its not powered - notifies AI and does nothing. + /// private void OnAirlockBolt(EntityUid ent, DoorBoltComponent component, StationAiBoltEvent args) { - _doors.SetBoltsDown((ent, component), args.Bolted, args.User, predicted: true); + if (component.BoltWireCut) + { + ShowDeviceNotRespondingPopup(args.User); + return; + } + + var setResult = _doors.TrySetBoltDown((ent, component), args.Bolted, args.User, predicted: true); + if (!setResult) + { + ShowDeviceNotRespondingPopup(args.User); + } + } + + /// + /// Attempts to bolt door. If wire was cut (AI) or its not powered - notifies AI and does nothing. + /// + private void OnAirlockEmergencyAccess(EntityUid ent, AirlockComponent component, StationAiEmergencyAccessEvent args) + { + if (!PowerReceiver.IsPowered(ent)) + { + ShowDeviceNotRespondingPopup(args.User); + return; + } + + _airlocks.SetEmergencyAccess((ent, component), args.EmergencyAccess, args.User, predicted: true); + } + + /// + /// Attempts to bolt door. If wire was cut (AI or for one of power-wires) or its not powered - notifies AI and does nothing. + /// + private void OnElectrified(EntityUid ent, ElectrifiedComponent component, StationAiElectrifiedEvent args) + { + if ( + component.IsWireCut + || !PowerReceiver.IsPowered(ent) + ) + { + ShowDeviceNotRespondingPopup(args.User); + return; + } + + _electrify.SetElectrified((ent, component), args.Electrified); + var soundToPlay = component.Enabled + ? component.AirlockElectrifyDisabled + : component.AirlockElectrifyEnabled; + _audio.PlayLocal(soundToPlay, ent, args.User); } } +/// Event for StationAI attempt at bolting/unbolting door. [Serializable, NetSerializable] public sealed class StationAiBoltEvent : BaseStationAiAction { + /// Marker, should be door bolted or unbolted. public bool Bolted; } + +/// Event for StationAI attempt at setting emergency access for door on/off. +[Serializable, NetSerializable] +public sealed class StationAiEmergencyAccessEvent : BaseStationAiAction +{ + /// Marker, should door have emergency access on or off. + public bool EmergencyAccess; +} + +/// Event for StationAI attempt at electrifying/de-electrifying door. +[Serializable, NetSerializable] +public sealed class StationAiElectrifiedEvent : BaseStationAiAction +{ + /// Marker, should door be electrified or no. + public bool Electrified; +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index c59c4723079aa5..e067cf3efadb60 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -2,6 +2,7 @@ using Content.Shared.Actions.Events; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; +using Content.Shared.Popups; using Content.Shared.Verbs; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -13,9 +14,9 @@ public abstract partial class SharedStationAiSystem /* * Added when an entity is inserted into a StationAiCore. */ - - //TODO: Fix this, please - private const string JobNameLocId = "job-name-station-ai"; + + //TODO: Fix this, please + private const string JobNameLocId = "job-name-station-ai"; private void InitializeHeld() { @@ -26,10 +27,10 @@ private void InitializeHeld() SubscribeLocalEvent(OnHeldInteraction); SubscribeLocalEvent(OnHeldRelay); SubscribeLocalEvent(OnCoreJump); - SubscribeLocalEvent(OnTryGetIdentityShortInfo); + SubscribeLocalEvent(OnTryGetIdentityShortInfo); } - - private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args) + + private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args) { if (args.Handled) { @@ -40,7 +41,7 @@ private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args) { return; } - args.Title = $"{Name(args.ForActor)} ({Loc.GetString(JobNameLocId)})"; + args.Title = $"{Name(args.ForActor)} ({Loc.GetString(JobNameLocId)})"; args.Handled = true; } @@ -108,41 +109,56 @@ private void OnMessageAttempt(BoundUserInterfaceMessageAttempt ev) return; if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) && - (!ValidateAi((ev.Actor, aiComp)) || - !HasComp(ev.Target))) + (!TryComp(ev.Target, out StationAiWhitelistComponent? whitelistComponent) || + !ValidateAi((ev.Actor, aiComp)))) { + if (whitelistComponent is { Enabled: false }) + { + ShowDeviceNotRespondingPopup(ev.Actor); + } ev.Cancel(); } } private void OnHeldInteraction(Entity ent, ref InteractionAttemptEvent args) { - // Cancel if it's not us or something with a whitelist. - args.Cancelled = ent.Owner != args.Target && - args.Target != null && - (!TryComp(args.Target, out StationAiWhitelistComponent? whitelist) || !whitelist.Enabled); + // Cancel if it's not us or something with a whitelist, or whitelist is disabled. + args.Cancelled = (!TryComp(args.Target, out StationAiWhitelistComponent? whitelistComponent) + || !whitelistComponent.Enabled) + && ent.Owner != args.Target + && args.Target != null; + if (whitelistComponent is { Enabled: false }) + { + ShowDeviceNotRespondingPopup(ent.Owner); + } } private void OnTargetVerbs(Entity ent, ref GetVerbsEvent args) { - if (!args.CanComplexInteract || - !ent.Comp.Enabled || - !HasComp(args.User) || - !HasComp(args.Target)) + if (!args.CanComplexInteract + || !HasComp(args.User)) { return; } var user = args.User; + var target = args.Target; var isOpen = _uiSystem.IsUiOpen(target, AiUi.Key, user); - args.Verbs.Add(new AlternativeVerb() + var verb = new AlternativeVerb { Text = isOpen ? Loc.GetString("ai-close") : Loc.GetString("ai-open"), - Act = () => + Act = () => { + // no need to show menu if device is not powered. + if (!PowerReceiver.IsPowered(ent.Owner)) + { + ShowDeviceNotRespondingPopup(user); + return; + } + if (isOpen) { _uiSystem.CloseUi(ent.Owner, AiUi.Key, user); @@ -152,7 +168,13 @@ private void OnTargetVerbs(Entity ent, ref GetVerbs _uiSystem.OpenUi(ent.Owner, AiUi.Key, user); } } - }); + }; + args.Verbs.Add(verb); + } + + private void ShowDeviceNotRespondingPopup(EntityUid toEntity) + { + _popup.PopupClient(Loc.GetString("ai-device-not-responding"), toEntity, PopupType.MediumCaution); } } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 17c592879c8f99..baef62c3da98ec 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -4,14 +4,18 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Database; using Content.Shared.Doors.Systems; +using Content.Shared.Electrocution; using Content.Shared.Interaction; using Content.Shared.Item.ItemToggle; using Content.Shared.Mind; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; +using Content.Shared.Popups; using Content.Shared.Power; +using Content.Shared.Power.EntitySystems; using Content.Shared.StationAi; using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map.Components; using Robust.Shared.Network; @@ -24,23 +28,28 @@ namespace Content.Shared.Silicons.StationAi; public abstract partial class SharedStationAiSystem : EntitySystem { - [Dependency] private readonly ISharedAdminManager _admin = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly INetManager _net = default!; - [Dependency] private readonly ItemSlotsSystem _slots = default!; - [Dependency] private readonly ItemToggleSystem _toggles = default!; - [Dependency] private readonly ActionBlockerSystem _blocker = default!; - [Dependency] private readonly MetaDataSystem _metadata = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedContainerSystem _containers = default!; - [Dependency] private readonly SharedDoorSystem _doors = default!; - [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly ISharedAdminManager _admin = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly ItemToggleSystem _toggles = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly SharedAirlockSystem _airlocks = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly SharedDoorSystem _doors = default!; + [Dependency] private readonly SharedElectrocutionSystem _electrify = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; [Dependency] protected readonly SharedMapSystem Maps = default!; - [Dependency] private readonly SharedMindSystem _mind = default!; - [Dependency] private readonly SharedMoverController _mover = default!; - [Dependency] private readonly SharedTransformSystem _xforms = default!; - [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly StationAiVisionSystem _vision = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPowerReceiverSystem PowerReceiver = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly StationAiVisionSystem _vision = default!; // StationAiHeld is added to anything inside of an AI core. // StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core). diff --git a/Resources/Audio/Machines/airlock_electrify_off.ogg b/Resources/Audio/Machines/airlock_electrify_off.ogg new file mode 100644 index 00000000000000..c88d00299e9215 Binary files /dev/null and b/Resources/Audio/Machines/airlock_electrify_off.ogg differ diff --git a/Resources/Audio/Machines/airlock_electrify_on.ogg b/Resources/Audio/Machines/airlock_electrify_on.ogg new file mode 100644 index 00000000000000..c87b7d3851bc94 Binary files /dev/null and b/Resources/Audio/Machines/airlock_electrify_on.ogg differ diff --git a/Resources/Audio/Machines/airlock_emergencyoff.ogg b/Resources/Audio/Machines/airlock_emergencyoff.ogg new file mode 100644 index 00000000000000..5046a75ec20e11 Binary files /dev/null and b/Resources/Audio/Machines/airlock_emergencyoff.ogg differ diff --git a/Resources/Audio/Machines/airlock_emergencyon.ogg b/Resources/Audio/Machines/airlock_emergencyon.ogg new file mode 100644 index 00000000000000..6c8b54a28459d7 Binary files /dev/null and b/Resources/Audio/Machines/airlock_emergencyon.ogg differ diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index b1f99245468303..1b4ea7474160bf 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -171,3 +171,12 @@ license: "CC0-1.0" copyright: "by Ko4erga" source: "https://github.com/space-wizards/space-station-14/pull/30431" + +- files: + - airlock_emergencyoff.ogg + - airlock_emergencyon.ogg + - airlock_electrify_off.ogg + - airlock_electrify_on.ogg + license: "CC0-1.0" + copyright: "by ScarKy0" + source: "https://github.com/space-wizards/space-station-14/pull/32012" diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl index d51a99ebb0437c..7d9db3f6dc5a8b 100644 --- a/Resources/Locale/en-US/silicons/station-ai.ftl +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -11,4 +11,12 @@ ai-close = Close actions bolt-close = Close bolt bolt-open = Open bolt +emergency-access-on = Enable emergency access +emergency-access-off = Disable emergency access + +electrify-door-on = Enable overcharge +electrify-door-off = Disable overcharge + toggle-light = Toggle light + +ai-device-not-responding = Device is not responding diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/bolt_door.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/bolt_door.png new file mode 100644 index 00000000000000..f79424898024d8 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/bolt_door.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_off.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_off.png new file mode 100644 index 00000000000000..d5301ccba0b258 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_off.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_on.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_on.png new file mode 100644 index 00000000000000..ea654d8634b7be Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_on.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_off.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_off.png new file mode 100644 index 00000000000000..86328da46be37b Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_off.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_on.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_on.png new file mode 100644 index 00000000000000..14034429f4c189 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_on.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json index 6b974d8521a5ea..1efee13f3aae0f 100644 --- a/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json @@ -1,27 +1,27 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/c473a8bcc28fbd80827dfca5660d81ca6e833e2c/icons/hud/screen_ai.dmi", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "ai_core" - }, - { - "name": "camera_light" - }, - { - "name": "crew_monitor" - }, - { - "name": "manifest" - }, - { - "name": "state_laws" - }, + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/c473a8bcc28fbd80827dfca5660d81ca6e833e2c/icons/hud/screen_ai.dmi , some sprites updated by ScarKy0(Discord), door actions by ScarKy0(Discord) and @Max_tanuki(Twitter)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "ai_core" + }, + { + "name": "camera_light" + }, + { + "name": "crew_monitor" + }, + { + "name": "manifest" + }, + { + "name": "state_laws" + }, { "name": "station_records" }, @@ -33,6 +33,24 @@ }, { "name": "comms_console" + }, + { + "name": "emergency_off" + }, + { + "name": "emergency_on" + }, + { + "name": "bolt_door" + }, + { + "name": "unbolt_door" + }, + { + "name": "door_overcharge_on" + }, + { + "name": "door_overcharge_off" } - ] + ] } diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/unbolt_door.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/unbolt_door.png new file mode 100644 index 00000000000000..dfbb102f97bf98 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/unbolt_door.png differ