]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Better robotics console (#38023)
authorSamuka-C <47865393+Samuka-C@users.noreply.github.com>
Mon, 11 Aug 2025 18:57:39 +0000 (15:57 -0300)
committerGitHub <noreply@github.com>
Mon, 11 Aug 2025 18:57:39 +0000 (21:57 +0300)
Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs
Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs
Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs
Content.Shared/Robotics/RoboticsConsoleUi.cs
Resources/Locale/en-US/research/components/robotics-console.ftl

index 06e5674d9cb33b05e98e3b30793a139f5c5eb598..24583d2776bf3b35e2011d0f994986911cc30523 100644 (file)
@@ -28,6 +28,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
 
     public EntityUid Entity;
 
+    private bool _allowBorgControl = true;
+
     public RoboticsConsoleWindow()
     {
         RobustXamlLoader.Load(this);
@@ -72,6 +74,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
     public void UpdateState(RoboticsConsoleState state)
     {
         _cyborgs = state.Cyborgs;
+        _allowBorgControl = state.AllowBorgControl;
 
         // clear invalid selection
         if (_selected is {} selected && !_cyborgs.ContainsKey(selected))
@@ -85,8 +88,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
         PopulateData();
 
         var locked = _lock.IsLocked(Entity);
-        DangerZone.Visible = !locked;
-        LockedMessage.Visible = locked;
+        DangerZone.Visible = !locked && _allowBorgControl;
+        LockedMessage.Visible = locked && _allowBorgControl; // Only show if locked AND control is allowed
     }
 
     private void PopulateCyborgs()
@@ -120,11 +123,19 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
         BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!);
 
         var batteryColor = data.Charge switch {
-            < 0.2f => "red",
-            < 0.4f => "orange",
-            < 0.6f => "yellow",
-            < 0.8f => "green",
-            _ => "blue"
+            < 0.2f => "#FF6C7F", // red
+            < 0.4f => "#EF973C", // orange
+            < 0.6f => "#E8CB2D", // yellow
+            < 0.8f => "#30CC19", // green
+            _ => "#00D3B8" // cyan
+        };
+
+        var hpPercentColor = data.HpPercent switch {
+            < 0.2f => "#FF6C7F", // red
+            < 0.4f => "#EF973C", // orange
+            < 0.6f => "#E8CB2D", // yellow
+            < 0.8f => "#30CC19", // green
+            _ => "#00D3B8" // cyan
         };
 
         var text = new FormattedMessage();
@@ -132,12 +143,14 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
         text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation"));
         text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color]
         text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n");
+        text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-hp", ("hp", (int)(data.HpPercent * 100f)), ("color", hpPercentColor))}\n");
         text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n");
         text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
         BorgInfo.SetMessage(text);
 
         // how the turntables
-        DisableButton.Disabled = !(data.HasBrain && data.CanDisable);
+        DisableButton.Disabled = !_allowBorgControl || !(data.HasBrain && data.CanDisable);
+        DestroyButton.Disabled = !_allowBorgControl;
     }
 
     protected override void FrameUpdate(FrameEventArgs args)
index c4554d65d673fa411b162e2ddbbdb81b1214f76e..560d8174aa31ecd4e3b5c4455a919a2e5b89014d 100644 (file)
@@ -95,6 +95,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
 
     private void OnDisable(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDisableMessage args)
     {
+        if (!ent.Comp.AllowBorgControl)
+            return;
+
         if (_lock.IsLocked(ent.Owner))
             return;
 
@@ -112,6 +115,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
 
     private void OnDestroy(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDestroyMessage args)
     {
+        if (!ent.Comp.AllowBorgControl)
+            return;
+
         if (_lock.IsLocked(ent.Owner))
             return;
 
@@ -139,7 +145,7 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
 
     private void UpdateUserInterface(Entity<RoboticsConsoleComponent> ent)
     {
-        var state = new RoboticsConsoleState(ent.Comp.Cyborgs);
+        var state = new RoboticsConsoleState(ent.Comp.Cyborgs, ent.Comp.AllowBorgControl);
         _ui.SetUiState(ent.Owner, RoboticsConsoleUiKey.Key, state);
     }
 }
index debed525f6ebc750f542c4d6b419383e30f9472c..c14e81ce8a16ccd2f9d9142c6b297f40d8a31bbc 100644 (file)
@@ -1,4 +1,9 @@
+using Content.Shared.Containers.ItemSlots;
 using Content.Shared.DeviceNetwork;
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Systems;
 using Content.Shared.Movement.Components;
 using Content.Shared.Popups;
 using Content.Shared.Robotics;
@@ -14,6 +19,9 @@ namespace Content.Server.Silicons.Borgs;
 public sealed partial class BorgSystem
 {
     [Dependency] private readonly EmagSystem _emag = default!;
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+    [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
+    [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
 
     private void InitializeTransponder()
     {
@@ -28,7 +36,7 @@ public sealed partial class BorgSystem
         var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
         while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
         {
-            if (comp.NextDisable is {} nextDisable && now >= nextDisable)
+            if (comp.NextDisable is { } nextDisable && now >= nextDisable)
                 DoDisable((uid, comp, chassis, meta));
 
             if (now < comp.NextBroadcast)
@@ -38,13 +46,17 @@ public sealed partial class BorgSystem
             if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
                 charge = battery.CurrentCharge / battery.MaxCharge;
 
-            var hasBrain = chassis.BrainEntity != null && !comp.FakeDisabled;
+            var hpPercent = CalcHP(uid);
+
+            // checks if it has a brain and if the brain is not a empty MMI (gives false anyway if the fake disable is true)
+            var hasBrain = CheckBrain(chassis.BrainEntity) && !comp.FakeDisabled;
             var canDisable = comp.NextDisable == null && !comp.FakeDisabling;
             var data = new CyborgControlData(
                 comp.Sprite,
                 comp.Name,
                 meta.EntityName,
                 charge,
+                hpPercent,
                 chassis.ModuleCount,
                 hasBrain,
                 canDisable);
@@ -70,7 +82,7 @@ public sealed partial class BorgSystem
             return;
         }
 
-        if (ent.Comp2.BrainEntity is not {} brain)
+        if (ent.Comp2.BrainEntity is not { } brain)
             return;
 
         var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3)));
@@ -151,4 +163,40 @@ public sealed partial class BorgSystem
     {
         ent.Comp.Name = name;
     }
+
+    /// <summary>
+    /// Returns a ratio between 0 and 1, 1 when they have no damage and 0 whenever they are crit (or more damaged)
+    /// </summary>
+    private float CalcHP(EntityUid uid)
+    {
+        if (!TryComp<DamageableComponent>(uid, out var damageable))
+            return 1;
+
+        if (!_mobState.IsAlive(uid))
+            return 0;
+
+        if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold))
+        {
+            Log.Error($"Borg({ToPrettyString(uid)}), doesn't have critical threshold.");
+            return 1;
+        }
+
+        return 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float();
+    }
+
+    /// <summary>
+    /// Returns true if the borg has a brain
+    /// </summary>
+    private bool CheckBrain(EntityUid? brainEntity)
+    {
+        if (brainEntity == null)
+            return false;
+
+        // if the brainEntity.Value has the component MMIComponent then it is a MMI,
+        // in that case it trys to get the "brain" of the MMI, if it is null the MMI is empty and so it returns false
+        if (TryComp<MMIComponent>(brainEntity.Value, out var mmi) && _itemSlotsSystem.GetItemOrNull(brainEntity.Value, mmi.BrainSlotId) == null)
+            return false;
+
+        return true;
+    }
 }
