]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Station AI ability to electricute doors (#32012)
authorFildrance <fildrance@gmail.com>
Fri, 27 Sep 2024 07:22:17 +0000 (10:22 +0300)
committerGitHub <noreply@github.com>
Fri, 27 Sep 2024 07:22:17 +0000 (17:22 +1000)
* Boom! Emergency access!

* Emergency access sound

* locale

* Updated sounds

* bleh

* Door electrify base

* feat: popups on attempt to activate AI action when wires cut

* refactor: use SharedApcPowerReceiverComponent to check if AI can interact with door

* refactor: added icon and sound for door overcharge

* meta.json should use tabs not spaces

* refactor: extracted sounds for airlock overcharge to static field in system

* refactor: cleanup, ScarKy0 mentions for resources

* refactor: removed unused textures

* feat: now notification is displayed when AI attempting to interact with door which have wire cut

* StationAiWhitelistComponent is properly gating  BUI OnMessageAttempt, SharedPowerReceiverSystem.IsPowered is now used to check if device powered

* refactor: use PlayLocal to play electrify sound only for AI player

* refactor: SetBoltsDown now uses TrySetBoltDown, checks for power.

* bolts now check for power using SharedPowerReceiverSystem

* electrify localization and louder electrify sounds

* extracted ShowDeviceNotRespondingPopup, reverted airlocks not opening/closing when ai wire was cut

* refactor: cleanup

* New sprites and fixes

* Copyright

* even more sprite changes

* refactore: cleanup, rename overcharge => electrify

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
30 files changed:
Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
Content.Server/Construction/Completions/AttemptElectrocute.cs
Content.Server/Doors/WireActions/DoorBoltWireAction.cs
Content.Server/Electrocution/ElectrocutionSystem.cs
Content.Server/Power/PowerWireAction.cs
Content.Server/Remotes/DoorRemoteSystem.cs
Content.Shared/Doors/Components/AirlockComponent.cs
Content.Shared/Doors/Systems/SharedAirlockSystem.cs
Content.Shared/Doors/Systems/SharedDoorSystem.Bolts.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Content.Shared/Electrocution/Components/ElectrifiedComponent.cs [moved from Content.Server/Electrocution/Components/ElectrifiedComponent.cs with 65% similarity]
Content.Shared/Electrocution/SharedElectrocutionSystem.cs
Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs
Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs
Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs
Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs
Resources/Audio/Machines/airlock_electrify_off.ogg [new file with mode: 0644]
Resources/Audio/Machines/airlock_electrify_on.ogg [new file with mode: 0644]
Resources/Audio/Machines/airlock_emergencyoff.ogg [new file with mode: 0644]
Resources/Audio/Machines/airlock_emergencyon.ogg [new file with mode: 0644]
Resources/Audio/Machines/attributions.yml
Resources/Locale/en-US/silicons/station-ai.ftl
Resources/Textures/Interface/Actions/actions_ai.rsi/bolt_door.png [new file with mode: 0644]
Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_off.png [new file with mode: 0644]
Resources/Textures/Interface/Actions/actions_ai.rsi/door_overcharge_on.png [new file with mode: 0644]
Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_off.png [new file with mode: 0644]
Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_on.png [new file with mode: 0644]
Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json
Resources/Textures/Interface/Actions/actions_ai.rsi/unbolt_door.png [new file with mode: 0644]

index bf6b65a9697b5a4306c6764e21c1b7348b9768d0..d5bc764b348c8ed1379f960417f4cf51609d49cf 100644 (file)
@@ -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<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
+        SubscribeLocalEvent<AirlockComponent, GetStationAiRadialEvent>(OnEmergencyAccessGetRadial);
+        SubscribeLocalEvent<ElectrifiedComponent, GetStationAiRadialEvent>(OnDoorElectrifiedGetRadial);
     }
 
     private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> 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<AirlockComponent> 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<ElectrifiedComponent> 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,
+                }
             }
-        });
+        );
     }
 }
index fef8a031d9da9a82098c375109213fab84104b17..56cf2878506060fa2f0f6589dcbf7804134c8890 100644 (file)
@@ -90,22 +90,22 @@ public sealed partial class AdminVerbSystem
                 args.Verbs.Add(bolt);
             }
 
-            if (TryComp<AirlockComponent>(args.Target, out var airlock))
+            if (TryComp<AirlockComponent>(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);
             }
index 05f0977b662788638d316725b23a65755a247d9e..5c97d5e90fec98bb5d565c280bd271f6e6f40a7d 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Electrocution;
+using Content.Shared.Electrocution;
 using Content.Shared.Construction;
 
 namespace Content.Server.Construction.Completions;
