diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs new file mode 100644 index 0000000000..bd3e21718f --- /dev/null +++ b/Content.Client/Commands/ShowHealthBarsCommand.cs @@ -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(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(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 index 2a9490eb62..0000000000 --- a/Content.Client/Commands/ToggleHealthOverlayCommand.cs +++ /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(); - 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 index 29ac937199..0000000000 --- a/Content.Client/HealthOverlay/HealthOverlaySystem.cs +++ /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 _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(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(); - while (query.MoveNext(out var entity, out var mobState, out _)) - { - if (_entities.GetComponent(ent).MapID != _entities.GetComponent(entity).MapID || - !viewBox.Contains(_entities.GetComponent(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 index be22f64c08..0000000000 --- a/Content.Client/HealthOverlay/UI/HealthOverlayBar.cs +++ /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().Index("unshaded").Instance(); - } - - private ShaderInstance Shader { get; } - - /// - /// From -1 (dead) to 0 (crit) and 1 (alive) - /// - 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 index e8ec77e540..0000000000 --- a/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs +++ /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(); - var mobThresholdSystem = _entities.EntitySysManager.GetEntitySystem(); - 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(Entity).MapID) - { - Visible = false; - return; - } - - Visible = true; - - var screenCoordinates = _eyeManager.CoordinatesToScreen(_entities.GetComponent(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 index 0000000000..6bf68fc3b5 --- /dev/null +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -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; + +/// +/// Overlay that shows a health bar on mobs. +/// +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 DamageContainers = new(); + + public EntityHealthBarOverlay(IEntityManager entManager) + { + _entManager = entManager; + _transform = _entManager.EntitySysManager.GetEntitySystem(); + _mobStateSystem = _entManager.EntitySysManager.GetEntitySystem(); + _mobThresholdSystem = _entManager.EntitySysManager.GetEntitySystem(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var handle = args.WorldHandle; + var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero; + var xformQuery = _entManager.GetEntityQuery(); + + const float scale = 1f; + var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3.CreateRotation(-rotation); + + var query = _entManager.AllEntityQueryEnumerator(); + while (query.MoveNext(out var uid, + out var mobThresholdsComponent, + out var mobStateComponent, + out var damageableComponent, + out var spriteComponent)) + { + if (_entManager.TryGetComponent(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); + } + + /// + /// Returns a ratio between 0 and 1, and whether the entity is in crit. + /// + 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; + } +} diff --git a/Content.Client/Overlays/EquipmentHudSystem.cs b/Content.Client/Overlays/EquipmentHudSystem.cs index 3ac2a36d53..c7578b6793 100644 --- a/Content.Client/Overlays/EquipmentHudSystem.cs +++ b/Content.Client/Overlays/EquipmentHudSystem.cs @@ -72,7 +72,7 @@ public abstract class EquipmentHudSystem : 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 : EntitySystem where T : IComponent protected virtual void OnRefreshEquipmentHud(EntityUid uid, T component, InventoryRelayedEvent> args) { - args.Args.Active = true; + OnRefreshComponentHud(uid, component, args.Args); } protected virtual void OnRefreshComponentHud(EntityUid uid, T component, RefreshEquipmentHudEvent 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(TargetSlots); diff --git a/Content.Client/Overlays/ShowHealthBarsSystem.cs b/Content.Client/Overlays/ShowHealthBarsSystem.cs new file mode 100644 index 0000000000..170f552cf3 --- /dev/null +++ b/Content.Client/Overlays/ShowHealthBarsSystem.cs @@ -0,0 +1,46 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Overlays; +using Robust.Client.Graphics; +using System.Linq; + +namespace Content.Client.Overlays; + +/// +/// Adds a health bar overlay. +/// +public sealed class ShowHealthBarsSystem : EquipmentHudSystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private EntityHealthBarOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(EntityManager); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent component) + { + base.UpdateInternal(component); + + foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers)) + { + _overlay.DamageContainers.Add(damageContainerId); + } + + if (!_overlayMan.HasOverlay()) + { + _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 index 0000000000..6ed9d6a41d --- /dev/null +++ b/Content.Client/Overlays/ShowHealthIconsSystem.cs @@ -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; + +/// +/// Shows a healthy icon on mobs. +/// +public sealed class ShowHealthIconsSystem : EquipmentHudSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeMan = default!; + + public HashSet DamageContainers = new(); + + [ValidatePrototypeId] + private const string HealthIconFine = "HealthIconFine"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetStatusIconsEvent); + + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent 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 DecideHealthIcons(DamageableComponent damageableComponent) + { + if (damageableComponent.DamageContainerID == null || + !DamageContainers.Contains(damageableComponent.DamageContainerID)) + { + return Array.Empty(); + } + + var result = new List(); + + // 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(HealthIconFine, out var healthyIcon)) + { + result.Add(healthyIcon); + } + + return result; + } +} diff --git a/Content.Client/Overlays/ShowHungerIconsSystem.cs b/Content.Client/Overlays/ShowHungerIconsSystem.cs index 0182a08678..58551b30c2 100644 --- a/Content.Client/Overlays/ShowHungerIconsSystem.cs +++ b/Content.Client/Overlays/ShowHungerIconsSystem.cs @@ -22,9 +22,9 @@ public sealed class ShowHungerIconsSystem : EquipmentHudSystem DecideHungerIcon(EntityUid uid, HungerComponent hungerComponent) diff --git a/Content.Client/Overlays/ShowSecurityIconsSystem.cs b/Content.Client/Overlays/ShowSecurityIconsSystem.cs index 28984c6f7a..77c14c5ef0 100644 --- a/Content.Client/Overlays/ShowSecurityIconsSystem.cs +++ b/Content.Client/Overlays/ShowSecurityIconsSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.StatusIcon.Components; using Robust.Shared.Prototypes; namespace Content.Client.Overlays; + public sealed class ShowSecurityIconsSystem : EquipmentHudSystem { [Dependency] private readonly IPrototypeManager _prototypeMan = default!; @@ -30,9 +31,9 @@ public sealed class ShowSecurityIconsSystem : EquipmentHudSystem DecideSecurityIcon(EntityUid uid) diff --git a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs index 25d4d9b895..a640726685 100644 --- a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs +++ b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs @@ -23,9 +23,9 @@ public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem SyndicateIcon(EntityUid uid, NukeOperativeComponent nukeOperativeComponent) diff --git a/Content.Client/Overlays/ShowThirstIconsSystem.cs b/Content.Client/Overlays/ShowThirstIconsSystem.cs index 89bc5029ba..f9d6d0ab25 100644 --- a/Content.Client/Overlays/ShowThirstIconsSystem.cs +++ b/Content.Client/Overlays/ShowThirstIconsSystem.cs @@ -22,9 +22,9 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem DecideThirstIcon(EntityUid uid, ThirstComponent thirstComponent) diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index 225dd3ceea..3e7161b1fb 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -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(); _transform = _entity.System(); _statusIcon = _entity.System(); - _shader = _prototype.Index("unshaded").Instance(); } diff --git a/Content.Shared/Inventory/Events/RefreshEquipmentHudEvent.cs b/Content.Shared/Inventory/Events/RefreshEquipmentHudEvent.cs index 2f2744331d..4f486fe695 100644 --- a/Content.Shared/Inventory/Events/RefreshEquipmentHudEvent.cs +++ b/Content.Shared/Inventory/Events/RefreshEquipmentHudEvent.cs @@ -4,7 +4,7 @@ public sealed class RefreshEquipmentHudEvent : EntityEventArgs, IInventoryRel { public SlotFlags TargetSlots { get; init; } public bool Active = false; - public object? ExtraData; + public List Components = new(); public RefreshEquipmentHudEvent(SlotFlags targetSlots) { diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index fb27811073..c43a588507 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -39,6 +39,8 @@ public partial class InventorySystem // ComponentActivatedClientSystems SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); diff --git a/Content.Shared/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs new file mode 100644 index 0000000000..48e3162269 --- /dev/null +++ b/Content.Shared/Overlays/ShowHealthBarsComponent.cs @@ -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; + +/// +/// This component allows you to see health bars above damageable mobs. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowHealthBarsComponent : Component +{ + /// + /// Displays health bars of the damage containers. + /// + [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List DamageContainers = new(); +} diff --git a/Content.Shared/Overlays/ShowHealthIconsComponent.cs b/Content.Shared/Overlays/ShowHealthIconsComponent.cs new file mode 100644 index 0000000000..c2526c2f40 --- /dev/null +++ b/Content.Shared/Overlays/ShowHealthIconsComponent.cs @@ -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; + +/// +/// This component allows you to see health status icons above damageable mobs. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowHealthIconsComponent : Component +{ + /// + /// Displays health status icons of the damage containers. + /// + [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List 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 index 0000000000..d660e93ce1 --- /dev/null +++ b/Resources/Locale/en-US/commands/show-health-bars-command.ftl @@ -0,0 +1,6 @@ +cmd-showhealthbars-desc = Toggles health bars above mobs. +cmd-showhealthbars-help = Usage: {$command} [] +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 index dd54e11d33..0000000000 --- a/Resources/Locale/en-US/commands/toggle-health-overlay-command.ftl +++ /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 diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml index 121f2d8f57..fe2f990838 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml @@ -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 @@ -19,6 +22,12 @@ 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 @@ -94,6 +103,12 @@ 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 @@ -106,6 +121,12 @@ 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 @@ -132,6 +153,13 @@ - type: Clothing sprite: Clothing/Eyes/Hud/medsecengi.rsi - type: ShowSecurityIcons + - type: ShowHealthBars + damageContainers: + - Biological + - Inorganic + - type: ShowHealthIcons + damageContainers: + - Biological - type: ShowSyndicateIcons - type: entity @@ -145,6 +173,13 @@ - 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 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index aa6e440cf6..cd7882d3d1 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -349,7 +349,13 @@ 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 index 0000000000..2ab90e7582 --- /dev/null +++ b/Resources/Prototypes/StatusEffects/health.yml @@ -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 index 6038bb659d..0000000000 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 index 9a6f00e2c2..0000000000 --- a/Resources/Textures/Interface/Misc/health_bar.rsi/meta.json +++ /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 index 0000000000..8d07f93496 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 index 0000000000..9bc6327145 --- /dev/null +++ b/Resources/Textures/Interface/Misc/health_icons.rsi/meta.json @@ -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