]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add health bar overlays for eye equipment (#21980)
authorPrPleGoo <PrPleGoo@users.noreply.github.com>
Thu, 4 Jan 2024 16:48:57 +0000 (17:48 +0100)
committerGitHub <noreply@github.com>
Thu, 4 Jan 2024 16:48:57 +0000 (11:48 -0500)
* PR 1

* fix an error with health bar overlay (#1292)

* Revert "Revert "Replace `ResourcePath` with `ResPath` (#15308)" (#155… (#15566)

* [1612] change ShowHealthBarsComponent's DamageContainer field to a list (#1662)

* fix build

* no crit entities from not updating

* cleanup

* namespace

* undu irrelevant changes

* undo icon change

* make health bar 1 px taller and icon 1 px shorter

* fix medibot

* fix comment

* don't show health bar ratio when in crit

* fix build

* put the crit bar back

* don't render healthbars for mobs that are in containers

* draw more boxes without the background sprite

* fine status icon for all bio mobs

* add wacky mandatory things

* attempt 2

* whoops wrong file

* cool, this works too

* move null check to top

* only 1 init

* security huds

* remove shader

* fix build after cleanup

* slight cleanup

* little more cleanup

* Remove clothing grant component system

* security HUD now shows a job icon on entities with a body

* remove sec stuff and do similar changes to split off PR + remove unused comp

* process comments

* don't return

* update to ComponentAddedOverlaySystemBase

* no cache

* colors and not rendering out of sight

* touch ups

* fix build & cleanup

* undo

* remove shader from icons

* process comments

* documentation

* fix licence

* validate prototype id

* just use args

* rename method and append in method

* type

* just fucken delete the command

* space

* undo

* remove

* don't use LocalPlayer

* re-add showhealthbars command, but working

* rename icon lists and conform health icon code to the others

* space

* undo

* update command

* oops

---------

Co-authored-by: Rane <60792108+Elijahrane@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
27 files changed:
Content.Client/Commands/ShowHealthBarsCommand.cs [new file with mode: 0644]
Content.Client/Commands/ToggleHealthOverlayCommand.cs [deleted file]
Content.Client/HealthOverlay/HealthOverlaySystem.cs [deleted file]
Content.Client/HealthOverlay/UI/HealthOverlayBar.cs [deleted file]
Content.Client/HealthOverlay/UI/HealthOverlayGui.cs [deleted file]
Content.Client/Overlays/EntityHealthBarOverlay.cs [new file with mode: 0644]
Content.Client/Overlays/EquipmentHudSystem.cs
Content.Client/Overlays/ShowHealthBarsSystem.cs [new file with mode: 0644]
Content.Client/Overlays/ShowHealthIconsSystem.cs [new file with mode: 0644]
Content.Client/Overlays/ShowHungerIconsSystem.cs
Content.Client/Overlays/ShowSecurityIconsSystem.cs
Content.Client/Overlays/ShowSyndicateIconsSystem.cs
Content.Client/Overlays/ShowThirstIconsSystem.cs
Content.Client/StatusIcon/StatusIconOverlay.cs
Content.Shared/Inventory/Events/RefreshEquipmentHudEvent.cs
Content.Shared/Inventory/InventorySystem.Relay.cs
Content.Shared/Overlays/ShowHealthBarsComponent.cs [new file with mode: 0644]
Content.Shared/Overlays/ShowHealthIconsComponent.cs [new file with mode: 0644]
Resources/Locale/en-US/commands/show-health-bars-command.ftl [new file with mode: 0644]
Resources/Locale/en-US/commands/toggle-health-overlay-command.ftl [deleted file]
Resources/Prototypes/Entities/Clothing/Eyes/hud.yml
Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
Resources/Prototypes/StatusEffects/health.yml [new file with mode: 0644]
Resources/Textures/Interface/Misc/health_bar.rsi/icon.png [deleted file]
Resources/Textures/Interface/Misc/health_bar.rsi/meta.json [deleted file]
Resources/Textures/Interface/Misc/health_icons.rsi/Fine.png [new file with mode: 0644]
Resources/Textures/Interface/Misc/health_icons.rsi/meta.json [new file with mode: 0644]

diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs
new file mode 100644 (file)
index 0000000..bd3e217
--- /dev/null
@@ -0,0 +1,54 @@
+using Content.Shared.Overlays;
+using Robust.Client.Player;
+using Robust.Shared.Console;
+using System.Linq;
+
+namespace Content.Client.Commands;
+
+public sealed class ShowHealthBarsCommand : LocalizedCommands
+{
+    [Dependency] private readonly IPlayerManager _playerManager = default!;
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+
+    public override string Command => "showhealthbars";
+
+    public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+    public override void Execute(IConsoleShell shell, string argStr, string[] args)
+    {
+        var player = _playerManager.LocalSession;
+        if (player == null)
+        {
+            shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-not-player"));
+            return;
+        }
+
+        var playerEntity = player?.AttachedEntity;
+        if (!playerEntity.HasValue)
+        {
+            shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-no-entity"));
+            return;
+        }
+
+        if (!_entityManager.HasComponent<ShowHealthBarsComponent>(playerEntity))
+        {
+            var showHealthBarsComponent = new ShowHealthBarsComponent
+            {
+                DamageContainers = args.ToList(),
+                NetSyncEnabled = false
+            };
+
+            _entityManager.AddComponent(playerEntity.Value, showHealthBarsComponent, true);
+
+            shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-enabled", ("args", string.Join(", ", args))));
+            return;
+        }
+        else
+        {
+            _entityManager.RemoveComponentDeferred<ShowHealthBarsComponent>(playerEntity.Value);
+            shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-disabled"));
+        }
+
+        return;
+    }
+}
diff --git a/Content.Client/Commands/ToggleHealthOverlayCommand.cs b/Content.Client/Commands/ToggleHealthOverlayCommand.cs
deleted file mode 100644 (file)
index 2a9490e..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Client.HealthOverlay;
-using Robust.Shared.Console;
-
-namespace Content.Client.Commands;
-
-public sealed class ToggleHealthOverlayCommand : LocalizedCommands
-{
-    [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
-
-    public override string Command => "togglehealthoverlay";
-
-    public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
-
-    public override void Execute(IConsoleShell shell, string argStr, string[] args)
-    {
-        var system = _entitySystemManager.GetEntitySystem<HealthOverlaySystem>();
-        system.Enabled = !system.Enabled;
-
-        shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify", ("state", system.Enabled ? "enabled" : "disabled")));
-    }
-}
diff --git a/Content.Client/HealthOverlay/HealthOverlaySystem.cs b/Content.Client/HealthOverlay/HealthOverlaySystem.cs
deleted file mode 100644 (file)
index 29ac937..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-using Content.Client.HealthOverlay.UI;
-using Content.Shared.Damage;
-using Content.Shared.GameTicking;
-using Content.Shared.Mobs.Components;
-using JetBrains.Annotations;
-using Robust.Client.Graphics;
-using Robust.Client.Player;
-
-namespace Content.Client.HealthOverlay
-{
-    [UsedImplicitly]
-    public sealed class HealthOverlaySystem : EntitySystem
-    {
-        [Dependency] private readonly IEyeManager _eyeManager = default!;
-        [Dependency] private readonly IEntityManager _entities = default!;
-        [Dependency] private readonly IPlayerManager _player = default!;
-
-        private readonly Dictionary<EntityUid, HealthOverlayGui> _guis = new();
-        private bool _enabled;
-
-        public bool Enabled
-        {
-            get => _enabled;
-            set
-            {
-                if (_enabled == value)
-                {
-                    return;
-                }
-
-                _enabled = value;
-
-                foreach (var gui in _guis.Values)
-                {
-                    gui.SetVisibility(value);
-                }
-            }
-        }
-
-        public override void Initialize()
-        {
-            base.Initialize();
-
-            SubscribeNetworkEvent<RoundRestartCleanupEvent>(Reset);
-        }
-
-        public void Reset(RoundRestartCleanupEvent ev)
-        {
-            foreach (var gui in _guis.Values)
-            {
-                gui.Dispose();
-            }
-
-            _guis.Clear();
-        }
-
-        public override void FrameUpdate(float frameTime)
-        {
-            base.Update(frameTime);
-
-            if (!_enabled)
-            {
-                return;
-            }
-
-            if (_player.LocalEntity is not {} ent || Deleted(ent))
-            {
-                return;
-            }
-
-            var viewBox = _eyeManager.GetWorldViewport().Enlarged(2.0f);
-
-            var query = EntityQueryEnumerator<MobStateComponent, DamageableComponent>();
-            while (query.MoveNext(out var entity, out var mobState, out _))
-            {
-                if (_entities.GetComponent<TransformComponent>(ent).MapID != _entities.GetComponent<TransformComponent>(entity).MapID ||
-                    !viewBox.Contains(_entities.GetComponent<TransformComponent>(entity).WorldPosition))
-                {
-                    if (_guis.TryGetValue(entity, out var oldGui))
-                    {
-                        _guis.Remove(entity);
-                        oldGui.Dispose();
-                    }
-
-                    continue;
-                }
-
-                if (_guis.ContainsKey(entity))
-                {
-                    continue;
-                }
-
-                var gui = new HealthOverlayGui(entity);
-                _guis.Add(entity, gui);
-            }
-        }
-    }
-}
diff --git a/Content.Client/HealthOverlay/UI/HealthOverlayBar.cs b/Content.Client/HealthOverlay/UI/HealthOverlayBar.cs
deleted file mode 100644 (file)
index be22f64..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.HealthOverlay.UI
-{
-    public sealed class HealthOverlayBar : Control
-    {
-        public const byte HealthBarScale = 2;
-
-        private const int XPixelDiff = 20 * HealthBarScale;
-
-        public HealthOverlayBar()
-        {
-            IoCManager.InjectDependencies(this);
-            Shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
-        }
-
-        private ShaderInstance Shader { get; }
-
-        /// <summary>
-        ///     From -1 (dead) to 0 (crit) and 1 (alive)
-        /// </summary>
-        public float Ratio { get; set; }
-
-        public Color Color { get; set; }
-
-        protected override void Draw(DrawingHandleScreen handle)
-        {
-            base.Draw(handle);
-
-            handle.UseShader(Shader);
-
-            var leftOffset = 2 * HealthBarScale;
-            var box = new UIBox2i(
-                leftOffset,
-                -2 + 2 * HealthBarScale,
-                leftOffset + (int) (XPixelDiff * Ratio * UIScale),
-                -2);
-
-            handle.DrawRect(box, Color);
-        }
-    }
-}
diff --git a/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs b/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs
deleted file mode 100644 (file)
index e8ec77e..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-using System.Numerics;
-using Content.Client.IoC;
-using Content.Client.Resources;
-using Content.Shared.Damage;
-using Content.Shared.FixedPoint;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Timing;
-
-namespace Content.Client.HealthOverlay.UI
-{
-    public sealed class HealthOverlayGui : BoxContainer
-    {
-        [Dependency] private readonly IEyeManager _eyeManager = default!;
-        [Dependency] private readonly IEntityManager _entities = default!;
-
-        public HealthOverlayGui(EntityUid entity)
-        {
-            IoCManager.InjectDependencies(this);
-            UserInterfaceManager.WindowRoot.AddChild(this);
-            SeparationOverride = 0;
-            Orientation = LayoutOrientation.Vertical;
-
-            CritBar = new HealthOverlayBar
-            {
-                Visible = false,
-                VerticalAlignment = VAlignment.Center,
-                Color = Color.Red
-            };
-
-            HealthBar = new HealthOverlayBar
-            {
-                Visible = false,
-                VerticalAlignment = VAlignment.Center,
-                Color = Color.LimeGreen
-            };
-
-            AddChild(Panel = new PanelContainer
-            {
-                Children =
-                {
-                    new TextureRect
-                    {
-                        Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/Misc/health_bar.rsi/icon.png"),
-                        TextureScale = Vector2.One * HealthOverlayBar.HealthBarScale,
-                        VerticalAlignment = VAlignment.Center,
-                    },
-                    CritBar,
-                    HealthBar
-                }
-            });
-
-            Entity = entity;
-        }
-
-        public PanelContainer Panel { get; }
-
-        public HealthOverlayBar HealthBar { get; }
-
-        public HealthOverlayBar CritBar { get; }
-
-        public EntityUid Entity { get; }
-
-        public void SetVisibility(bool val)
-        {
-            Visible = val;
-            Panel.Visible = val;
-        }
-
-        private void MoreFrameUpdate()
-        {
-            if (_entities.Deleted(Entity))
-            {
-                return;
-            }
-
-            if (!_entities.TryGetComponent(Entity, out MobStateComponent? mobState) ||
-                !_entities.TryGetComponent(Entity, out DamageableComponent? damageable))
-            {
-                CritBar.Visible = false;
-                HealthBar.Visible = false;
-                return;
-            }
-
-            var mobStateSystem = _entities.EntitySysManager.GetEntitySystem<MobStateSystem>();
-            var mobThresholdSystem = _entities.EntitySysManager.GetEntitySystem<MobThresholdSystem>();
-            if (mobStateSystem.IsAlive(Entity, mobState))
-            {
-                if (!mobThresholdSystem.TryGetThresholdForState(Entity,MobState.Critical, out var threshold))
-                {
-                    CritBar.Visible = false;
-                    HealthBar.Visible = false;
-                    return;
-                }
-
-                CritBar.Ratio = 1;
-                CritBar.Visible = true;
-                HealthBar.Ratio = 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float();
-                HealthBar.Visible = true;
-            }
-            else if (mobStateSystem.IsCritical(Entity, mobState))
-            {
-                HealthBar.Ratio = 0;
-                HealthBar.Visible = false;
-
-                if (!mobThresholdSystem.TryGetThresholdForState(Entity, MobState.Critical, out var critThreshold) ||
-                    !mobThresholdSystem.TryGetThresholdForState(Entity, MobState.Dead, out var deadThreshold))
-                {
-                    CritBar.Visible = false;
-                    return;
-                }
-
-                CritBar.Visible = true;
-                CritBar.Ratio = 1 -
-                    ((damageable.TotalDamage - critThreshold) /
-                    (deadThreshold - critThreshold)).Value.Float();
-            }
-            else if (mobStateSystem.IsDead(Entity, mobState))
-            {
-                CritBar.Ratio = 0;
-                CritBar.Visible = false;
-                HealthBar.Ratio = 0;
-                HealthBar.Visible = true;
-            }
-            else
-            {
-                CritBar.Visible = false;
-                HealthBar.Visible = false;
-            }
-        }
-
-        protected override void FrameUpdate(FrameEventArgs args)
-        {
-            base.FrameUpdate(args);
-
-            MoreFrameUpdate();
-
-            if (_entities.Deleted(Entity) || _eyeManager.CurrentMap != _entities.GetComponent<TransformComponent>(Entity).MapID)
-            {
-                Visible = false;
-                return;
-            }
-
-            Visible = true;
-
-            var screenCoordinates = _eyeManager.CoordinatesToScreen(_entities.GetComponent<TransformComponent>(Entity).Coordinates);
-            var playerPosition = UserInterfaceManager.ScreenToUIPosition(screenCoordinates);
-            LayoutContainer.SetPosition(this, new Vector2(playerPosition.X - Width / 2, playerPosition.Y - Height - 30.0f));
-        }
-
-        protected override void Dispose(bool disposing)
-        {
-            base.Dispose(disposing);
-
-            if (!disposing)
-                return;
-
-            HealthBar.Dispose();
-        }
-    }
-}
diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs
new file mode 100644 (file)
index 0000000..6bf68fc
--- /dev/null
@@ -0,0 +1,170 @@
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Shared.Enums;
+using System.Numerics;
+using static Robust.Shared.Maths.Color;
+
+namespace Content.Client.Overlays;
+
+/// <summary>
+/// Overlay that shows a health bar on mobs.
+/// </summary>
+public sealed class EntityHealthBarOverlay : Overlay
+{
+    private readonly IEntityManager _entManager;
+    private readonly SharedTransformSystem _transform;
+    private readonly MobStateSystem _mobStateSystem;
+    private readonly MobThresholdSystem _mobThresholdSystem;
+    public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
+    public HashSet<string> DamageContainers = new();
+
+    public EntityHealthBarOverlay(IEntityManager entManager)
+    {
+        _entManager = entManager;
+        _transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
+        _mobStateSystem = _entManager.EntitySysManager.GetEntitySystem<MobStateSystem>();
+        _mobThresholdSystem = _entManager.EntitySysManager.GetEntitySystem<MobThresholdSystem>();
+    }
+
+    protected override void Draw(in OverlayDrawArgs args)
+    {
+        var handle = args.WorldHandle;
+        var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
+        var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
+
+        const float scale = 1f;
+        var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
+        var rotationMatrix = Matrix3.CreateRotation(-rotation);
+
+        var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
+        while (query.MoveNext(out var uid,
+            out var mobThresholdsComponent,
+            out var mobStateComponent,
+            out var damageableComponent,
+            out var spriteComponent))
+        {
+            if (_entManager.TryGetComponent<MetaDataComponent>(uid, out var metaDataComponent) &&
+                metaDataComponent.Flags.HasFlag(MetaDataFlags.InContainer))
+            {
+                continue;
+            }
+
+            if (!xformQuery.TryGetComponent(uid, out var xform) ||
+                xform.MapID != args.MapId)
+            {
+                continue;
+            }
+
+            if (damageableComponent.DamageContainerID == null || !DamageContainers.Contains(damageableComponent.DamageContainerID))
+            {
+                continue;
+            }
+
+            var bounds = spriteComponent.Bounds;
+            var worldPos = _transform.GetWorldPosition(xform, xformQuery);
+
+            if (!bounds.Translated(worldPos).Intersects(args.WorldAABB))
+            {
+                continue;
+            }
+
+            var worldPosition = _transform.GetWorldPosition(xform);
+            var worldMatrix = Matrix3.CreateTranslation(worldPosition);
+
+            Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
+            Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+
+            handle.SetTransform(matty);
+
+            var yOffset = spriteComponent.Bounds.Height * EyeManager.PixelsPerMeter / 2 - 3f;
+            var widthOfMob = spriteComponent.Bounds.Width * EyeManager.PixelsPerMeter;
+
+            var position = new Vector2(-widthOfMob / EyeManager.PixelsPerMeter / 2, yOffset / EyeManager.PixelsPerMeter);
+
+            // we are all progressing towards death every day
+            (float ratio, bool inCrit) deathProgress = CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent);
+
+            var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit);
+
+            // Hardcoded width of the progress bar because it doesn't match the texture.
+            const float startX = 8f;
+            var endX = widthOfMob - 8f;
+
+            var xProgress = (endX - startX) * deathProgress.ratio + startX;
+
+            var boxBackground = new Box2(new Vector2(startX, 0f) / EyeManager.PixelsPerMeter, new Vector2(endX, 3f) / EyeManager.PixelsPerMeter);
+            boxBackground = boxBackground.Translated(position);
+            handle.DrawRect(boxBackground, Black.WithAlpha(192));
+
+            var boxMain = new Box2(new Vector2(startX, 0f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 3f) / EyeManager.PixelsPerMeter);
+            boxMain = boxMain.Translated(position);
+            handle.DrawRect(boxMain, color);
+
+            var pixelDarken = new Box2(new Vector2(startX, 2f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 3f) / EyeManager.PixelsPerMeter);
+            pixelDarken = pixelDarken.Translated(position);
+            handle.DrawRect(pixelDarken, Black.WithAlpha(128));
+        }
+
+        handle.UseShader(null);
+        handle.SetTransform(Matrix3.Identity);
+    }
+
+    /// <summary>
+    /// Returns a ratio between 0 and 1, and whether the entity is in crit.
+    /// </summary>
+    private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
+    {
+        if (_mobStateSystem.IsAlive(uid, component))
+        {
+            if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) &&
+                !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds))
+                return (1, false);
+
+            var ratio = 1 - ((FixedPoint2) (dmg.TotalDamage / threshold)).Float();
+            return (ratio, false);
+        }
+
+        if (_mobStateSystem.IsCritical(uid, component))
+        {
+            if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, thresholds) ||
+                !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out var deadThreshold, thresholds))
+            {
+                return (1, true);
+            }
+
+            var ratio = 1 - ((dmg.TotalDamage - critThreshold) / (deadThreshold - critThreshold)).Value.Float();
+
+            return (ratio, true);
+        }
+
+        return (0, true);
+    }
+
+    public static Color GetProgressColor(float progress, bool crit)
+    {
+        if (progress >= 1.0f)
+        {
+            return SeaBlue;
+        }
+
+        if (!crit)
+        {
+            switch (progress)
+            {
+                case > 0.90F:
+                    return SeaBlue;
+                case > 0.50F:
+                    return Violet;
+                case > 0.15F:
+                    return Ruber;
+            }
+        }
+
+        return VividGamboge;
+    }
+}
index 3ac2a36d53f5de65644c32390cbe1bd988fb09a8..c7578b6793f5e90e115e1e89d12e051490d9dafd 100644 (file)
@@ -72,7 +72,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
 
     private void OnPlayerDetached(LocalPlayerDetachedEvent args)
     {
-        if (_player.LocalPlayer?.ControlledEntity == null)
+        if (_player.LocalSession?.AttachedEntity == null)
             Deactivate();
     }
 