index fc1cf50cd87d5c6dff4b6f7b0e9fa839afd949e5..80555f68f9b63a924fdf38ea8c8bb11339f0f628 100644 (file)
@@ -2,7 +2,6 @@ using Content.Server.Doors.Systems;
 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;
index 67e60c9de4660c0243fbfc09468d7e10ce3b4eee..88404c4aa96052d2f5d28736b6ddc78202f94244 100644 (file)
@@ -488,4 +488,15 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
         }
         _audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
     }
+
+    public void SetElectrifiedWireCut(Entity<ElectrifiedComponent> ent, bool value)
+    {
+        if (ent.Comp.IsWireCut == value)
+        {
+            return;
+        }
+
+        ent.Comp.IsWireCut = value;
+        Dirty(ent);
+    }
 }
index ac34966036cc979d0b134b7805670782f5be3892..cebb7de8ec868568969e76a4df6161c31d5cc458 100644 (file)
@@ -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 @@ public sealed partial class PowerWireAction : BaseWireAction
             && !EntityManager.TryGetComponent(used, out electrified))
             return;
 
+        _electrocutionSystem.SetElectrifiedWireCut((used, electrified), setting);
         electrified.Enabled = setting;
     }
 
index 67160650871d4fe5b2aa59ab03e156a74e481049..de327bd084038ab86ea0fc5743759833f236705e 100644 (file)
@@ -74,7 +74,7 @@ namespace Content.Shared.Remotes
                 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")}");
                     }
index 6577b1942ac9270a02e647840980a82e822269ea..6b3fcfad7e436dcd919f857c2e0646b5795f360b 100644 (file)
@@ -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;
+       
+    /// <summary>
+    /// Sound to play when the airlock emergency access is turned on.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier EmergencyOnSound = new SoundPathSpecifier("/Audio/Machines/airlock_emergencyon.ogg");
+
+    /// <summary>
+    /// Sound to play when the airlock emergency access is turned off.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier EmergencyOffSound = new SoundPathSpecifier("/Audio/Machines/airlock_emergencyoff.ogg");
 
     /// <summary>
     /// Pry modifier for a powered airlock.
index 5a9cde74ee10fdcbed42535696dad2203c0b04df..e404a91bdd74ff4ad1cbe76b6e0a36bdcabfe4f4 100644 (file)
@@ -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 abstract class SharedAirlockSystem : EntitySystem
         Appearance.SetData(uid, DoorVisuals.EmergencyLights, component.EmergencyAccess);
     }
 
-    public void ToggleEmergencyAccess(EntityUid uid, AirlockComponent component)
+    public void SetEmergencyAccess(Entity<AirlockComponent> 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)
index 35681bfd82293146eb99b1312dee3180e02b58c7..13050616e1ba5e5fed59683bfe894d39ff96a8ef 100644 (file)
@@ -77,8 +77,20 @@ public abstract partial class SharedDoorSystem
 
     public void SetBoltsDown(Entity<DoorBoltComponent> ent, bool value, EntityUid? user = null, bool predicted = false)
     {
+        TrySetBoltDown(ent, value, user, predicted);
+    }
+
+    public bool TrySetBoltDown(
+        Entity<DoorBoltComponent> 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 abstract partial class SharedDoorSystem
             Audio.PlayPredicted(sound, ent, user: user);
         else
             Audio.PlayPvs(sound, ent);
+        return true;
     }
 
     private void OnStateChanged(Entity<DoorBoltComponent> entity, ref DoorStateChangedEvent args)
index 2319d5e916b2e5579a9438993aa2d75db2e2be7c..3d9a721473516fe6421df36bb232693acbc890e3 100644 (file)
@@ -9,6 +9,7 @@ using Content.Shared.Emag.Systems;
 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<TagPrototype>]
similarity index 65%
rename from Content.Server/Electrocution/Components/ElectrifiedComponent.cs
rename to Content.Shared/Electrocution/Components/ElectrifiedComponent.cs
index 5755e98091b57878b0d20608242ddf2f35fad244..52eb76ca54135464a32d17279608acf566bb0648 100644 (file)
+using Robust.Shared.GameStates;
 using Robust.Shared.Audio;
 
