Better robotics console (#38023)

This commit is contained in:
Samuka-C
2025-08-11 15:57:39 -03:00
committed by GitHub
parent 47d7db0665
commit ed6f906e6f
6 changed files with 102 additions and 15 deletions

View File

@@ -28,6 +28,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
public EntityUid Entity; public EntityUid Entity;
private bool _allowBorgControl = true;
public RoboticsConsoleWindow() public RoboticsConsoleWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -72,6 +74,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
public void UpdateState(RoboticsConsoleState state) public void UpdateState(RoboticsConsoleState state)
{ {
_cyborgs = state.Cyborgs; _cyborgs = state.Cyborgs;
_allowBorgControl = state.AllowBorgControl;
// clear invalid selection // clear invalid selection
if (_selected is {} selected && !_cyborgs.ContainsKey(selected)) if (_selected is {} selected && !_cyborgs.ContainsKey(selected))
@@ -85,8 +88,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
PopulateData(); PopulateData();
var locked = _lock.IsLocked(Entity); var locked = _lock.IsLocked(Entity);
DangerZone.Visible = !locked; DangerZone.Visible = !locked && _allowBorgControl;
LockedMessage.Visible = locked; LockedMessage.Visible = locked && _allowBorgControl; // Only show if locked AND control is allowed
} }
private void PopulateCyborgs() private void PopulateCyborgs()
@@ -120,11 +123,19 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!); BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!);
var batteryColor = data.Charge switch { var batteryColor = data.Charge switch {
< 0.2f => "red", < 0.2f => "#FF6C7F", // red
< 0.4f => "orange", < 0.4f => "#EF973C", // orange
< 0.6f => "yellow", < 0.6f => "#E8CB2D", // yellow
< 0.8f => "green", < 0.8f => "#30CC19", // green
_ => "blue" _ => "#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(); var text = new FormattedMessage();
@@ -132,12 +143,14 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation")); text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation"));
text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color] 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-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-brain", ("brain", data.HasBrain))}\n");
text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount))); text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
BorgInfo.SetMessage(text); BorgInfo.SetMessage(text);
// how the turntables // 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) protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -95,6 +95,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
private void OnDisable(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDisableMessage args) private void OnDisable(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDisableMessage args)
{ {
if (!ent.Comp.AllowBorgControl)
return;
if (_lock.IsLocked(ent.Owner)) if (_lock.IsLocked(ent.Owner))
return; return;
@@ -112,6 +115,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
private void OnDestroy(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDestroyMessage args) private void OnDestroy(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDestroyMessage args)
{ {
if (!ent.Comp.AllowBorgControl)
return;
if (_lock.IsLocked(ent.Owner)) if (_lock.IsLocked(ent.Owner))
return; return;
@@ -139,7 +145,7 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
private void UpdateUserInterface(Entity<RoboticsConsoleComponent> ent) 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); _ui.SetUiState(ent.Owner, RoboticsConsoleUiKey.Key, state);
} }
} }

View File

@@ -1,4 +1,9 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DeviceNetwork; 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.Movement.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Robotics; using Content.Shared.Robotics;
@@ -14,6 +19,9 @@ namespace Content.Server.Silicons.Borgs;
public sealed partial class BorgSystem public sealed partial class BorgSystem
{ {
[Dependency] private readonly EmagSystem _emag = default!; [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() private void InitializeTransponder()
{ {
@@ -28,7 +36,7 @@ public sealed partial class BorgSystem
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>(); 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)) 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)); DoDisable((uid, comp, chassis, meta));
if (now < comp.NextBroadcast) if (now < comp.NextBroadcast)
@@ -38,13 +46,17 @@ public sealed partial class BorgSystem
if (_powerCell.TryGetBatteryFromSlot(uid, out var battery)) if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
charge = battery.CurrentCharge / battery.MaxCharge; 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 canDisable = comp.NextDisable == null && !comp.FakeDisabling;
var data = new CyborgControlData( var data = new CyborgControlData(
comp.Sprite, comp.Sprite,
comp.Name, comp.Name,
meta.EntityName, meta.EntityName,
charge, charge,
hpPercent,
chassis.ModuleCount, chassis.ModuleCount,
hasBrain, hasBrain,
canDisable); canDisable);
@@ -70,7 +82,7 @@ public sealed partial class BorgSystem
return; return;
} }
if (ent.Comp2.BrainEntity is not {} brain) if (ent.Comp2.BrainEntity is not { } brain)
return; return;
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3))); 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; 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;
}
} }

View File

@@ -50,4 +50,10 @@ public sealed partial class RoboticsConsoleComponent : Component
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField, AutoPausedField] [AutoNetworkedField, AutoPausedField]
public TimeSpan NextDestroy = TimeSpan.Zero; public TimeSpan NextDestroy = TimeSpan.Zero;
/// <summary>
/// Controls if the console can disable or destroy any borg.
/// </summary>
[DataField]
public bool AllowBorgControl = true;
} }

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -19,9 +19,15 @@ public sealed class RoboticsConsoleState : BoundUserInterfaceState
/// </summary> /// </summary>
public Dictionary<string, CyborgControlData> Cyborgs; 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; Cyborgs = cyborgs;
AllowBorgControl = allowBorgControl;
} }
} }
@@ -84,6 +90,12 @@ public partial record struct CyborgControlData
[DataField] [DataField]
public float Charge; public float Charge;
/// <summary>
/// HP level from 0 to 1.
/// </summary>
[DataField]
public float HpPercent; // 0.0 to 1.0
/// <summary> /// <summary>
/// How many modules this borg has, just useful information for roboticists. /// 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. /// 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))] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan Timeout = TimeSpan.Zero; 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; ChassisSprite = chassisSprite;
ChassisName = chassisName; ChassisName = chassisName;
Name = name; Name = name;
Charge = charge; Charge = charge;
HpPercent = hpPercent;
ModuleCount = moduleCount; ModuleCount = moduleCount;
HasBrain = hasBrain; HasBrain = hasBrain;
CanDisable = canDisable; CanDisable = canDisable;

View File

@@ -6,6 +6,7 @@ robotics-console-model = [color=gray]Model:[/color] {$name}
# name is not formatted to prevent players trolling # name is not formatted to prevent players trolling
robotics-console-designation = [color=gray]Designation:[/color] robotics-console-designation = [color=gray]Designation:[/color]
robotics-console-battery = [color=gray]Battery charge:[/color] [color={$color}]{$charge}[/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-modules = [color=gray]Modules installed:[/color] {$count}
robotics-console-brain = [color=gray]Brain installed:[/color] [color={$brain -> robotics-console-brain = [color=gray]Brain installed:[/color] [color={$brain ->
[true] green]Yes [true] green]Yes