@@ -93,17 +93,18 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
 
     protected virtual void OnRefreshEquipmentHud(EntityUid uid, T component, InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args)
     {
-        args.Args.Active = true;
+        OnRefreshComponentHud(uid, component, args.Args);
     }
 
     protected virtual void OnRefreshComponentHud(EntityUid uid, T component, RefreshEquipmentHudEvent<T> args)
     {
         args.Active = true;
+        args.Components.Add(component);
     }
 
     private void RefreshOverlay(EntityUid uid)
     {
-        if (uid != _player.LocalPlayer?.ControlledEntity)
+        if (uid != _player.LocalSession?.AttachedEntity)
             return;
 
         var ev = new RefreshEquipmentHudEvent<T>(TargetSlots);
diff --git a/Content.Client/Overlays/ShowHealthBarsSystem.cs b/Content.Client/Overlays/ShowHealthBarsSystem.cs
new file mode 100644 (file)
index 0000000..170f552
--- /dev/null
@@ -0,0 +1,46 @@
+using Content.Shared.Inventory.Events;
+using Content.Shared.Overlays;
+using Robust.Client.Graphics;
+using System.Linq;
+
+namespace Content.Client.Overlays;
+
+/// <summary>
+/// Adds a health bar overlay.
+/// </summary>
+public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComponent>
+{
+    [Dependency] private readonly IOverlayManager _overlayMan = default!;
+
+    private EntityHealthBarOverlay _overlay = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _overlay = new(EntityManager);
+    }
+
+    protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
+    {
+        base.UpdateInternal(component);
+
+        foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers))
+        {
+            _overlay.DamageContainers.Add(damageContainerId);
+        }
+
+        if (!_overlayMan.HasOverlay<EntityHealthBarOverlay>())
+        {
+            _overlayMan.AddOverlay(_overlay);
+        }
+    }
+
+    protected override void DeactivateInternal()
+    {
+        base.DeactivateInternal();
+
+        _overlay.DamageContainers.Clear();
+        _overlayMan.RemoveOverlay(_overlay);
+    }
+}
diff --git a/Content.Client/Overlays/ShowHealthIconsSystem.cs b/Content.Client/Overlays/ShowHealthIconsSystem.cs
new file mode 100644 (file)
index 0000000..6ed9d6a
--- /dev/null
@@ -0,0 +1,77 @@
+using Content.Shared.Damage;
+using Content.Shared.Inventory.Events;
+using Content.Shared.Overlays;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+using System.Linq;
+
+namespace Content.Client.Overlays;
+
+/// <summary>
+/// Shows a healthy icon on mobs.
+/// </summary>
+public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsComponent>
+{
+    [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
+
+    public HashSet<string> DamageContainers = new();
+
+    [ValidatePrototypeId<StatusIconPrototype>]
+    private const string HealthIconFine = "HealthIconFine";
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
+
+    }
+
+    protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component)
+    {
+        base.UpdateInternal(component);
+
+        foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers))
+        {
+            DamageContainers.Add(damageContainerId);
+        }
+    }
+
+    protected override void DeactivateInternal()
+    {
+        base.DeactivateInternal();
+
+        DamageContainers.Clear();
+    }
+
+    private void OnGetStatusIconsEvent(EntityUid uid, DamageableComponent damageableComponent, ref GetStatusIconsEvent args)
+    {
+        if (!IsActive || args.InContainer)
+            return;
+
+        var healthIcons = DecideHealthIcons(damageableComponent);
+
+        args.StatusIcons.AddRange(healthIcons);
+    }
+
+    private IReadOnlyList<StatusIconPrototype> DecideHealthIcons(DamageableComponent damageableComponent)
+    {
+        if (damageableComponent.DamageContainerID == null ||
+            !DamageContainers.Contains(damageableComponent.DamageContainerID))
+        {
+            return Array.Empty<StatusIconPrototype>();
+        }
+
+        var result = new List<StatusIconPrototype>();
+
+        // Here you could check health status, diseases, mind status, etc. and pick a good icon, or multiple depending on whatever.
+        if (damageableComponent?.DamageContainerID == "Biological" &&
+            _prototypeMan.TryIndex<StatusIconPrototype>(HealthIconFine, out var healthyIcon))
+        {
+            result.Add(healthyIcon);
+        }
+
+        return result;
+    }
+}
index 0182a08678b45dc3d02f5dee26b26af6c7824aa7..58551b30c264a056bd9559e4846ebd201365d6d9 100644 (file)
@@ -22,9 +22,9 @@ public sealed class ShowHungerIconsSystem : EquipmentHudSystem<ShowHungerIconsCo
         if (!IsActive || args.InContainer)
             return;
 
