From ed6f906e6f5d917061dd69f88dfd4731d2b2b5dc Mon Sep 17 00:00:00 2001 From: Samuka-C <47865393+Samuka-C@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:57:39 -0300 Subject: [PATCH] Better robotics console (#38023) --- .../Robotics/UI/RoboticsConsoleWindow.xaml.cs | 29 +++++++--- .../Robotics/Systems/RoboticsConsoleSystem.cs | 8 ++- .../Silicons/Borgs/BorgSystem.Transponder.cs | 54 +++++++++++++++++-- .../Components/RoboticsConsoleComponent.cs | 6 +++ Content.Shared/Robotics/RoboticsConsoleUi.cs | 19 +++++-- .../research/components/robotics-console.ftl | 1 + 6 files changed, 102 insertions(+), 15 deletions(-) diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs index 06e5674d9c..24583d2776 100644 --- a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs +++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs @@ -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) diff --git a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs index c4554d65d6..560d8174aa 100644 --- a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs +++ b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs @@ -95,6 +95,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void OnDisable(Entity 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 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 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); } } diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs index debed525f6..c14e81ce8a 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs @@ -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(); 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; } + + /// + /// Returns a ratio between 0 and 1, 1 when they have no damage and 0 whenever they are crit (or more damaged) + /// + private float CalcHP(EntityUid uid) + { + if (!TryComp(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(); + } + + /// + /// Returns true if the borg has a brain + /// + 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(brainEntity.Value, out var mmi) && _itemSlotsSystem.GetItemOrNull(brainEntity.Value, mmi.BrainSlotId) == null) + return false; + + return true; + } } diff --git a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs index 9e4b51866f..eef2007f31 100644 --- a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs +++ b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs @@ -50,4 +50,10 @@ public sealed partial class RoboticsConsoleComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoNetworkedField, AutoPausedField] public TimeSpan NextDestroy = TimeSpan.Zero; + + /// + /// Controls if the console can disable or destroy any borg. + /// + [DataField] + public bool AllowBorgControl = true; } diff --git a/Content.Shared/Robotics/RoboticsConsoleUi.cs b/Content.Shared/Robotics/RoboticsConsoleUi.cs index 7a6974fb51..01ecb3f598 100644 --- a/Content.Shared/Robotics/RoboticsConsoleUi.cs +++ b/Content.Shared/Robotics/RoboticsConsoleUi.cs @@ -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 /// public Dictionary Cyborgs; - public RoboticsConsoleState(Dictionary cyborgs) + /// + /// If the UI will have the buttons to disable and destroy. + /// + public bool AllowBorgControl; + + public RoboticsConsoleState(Dictionary cyborgs, bool allowBorgControl) { Cyborgs = cyborgs; + AllowBorgControl = allowBorgControl; } } @@ -84,6 +90,12 @@ public partial record struct CyborgControlData [DataField] public float Charge; + /// + /// HP level from 0 to 1. + /// + [DataField] + public float HpPercent; // 0.0 to 1.0 + /// /// 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; diff --git a/Resources/Locale/en-US/research/components/robotics-console.ftl b/Resources/Locale/en-US/research/components/robotics-console.ftl index a4c82bd032..d3d2bcea48 100644 --- a/Resources/Locale/en-US/research/components/robotics-console.ftl +++ b/Resources/Locale/en-US/research/components/robotics-console.ftl @@ -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