-namespace Content.Server.Electrocution;
+namespace Content.Shared.Electrocution;
 
 /// <summary>
 ///     Component for things that shock users on touch.
 /// </summary>
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class ElectrifiedComponent : Component
 {
-    [DataField("enabled")]
+    [DataField, AutoNetworkedField]
     public bool Enabled = true;
 
     /// <summary>
     /// Should player get damage on collide
     /// </summary>
-    [DataField("onBump")]
+    [DataField, AutoNetworkedField]
     public bool OnBump = true;
 
     /// <summary>
     /// Should player get damage on attack
     /// </summary>
-    [DataField("onAttacked")]
+    [DataField, AutoNetworkedField]
     public bool OnAttacked = true;
 
     /// <summary>
     /// When true - disables power if a window is present in the same tile
     /// </summary>
-    [DataField("noWindowInTile")]
+    [DataField, AutoNetworkedField]
     public bool NoWindowInTile = false;
 
     /// <summary>
     /// Should player get damage on interact with empty hand
     /// </summary>
-    [DataField("onHandInteract")]
+    [DataField, AutoNetworkedField]
     public bool OnHandInteract = true;
 
     /// <summary>
     /// Should player get damage on interact while holding an object in their hand
     /// </summary>
-    [DataField("onInteractUsing")]
+    [DataField, AutoNetworkedField]
     public bool OnInteractUsing = true;
 
     /// <summary>
     /// Indicates if the entity requires power to function
     /// </summary>
-    [DataField("requirePower")]
+    [DataField, AutoNetworkedField]
     public bool RequirePower = true;
 
     /// <summary>
     /// Indicates if the entity uses APC power
     /// </summary>
-    [DataField("usesApcPower")]
+    [DataField, AutoNetworkedField]
     public bool UsesApcPower = false;
 
     /// <summary>
     /// Identifier for the high voltage node.
     /// </summary>
-    [DataField("highVoltageNode")]
+    [DataField, AutoNetworkedField]
     public string? HighVoltageNode;
 
     /// <summary>
     /// Identifier for the medium voltage node.
     /// </summary>
-    [DataField("mediumVoltageNode")]
+    [DataField, AutoNetworkedField]
     public string? MediumVoltageNode;
 
     /// <summary>
     /// Identifier for the low voltage node.
     /// </summary>
-    [DataField("lowVoltageNode")]
+    [DataField, AutoNetworkedField]
     public string? LowVoltageNode;
 
     /// <summary>
     /// Damage multiplier for HV electrocution
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float HighVoltageDamageMultiplier = 3f;
 
     /// <summary>
     /// Shock time multiplier for HV electrocution
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float HighVoltageTimeMultiplier = 1.5f;
 
     /// <summary>
     /// Damage multiplier for MV electrocution
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float MediumVoltageDamageMultiplier = 2f;
 
     /// <summary>
     /// Shock time multiplier for MV electrocution
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float MediumVoltageTimeMultiplier = 1.25f;
 
-    [DataField("shockDamage")]
+    [DataField, AutoNetworkedField]
     public float ShockDamage = 7.5f;
 
     /// <summary>
     /// Shock time, in seconds.
     /// </summary>
-    [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;
 }
index b228a987af4af67953d9b2fbd63b7998e6a69d95..e36e4a804b7ee1d5a5a20a77819aa27db56c7cc8 100644 (file)
@@ -23,6 +23,20 @@ namespace Content.Shared.Electrocution
             Dirty(uid, insulated);
         }
 
+        /// <summary>
+        /// Sets electrified value of component and marks dirty if required.
+        /// </summary>
+        public void SetElectrified(Entity<ElectrifiedComponent> ent, bool value)
+        {
+            if (ent.Comp.Enabled == value)
+            {
+                return;
+            }
+
+            ent.Comp.Enabled = value;
+            Dirty(ent, ent.Comp);
+        }
+
         /// <param name="uid">Entity being electrocuted.</param>
         /// <param name="sourceUid">Source entity of the electrocution.</param>
         /// <param name="shockDamage">How much shock damage the entity takes.</param>
index 2bc2af78314352e21d988577fb91209d25b9fbad..b7ba2a31c5d94efd6c1d2718cea87c4d10d86642 100644 (file)
@@ -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);
 
+    /// <summary>
+    /// Checks if entity is APC-powered device, and if it have power.
+    /// </summary>
     public bool IsPowered(Entity<SharedApcPowerReceiverComponent?> entity)
     {
         if (!ResolveApc(entity.Owner, ref entity.Comp))
index ff6fc1ece0796787289c334c8186c948f9ba5b87..37e5cd6e6aed9ca85ef267ef306cf06c3d05f16f 100644 (file)
@@ -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<DoorBoltComponent, StationAiBoltEvent>(OnAirlockBolt);
+        SubscribeLocalEvent<AirlockComponent, StationAiEmergencyAccessEvent>(OnAirlockEmergencyAccess);
+        SubscribeLocalEvent<ElectrifiedComponent, StationAiElectrifiedEvent>(OnElectrified);
     }
 