-        var healthIcons = DecideHungerIcon(uid, hungerComponent);
+        var hungerIcons = DecideHungerIcon(uid, hungerComponent);
 
-        args.StatusIcons.AddRange(healthIcons);
+        args.StatusIcons.AddRange(hungerIcons);
     }
 
     private IReadOnlyList<StatusIconPrototype> DecideHungerIcon(EntityUid uid, HungerComponent hungerComponent)
index 28984c6f7a8bb0e029a0d3b10ff06088855b82cb..77c14c5ef0f7c7481e1cc74bbe55291bafacfb6c 100644 (file)
@@ -8,6 +8,7 @@ using Content.Shared.StatusIcon.Components;
 using Robust.Shared.Prototypes;
 
 namespace Content.Client.Overlays;
+
 public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIconsComponent>
 {
     [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
@@ -30,9 +31,9 @@ public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIco
             return;
         }
 
-        var healthIcons = DecideSecurityIcon(uid);
+        var securityIcons = DecideSecurityIcon(uid);
 
-        @event.StatusIcons.AddRange(healthIcons);
+        @event.StatusIcons.AddRange(securityIcons);
     }
 
     private IReadOnlyList<StatusIconPrototype> DecideSecurityIcon(EntityUid uid)
index 25d4d9b895b7c575b8ef47ec5c888f3eb1d137e8..a6407266853bbcf27b75e024ebdc55c87a4af9a1 100644 (file)
@@ -23,9 +23,9 @@ public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem<ShowSyndicateI
             return;
         }
 