index 9e4b51866f3adf29c3494b028d2f2f1d4c08da4c..eef2007f3130e87d323f221ddfb96750a066ad23 100644 (file)
@@ -50,4 +50,10 @@ public sealed partial class RoboticsConsoleComponent : Component
     [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
     [AutoNetworkedField, AutoPausedField]
     public TimeSpan NextDestroy = TimeSpan.Zero;
+
+    /// <summary>
+    /// Controls if the console can disable or destroy any borg.
+    /// </summary>
+    [DataField]
+    public bool AllowBorgControl = true;
 }
index 7a6974fb51c2e333dc7a69b388bdc9e768e3b678..01ecb3f5985a2f51b9adfa6d5a35515281606c8c 100644 (file)
@@ -1,4 +1,4 @@
-using Robust.Shared.Prototypes;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 using Robust.Shared.Utility;
@@ -19,9 +19,15 @@ public sealed class RoboticsConsoleState : BoundUserInterfaceState
     /// </summary>
     public Dictionary<string, CyborgControlData> Cyborgs;
 
-    public RoboticsConsoleState(Dictionary<string, CyborgControlData> cyborgs)
+    /// <summary>
+    /// If the UI will have the buttons to disable and destroy.
+    /// </summary>
+    public bool AllowBorgControl;
+
+    public RoboticsConsoleState(Dictionary<string, CyborgControlData> cyborgs, bool allowBorgControl)
     {
         Cyborgs = cyborgs;
+        AllowBorgControl = allowBorgControl;
     }
 }
 
@@ -84,6 +90,12 @@ public partial record struct CyborgControlData
     [DataField]
     public float Charge;
 
+    /// <summary>
+    /// HP level from 0 to 1.
+    /// </summary>
+    [DataField]
+    public float HpPercent; // 0.0 to 1.0
+
     /// <summary>
     /// How many modules this borg has, just useful information for roboticists.
     /// Lets them keep track of the latejoin borgs that need new modules and stuff.
@@ -111,12 +123,13 @@ public partial record struct CyborgControlData
     [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
     public TimeSpan Timeout = TimeSpan.Zero;
 
-    public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, int moduleCount, bool hasBrain, bool canDisable)
+    public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, float hpPercent, int moduleCount, bool hasBrain, bool canDisable)
     {
         ChassisSprite = chassisSprite;
         ChassisName = chassisName;
         Name = name;
         Charge = charge;
+        HpPercent = hpPercent;
         ModuleCount = moduleCount;
         HasBrain = hasBrain;
         CanDisable = canDisable;
index a4c82bd03229acfa41644c86384e07bc8e802fb9..d3d2bcea48889cfbc6e660fc5d60afbcd3b16441 100644 (file)
@@ -6,6 +6,7 @@ robotics-console-model = [color=gray]Model:[/color] {$name}
 # name is not formatted to prevent players trolling
 robotics-console-designation = [color=gray]Designation:[/color]
 robotics-console-battery = [color=gray]Battery charge:[/color] [color={$color}]{$charge}[/color]%
+robotics-console-hp = [color=gray]Integrity:[/color] [color={$color}]{$hp}[/color]%
 robotics-console-modules = [color=gray]Modules installed:[/color] {$count}
 robotics-console-brain = [color=gray]Brain installed:[/color] [color={$brain ->
     [true] green]Yes