From 044d5f68530ba210ee70358b8ab0364946f53d83 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Tue, 27 Jun 2023 20:31:53 -0400 Subject: [PATCH] Status Icons (#17529) --- .../StatusIcon/StatusIconOverlay.cs | 92 ++++++++++++++++++ Content.Client/StatusIcon/StatusIconSystem.cs | 66 +++++++++++++ Content.Shared/CCVar/CCVars.cs | 12 +++ .../Components/StatusIconComponent.cs | 27 +++++ .../StatusIcon/SharedStatusIconSystem.cs | 9 ++ .../StatusIcon/StatusIconPrototype.cs | 40 ++++++++ .../Entities/Mobs/NPCs/simplemob.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + Resources/Prototypes/StatusIcon/antag.yml | 6 ++ Resources/Prototypes/StatusIcon/debug.yml | 19 ++++ .../Interface/Misc/job_icons.rsi/Zombie.png | Bin 0 -> 159 bytes .../Interface/Misc/job_icons.rsi/meta.json | 3 + 12 files changed, 276 insertions(+) create mode 100644 Content.Client/StatusIcon/StatusIconOverlay.cs create mode 100644 Content.Client/StatusIcon/StatusIconSystem.cs create mode 100644 Content.Shared/StatusIcon/Components/StatusIconComponent.cs create mode 100644 Content.Shared/StatusIcon/SharedStatusIconSystem.cs create mode 100644 Content.Shared/StatusIcon/StatusIconPrototype.cs create mode 100644 Resources/Prototypes/StatusIcon/antag.yml create mode 100644 Resources/Prototypes/StatusIcon/debug.yml create mode 100644 Resources/Textures/Interface/Misc/job_icons.rsi/Zombie.png diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs new file mode 100644 index 0000000000..ea255dde99 --- /dev/null +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -0,0 +1,92 @@ +using Content.Shared.StatusIcon.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; + +namespace Content.Client.StatusIcon; + +public sealed class StatusIconOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entity = default!; + + private readonly SpriteSystem _sprite; + private readonly TransformSystem _transform; + private readonly StatusIconSystem _statusIcon; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + + internal StatusIconOverlay() + { + IoCManager.InjectDependencies(this); + + _sprite = _entity.System(); + _transform = _entity.System(); + _statusIcon = _entity.System(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var handle = args.WorldHandle; + + var eyeRot = args.Viewport.Eye?.Rotation ?? default; + + var xformQuery = _entity.GetEntityQuery(); + var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1)); + var rotationMatrix = Matrix3.CreateRotation(-eyeRot); + + var query = _entity.AllEntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform)) + { + if (xform.MapID != args.MapId) + continue; + + var icons = _statusIcon.GetStatusIcons(uid); + if (icons.Count == 0) + continue; + + var bounds = comp.Bounds ?? sprite.Bounds; + + var worldPos = _transform.GetWorldPosition(xform, xformQuery); + var worldMatrix = Matrix3.CreateTranslation(worldPos); + Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); + Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + handle.SetTransform(matty); + + var count = 0; + var accOffsetL = 0; + var accOffsetR = 0; + icons.Sort(); + + foreach (var proto in icons) + { + var texture = _sprite.Frame0(proto.Icon); + + float yOffset; + float xOffset; + + // the icons are ordered left to right, top to bottom. + // extra icons that don't fit are just cut off. + if (count % 2 == 0) + { + if (accOffsetL + texture.Height > sprite.Bounds.Height * EyeManager.PixelsPerMeter) + break; + accOffsetL += texture.Height; + yOffset = (bounds.Height + sprite.Offset.Y) / 2f - (float) accOffsetL / EyeManager.PixelsPerMeter; + xOffset = -(bounds.Width + sprite.Offset.X) / 2f; + } + else + { + if (accOffsetR + texture.Height > sprite.Bounds.Height * EyeManager.PixelsPerMeter) + break; + accOffsetR += texture.Height; + yOffset = (bounds.Height + sprite.Offset.Y) / 2f - (float) accOffsetR / EyeManager.PixelsPerMeter; + xOffset = (bounds.Width + sprite.Offset.X) / 2f - (float) texture.Width / EyeManager.PixelsPerMeter; + } + count++; + + var position = new Vector2(xOffset, yOffset); + handle.DrawTexture(texture, position); + } + } + } +} diff --git a/Content.Client/StatusIcon/StatusIconSystem.cs b/Content.Client/StatusIcon/StatusIconSystem.cs new file mode 100644 index 0000000000..c5ca10735a --- /dev/null +++ b/Content.Client/StatusIcon/StatusIconSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.CCVar; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Client.Graphics; +using Robust.Shared.Configuration; + +namespace Content.Client.StatusIcon; + +/// +/// This handles rendering gathering and rendering icons on entities. +/// +public sealed class StatusIconSystem : SharedStatusIconSystem +{ + [Dependency] private readonly IConfigurationManager _configuration = default!; + [Dependency] private readonly IOverlayManager _overlay = default!; + + private bool _globalEnabled; + private bool _localEnabled; + + /// + public override void Initialize() + { + _configuration.OnValueChanged(CCVars.LocalStatusIconsEnabled, OnLocalStatusIconChanged, true); + _configuration.OnValueChanged(CCVars.GlobalStatusIconsEnabled, OnGlobalStatusIconChanged, true); + } + + public override void Shutdown() + { + base.Shutdown(); + + _configuration.UnsubValueChanged(CCVars.LocalStatusIconsEnabled, OnLocalStatusIconChanged); + _configuration.UnsubValueChanged(CCVars.GlobalStatusIconsEnabled, OnGlobalStatusIconChanged); + } + + private void OnLocalStatusIconChanged(bool obj) + { + _localEnabled = obj; + UpdateOverlayVisible(); + } + + private void OnGlobalStatusIconChanged(bool obj) + { + _globalEnabled = obj; + UpdateOverlayVisible(); + } + + private void UpdateOverlayVisible() + { + if (_overlay.RemoveOverlay()) + return; + + if (_globalEnabled && _localEnabled) + _overlay.AddOverlay(new StatusIconOverlay()); + } + + public List GetStatusIcons(EntityUid uid) + { + if (!Exists(uid) || Terminating(uid)) + return new(); + + var ev = new GetStatusIconsEvent(new()); + RaiseLocalEvent(uid, ref ev); + return ev.StatusIcons; + } +} + diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 79d4b0a649..f44513e688 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -273,6 +273,18 @@ namespace Content.Shared.CCVar public static readonly CVarDef GameTableBonk = CVarDef.Create("game.table_bonk", false, CVar.REPLICATED); + /// + /// Whether or not status icons are rendered for everyone. + /// + public static readonly CVarDef GlobalStatusIconsEnabled = + CVarDef.Create("game.global_status_icons_enabled", true, CVar.SERVER | CVar.REPLICATED); + + /// + /// Whether or not status icons are rendered on this specific client. + /// + public static readonly CVarDef LocalStatusIconsEnabled = + CVarDef.Create("game.local_status_icons_enabled", true, CVar.CLIENTONLY); + #if EXCEPTION_TOLERANCE /// /// Amount of times round start must fail before the server is shut down. diff --git a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs new file mode 100644 index 0000000000..c18cd7290f --- /dev/null +++ b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.StatusIcon.Components; + +/// +/// This is used for noting if an entity is able to +/// have StatusIcons displayed on them and inherent icons. (debug purposes) +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStatusIconSystem))] +public sealed partial class StatusIconComponent : Component +{ + /// + /// Optional bounds for where the icons are laid out. + /// If null, the sprite bounds will be used. + /// + [AutoNetworkedField] + [DataField("bounds"), ViewVariables(VVAccess.ReadWrite)] + public Box2? Bounds; +} + +/// +/// Event raised directed on an entity CLIENT-SIDE ONLY +/// in order to get what status icons an entity has. +/// +/// +[ByRefEvent] +public record struct GetStatusIconsEvent(List StatusIcons); diff --git a/Content.Shared/StatusIcon/SharedStatusIconSystem.cs b/Content.Shared/StatusIcon/SharedStatusIconSystem.cs new file mode 100644 index 0000000000..a4b22e595e --- /dev/null +++ b/Content.Shared/StatusIcon/SharedStatusIconSystem.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.StatusIcon; + +public abstract class SharedStatusIconSystem : EntitySystem +{ + // If you are trying to add logic for status icons here, you're probably in the wrong place. + // Status icons are gathered and rendered entirely clientside. + // If you wish to use data to render icons, you should replicate that data to the client + // and subscribe to GetStatusIconsEvent in order to add the relevant icon to a given entity. +} diff --git a/Content.Shared/StatusIcon/StatusIconPrototype.cs b/Content.Shared/StatusIcon/StatusIconPrototype.cs new file mode 100644 index 0000000000..582c6cacf4 --- /dev/null +++ b/Content.Shared/StatusIcon/StatusIconPrototype.cs @@ -0,0 +1,40 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.StatusIcon; + +/// +/// A data structure that holds relevant +/// information for status icons. +/// +[Virtual, DataDefinition] +public class StatusIconData : IComparable +{ + /// + /// The icon that's displayed on the entity. + /// + [DataField("icon", required: true)] + public SpriteSpecifier Icon = default!; + + /// + /// A priority for the order in which the icons will be displayed. + /// + [DataField("priority")] + public int Priority = 10; + + public int CompareTo(StatusIconData? other) + { + return Priority.CompareTo(other?.Priority ?? int.MaxValue); + } +} + +/// +/// but in new convenient prototype form! +/// +[Prototype("statusIcon")] +public sealed class StatusIconPrototype : StatusIconData, IPrototype +{ + /// + [IdDataField] + public string ID { get; } = default!; +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index d62b6960b2..2c64fb7a31 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -107,6 +107,7 @@ - type: HeatResistance - type: CombatMode - type: Internals + - type: StatusIcon - type: StatusEffects allowed: - SlowedDown diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index e55c7ba5eb..0e47f883a6 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -67,6 +67,7 @@ Bloodloss: -1 - type: Stamina + - type: StatusIcon - type: StatusEffects allowed: - Stun diff --git a/Resources/Prototypes/StatusIcon/antag.yml b/Resources/Prototypes/StatusIcon/antag.yml new file mode 100644 index 0000000000..a4690caf06 --- /dev/null +++ b/Resources/Prototypes/StatusIcon/antag.yml @@ -0,0 +1,6 @@ +- type: statusIcon + id: ZombieFaction + priority: 11 + icon: + sprite: Interface/Misc/job_icons.rsi + state: Zombie diff --git a/Resources/Prototypes/StatusIcon/debug.yml b/Resources/Prototypes/StatusIcon/debug.yml new file mode 100644 index 0000000000..d230fa852c --- /dev/null +++ b/Resources/Prototypes/StatusIcon/debug.yml @@ -0,0 +1,19 @@ +- type: statusIcon + id: DebugStatus + icon: + sprite: Interface/Misc/research_disciplines.rsi + state: civilianservices + +- type: statusIcon + id: DebugStatus2 + priority: 1 + icon: + sprite: Interface/Misc/research_disciplines.rsi + state: arsenal + +- type: statusIcon + id: DebugStatus3 + priority: 5 + icon: + sprite: Interface/Misc/research_disciplines.rsi + state: experimental diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/Zombie.png b/Resources/Textures/Interface/Misc/job_icons.rsi/Zombie.png new file mode 100644 index 0000000000000000000000000000000000000000..52629aa342a62336b5e0bcb6adb8491aa1065be7 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|B0XIkLnNjq zrz9jKbnqGeIN!jk6IHFPMNTHLe*5eNtn1tLT@sxlc|5XbFR-tDnm{r-UW| DvvoBK literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index 0ee395cb37..a72757362a 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -142,6 +142,9 @@ { "name": "SecurityCadet" }, + { + "name": "Zombie" + }, { "name": "Zookeeper" }