-        var healthIcons = SyndicateIcon(uid, nukeOperativeComponent);
+        var syndicateIcons = SyndicateIcon(uid, nukeOperativeComponent);
 
-        args.StatusIcons.AddRange(healthIcons);
+        args.StatusIcons.AddRange(syndicateIcons);
     }
 
     private IReadOnlyList<StatusIconPrototype> SyndicateIcon(EntityUid uid, NukeOperativeComponent nukeOperativeComponent)
index 89bc5029ba9a2d22c0a411b8d1014ceb5993a006..f9d6d0ab2592e489439455297d3c0c38aeab43fa 100644 (file)
@@ -22,9 +22,9 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsCo
         if (!IsActive || args.InContainer)
             return;
 
-        var healthIcons = DecideThirstIcon(uid, thirstComponent);
+        var thirstIcons = DecideThirstIcon(uid, thirstComponent);
 
-        args.StatusIcons.AddRange(healthIcons);
+        args.StatusIcons.AddRange(thirstIcons);
     }
 
     private IReadOnlyList<StatusIconPrototype> DecideThirstIcon(EntityUid uid, ThirstComponent thirstComponent)
index 225dd3ceea440a73375da1ee2ff5185b3694d350..3e7161b1fb5f00a1bbe13a5b3053dd9dda7b9407 100644 (file)
@@ -16,7 +16,6 @@ public sealed class StatusIconOverlay : Overlay
     private readonly SpriteSystem _sprite;
     private readonly TransformSystem _transform;
     private readonly StatusIconSystem _statusIcon;