+    /// <summary>
+    /// Attempts to bolt door. If wire was cut (AI or for bolts) or its not powered - notifies AI and does nothing.
+    /// </summary>
     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);
+        }
+    }
+
+    /// <summary>
+    /// Attempts to bolt door. If wire was cut (AI) or its not powered - notifies AI and does nothing.
+    /// </summary>
+    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);
+    }
+
+    /// <summary>
+    /// Attempts to bolt door. If wire was cut (AI or for one of power-wires) or its not powered - notifies AI and does nothing.
+    /// </summary>
+    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);
     }
 }
 
+/// <summary> Event for StationAI attempt at bolting/unbolting door. </summary>
 [Serializable, NetSerializable]
 public sealed class StationAiBoltEvent : BaseStationAiAction
 {
+    /// <summary> Marker, should be door bolted or unbolted. </summary>
     public bool Bolted;
 }
+
+/// <summary> Event for StationAI attempt at setting emergency access for door on/off. </summary>
+[Serializable, NetSerializable]
+public sealed class StationAiEmergencyAccessEvent : BaseStationAiAction
+{
+    /// <summary> Marker, should door have emergency access on or off. </summary>
+    public bool EmergencyAccess;
+}
+
+/// <summary> Event for StationAI attempt at electrifying/de-electrifying door. </summary>
+[Serializable, NetSerializable]
+public sealed class StationAiElectrifiedEvent : BaseStationAiAction
+{
+    /// <summary> Marker, should door be electrified or no. </summary>
+    public bool Electrified;
+}
index c59c4723079aa50700665ab93136c15f4e23fdaf..e067cf3efadb60cd9216d9eb8c53e63f2db72aea 100644 (file)
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
 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 @@ public abstract partial class SharedStationAiSystem
         SubscribeLocalEvent<StationAiHeldComponent, InteractionAttemptEvent>(OnHeldInteraction);
         SubscribeLocalEvent<StationAiHeldComponent, AttemptRelayActionComponentChangeEvent>(OnHeldRelay);
         SubscribeLocalEvent<StationAiHeldComponent, JumpToCoreEvent>(OnCoreJump);
-               SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
+        SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
     }
-       
-       private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args)
+
+    private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args)
     {
         if (args.Handled)
         {
@@ -40,7 +41,7 @@ public abstract partial class SharedStationAiSystem
         {
             return;
         }
-           args.Title = $"{Name(args.ForActor)} ({Loc.GetString(JobNameLocId)})";
+        args.Title = $"{Name(args.ForActor)} ({Loc.GetString(JobNameLocId)})";
         args.Handled = true;
     }
 
@@ -108,41 +109,56 @@ public abstract partial class SharedStationAiSystem
             return;
 
         if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) &&
-           (!ValidateAi((ev.Actor, aiComp)) ||
-            !HasComp<StationAiWhitelistComponent>(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<StationAiHeldComponent> 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<StationAiWhitelistComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
     {
-        if (!args.CanComplexInteract ||
-            !ent.Comp.Enabled ||
-            !HasComp<StationAiHeldComponent>(args.User) ||
-            !HasComp<StationAiWhitelistComponent>(args.Target))
+        if (!args.CanComplexInteract
+            || !HasComp<StationAiHeldComponent>(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 @@ public abstract partial class SharedStationAiSystem
                     _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);
     }
 }
 
index 17c592879c8f99f99d105730fdb5fb3b2cd7e675..baef62c3da98ec7ab165cd7a1481c74678c5d68a 100644 (file)
@@ -4,14 +4,18 @@ using Content.Shared.Administration.Managers;
 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 (file)
index 0000000..c88d002
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 (file)
index 0000000..c87b7d3
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 (file)
index 0000000..5046a75
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 (file)
index 0000000..6c8b54a
Binary files /dev/null and b/Resources/Audio/Machines/airlock_emergencyon.ogg differ
index b1f99245468303f5496768500e7b40269aeb6c2a..1b4ea7474160bfa765e810a0c4fd152505a66421 100644 (file)
   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"
index d51a99ebb0437c9c935f72b9ca60fc85adf555d8..7d9db3f6dc5a8b39b183c78335942bf87759250f 100644 (file)
@@ -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 (file)
index 0000000..f794248
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 (file)
index 0000000..d5301cc
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 (file)
index 0000000..ea654d8
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 (file)
index 0000000..86328da
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 (file)
index 0000000..1403442
Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/emergency_on.png differ
index 6b974d8521a5eaf208f379d4af5bbf5e29b121c4..1efee13f3aae0f630a167e8078ae2484f5386dd6 100644 (file)
@@ -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"
                },
                },
                {
                        "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 (file)
index 0000000..dfbb102
Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/unbolt_door.png differ