-
     private readonly ShaderInstance _shader;
 
     public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
@@ -28,7 +27,6 @@ public sealed class StatusIconOverlay : Overlay
         _sprite = _entity.System<SpriteSystem>();
         _transform = _entity.System<TransformSystem>();
         _statusIcon = _entity.System<StatusIconSystem>();
-
         _shader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
     }
 
index 2f2744331df5293b96fb27d26c59db9ebc1899f0..4f486fe695e7b47cd9fab07319f1ddccd85e6b7d 100644 (file)
@@ -4,7 +4,7 @@ public sealed class RefreshEquipmentHudEvent<T> : EntityEventArgs, IInventoryRel
 {
     public SlotFlags TargetSlots { get; init; }
     public bool Active = false;
-    public object? ExtraData;
+    public List<T> Components = new();
 
     public RefreshEquipmentHudEvent(SlotFlags targetSlots)
     {
index fb27811073203e4057649c3892b49711758ad450..c43a5885077371c02436bd28470f5da23864a4ad 100644 (file)
@@ -39,6 +39,8 @@ public partial class InventorySystem
 
         // ComponentActivatedClientSystems
         SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSecurityIconsComponent>>(RelayInventoryEvent);
+        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RelayInventoryEvent);
+        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RelayInventoryEvent);
diff --git a/Content.Shared/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs
new file mode 100644 (file)
index 0000000..48e3162
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Damage.Prototypes;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Overlays;
+
+/// <summary>
+/// This component allows you to see health bars above damageable mobs.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowHealthBarsComponent : Component
+{
+    /// <summary>
+    /// Displays health bars of the damage containers.
+    /// </summary>
+    [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageContainerPrototype>))]
+    public List<string> DamageContainers = new();
+}
diff --git a/Content.Shared/Overlays/ShowHealthIconsComponent.cs b/Content.Shared/Overlays/ShowHealthIconsComponent.cs
new file mode 100644 (file)
index 0000000..c2526c2
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Damage.Prototypes;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Overlays;
+
+/// <summary>
+/// This component allows you to see health status icons above damageable mobs.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowHealthIconsComponent : Component
+{
+    /// <summary>
+    /// Displays health status icons of the damage containers.
+    /// </summary>
+    [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageContainerPrototype>))]
+    public List<string> DamageContainers = new();
+}
diff --git a/Resources/Locale/en-US/commands/show-health-bars-command.ftl b/Resources/Locale/en-US/commands/show-health-bars-command.ftl
new file mode 100644 (file)
index 0000000..d660e93
--- /dev/null
@@ -0,0 +1,6 @@
+cmd-showhealthbars-desc = Toggles health bars above mobs.
+cmd-showhealthbars-help = Usage: {$command} [<DamageContainerId>]
+cmd-showhealthbars-error-not-player = You aren't a player.
+cmd-showhealthbars-error-no-entity = You do not have an attached entity.
+cmd-showhealthbars-notify-enabled = Enabled health overlay for DamageContainers: {$args}.
+cmd-showhealthbars-notify-disabled = Disabled health overlay.
\ No newline at end of file
diff --git a/Resources/Locale/en-US/commands/toggle-health-overlay-command.ftl b/Resources/Locale/en-US/commands/toggle-health-overlay-command.ftl
deleted file mode 100644 (file)
index dd54e11..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-cmd-togglehealthoverlay-desc = Toggles a health bar above mobs.
-cmd-togglehealthoverlay-help = Usage: {$command}
-cmd-togglehealthoverlay-notify = Health overlay system {$state}.
\ No newline at end of file
index 121f2d8f57a2abbb8632053f66348e8d78fe2d0e..fe2f990838d475be8e1500e6e49d49102df23dc5 100644 (file)
@@ -8,6 +8,9 @@
     sprite: Clothing/Eyes/Hud/diag.rsi
   - type: Clothing
     sprite: Clothing/Eyes/Hud/diag.rsi
+  - type: ShowHealthBars
+    damageContainers: 
+    - Inorganic
 
 - type: entity
   parent: ClothingEyesBase
     sprite: Clothing/Eyes/Hud/med.rsi
   - type: Clothing
     sprite: Clothing/Eyes/Hud/med.rsi
+  - type: ShowHealthBars
+    damageContainers: 
+    - Biological
+  - type: ShowHealthIcons
+    damageContainers: 
+    - Biological
 
 - type: entity
   parent: ClothingEyesBase
     sprite: Clothing/Eyes/Hud/medonion.rsi
   - type: Clothing
     sprite: Clothing/Eyes/Hud/medonion.rsi
+  - type: ShowHealthBars
+    damageContainers: 
+    - Biological
+  - type: ShowHealthIcons
+    damageContainers: 
+    - Biological
   - type: ShowHungerIcons
 
 - type: entity
     sprite: Clothing/Eyes/Hud/medonionbeer.rsi
   - type: Clothing
     sprite: Clothing/Eyes/Hud/medonionbeer.rsi
+  - type: ShowHealthBars
+    damageContainers: 
+    - Biological
+  - type: ShowHealthIcons
+    damageContainers: 
+    - Biological
   - type: ShowHungerIcons
   - type: ShowThirstIcons
 
   - type: Clothing
     sprite: Clothing/Eyes/Hud/medsecengi.rsi
   - type: ShowSecurityIcons
+  - type: ShowHealthBars
+    damageContainers: 
+    - Biological
+    - Inorganic
+  - type: ShowHealthIcons
+    damageContainers: 
+    - Biological
   - type: ShowSyndicateIcons
 
 - type: entity
   - type: Clothing
     sprite: Clothing/Eyes/Hud/omni.rsi
   - type: ShowSecurityIcons
+  - type: ShowHealthBars
+    damageContainers: 
+    - Biological
+    - Inorganic
+  - type: ShowHealthIcons
+    damageContainers: 
+    - Biological
   - type: ShowHungerIcons
   - type: ShowThirstIcons
   - type: ShowSyndicateIcons
index aa6e440cf6eea3376ecf4476c3a21832950d1d54..cd7882d3d1d2d7eef858d4b5149838f2623f1f76 100644 (file)
     interactFailureString: petting-failure-medibot
     interactSuccessSound:
       path: /Audio/Ambience/Objects/periodic_beep.ogg
-
+  - type: ShowHealthBars
+    damageContainers: 
+    - Biological
+  - type: ShowHealthIcons
+    damageContainers: 
+    - Biological
+  
 - type: entity
   parent: MobSiliconBase
   id: MobMimeBot
diff --git a/Resources/Prototypes/StatusEffects/health.yml b/Resources/Prototypes/StatusEffects/health.yml
new file mode 100644 (file)
index 0000000..2ab90e7
--- /dev/null
@@ -0,0 +1,7 @@
+- type: statusIcon
+  id: HealthIconFine
+  priority: 1
+  icon:
+    sprite: Interface/Misc/health_icons.rsi
+    state: Fine
+  locationPreference: Right
\ No newline at end of file
diff --git a/Resources/Textures/Interface/Misc/health_bar.rsi/icon.png b/Resources/Textures/Interface/Misc/health_bar.rsi/icon.png
deleted file mode 100644 (file)
index 6038bb6..0000000
Binary files a/Resources/Textures/Interface/Misc/health_bar.rsi/icon.png and /dev/null differ
diff --git a/Resources/Textures/Interface/Misc/health_bar.rsi/meta.json b/Resources/Textures/Interface/Misc/health_bar.rsi/meta.json
deleted file mode 100644 (file)
index 9a6f00e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "version": 1,
-    "size": {
-        "y": 7,
-        "x": 24
-    },
-    "license": "CC-BY-SA-3.0",
-    "copyright": "https://github.com/tgstation/tgstation/blob/886ca0f8dddf83ecaf10c92ff106172722352192/icons/effects/progessbar.dmi",
-    "states": [
-        {
-            "name": "icon"
-        }
-    ]
-}
diff --git a/Resources/Textures/Interface/Misc/health_icons.rsi/Fine.png b/Resources/Textures/Interface/Misc/health_icons.rsi/Fine.png
new file mode 100644 (file)
index 0000000..8d07f93
Binary files /dev/null and b/Resources/Textures/Interface/Misc/health_icons.rsi/Fine.png differ
diff --git a/Resources/Textures/Interface/Misc/health_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/health_icons.rsi/meta.json
new file mode 100644 (file)
index 0000000..9bc6327
--- /dev/null
@@ -0,0 +1,14 @@
+{
+    "version": 1,
+    "size": {
+        "x": 8,
+        "y": 8
+    },
+    "license": "CC-BY-SA-3.0",
+    "copyright": "https://github.com/tgstation/tgstation/blob/master/icons/mob/huds/hud.dmi",
+    "states": [
+        {
+            "name": "Fine"
+        }
+    ]
+}
\ No newline at end of file