Status Icons (#17529)
This commit is contained in:
92
Content.Client/StatusIcon/StatusIconOverlay.cs
Normal file
92
Content.Client/StatusIcon/StatusIconOverlay.cs
Normal file
@@ -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<SpriteSystem>();
|
||||
_transform = _entity.System<TransformSystem>();
|
||||
_statusIcon = _entity.System<StatusIconSystem>();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
|
||||
var eyeRot = args.Viewport.Eye?.Rotation ?? default;
|
||||
|
||||
var xformQuery = _entity.GetEntityQuery<TransformComponent>();
|
||||
var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1));
|
||||
var rotationMatrix = Matrix3.CreateRotation(-eyeRot);
|
||||
|
||||
var query = _entity.AllEntityQueryEnumerator<StatusIconComponent, SpriteComponent, TransformComponent>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Content.Client/StatusIcon/StatusIconSystem.cs
Normal file
66
Content.Client/StatusIcon/StatusIconSystem.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles rendering gathering and rendering icons on entities.
|
||||
/// </summary>
|
||||
public sealed class StatusIconSystem : SharedStatusIconSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
private bool _globalEnabled;
|
||||
private bool _localEnabled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<StatusIconOverlay>())
|
||||
return;
|
||||
|
||||
if (_globalEnabled && _localEnabled)
|
||||
_overlay.AddOverlay(new StatusIconOverlay());
|
||||
}
|
||||
|
||||
public List<StatusIconData> GetStatusIcons(EntityUid uid)
|
||||
{
|
||||
if (!Exists(uid) || Terminating(uid))
|
||||
return new();
|
||||
|
||||
var ev = new GetStatusIconsEvent(new());
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
return ev.StatusIcons;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +273,18 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> GameTableBonk =
|
||||
CVarDef.Create("game.table_bonk", false, CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not status icons are rendered for everyone.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GlobalStatusIconsEnabled =
|
||||
CVarDef.Create("game.global_status_icons_enabled", true, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not status icons are rendered on this specific client.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> LocalStatusIconsEnabled =
|
||||
CVarDef.Create("game.local_status_icons_enabled", true, CVar.CLIENTONLY);
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
/// <summary>
|
||||
/// Amount of times round start must fail before the server is shut down.
|
||||
|
||||
27
Content.Shared/StatusIcon/Components/StatusIconComponent.cs
Normal file
27
Content.Shared/StatusIcon/Components/StatusIconComponent.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.StatusIcon.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for noting if an entity is able to
|
||||
/// have StatusIcons displayed on them and inherent icons. (debug purposes)
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStatusIconSystem))]
|
||||
public sealed partial class StatusIconComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional bounds for where the icons are laid out.
|
||||
/// If null, the sprite bounds will be used.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[DataField("bounds"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public Box2? Bounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed on an entity CLIENT-SIDE ONLY
|
||||
/// in order to get what status icons an entity has.
|
||||
/// </summary>
|
||||
/// <param name="StatusIcons"></param>
|
||||
[ByRefEvent]
|
||||
public record struct GetStatusIconsEvent(List<StatusIconData> StatusIcons);
|
||||
9
Content.Shared/StatusIcon/SharedStatusIconSystem.cs
Normal file
9
Content.Shared/StatusIcon/SharedStatusIconSystem.cs
Normal file
@@ -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.
|
||||
}
|
||||
40
Content.Shared/StatusIcon/StatusIconPrototype.cs
Normal file
40
Content.Shared/StatusIcon/StatusIconPrototype.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.StatusIcon;
|
||||
|
||||
/// <summary>
|
||||
/// A data structure that holds relevant
|
||||
/// information for status icons.
|
||||
/// </summary>
|
||||
[Virtual, DataDefinition]
|
||||
public class StatusIconData : IComparable<StatusIconData>
|
||||
{
|
||||
/// <summary>
|
||||
/// The icon that's displayed on the entity.
|
||||
/// </summary>
|
||||
[DataField("icon", required: true)]
|
||||
public SpriteSpecifier Icon = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A priority for the order in which the icons will be displayed.
|
||||
/// </summary>
|
||||
[DataField("priority")]
|
||||
public int Priority = 10;
|
||||
|
||||
public int CompareTo(StatusIconData? other)
|
||||
{
|
||||
return Priority.CompareTo(other?.Priority ?? int.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="StatusIconData"/> but in new convenient prototype form!
|
||||
/// </summary>
|
||||
[Prototype("statusIcon")]
|
||||
public sealed class StatusIconPrototype : StatusIconData, IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
}
|
||||
@@ -107,6 +107,7 @@
|
||||
- type: HeatResistance
|
||||
- type: CombatMode
|
||||
- type: Internals
|
||||
- type: StatusIcon
|
||||
- type: StatusEffects
|
||||
allowed:
|
||||
- SlowedDown
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
Bloodloss:
|
||||
-1
|
||||
- type: Stamina
|
||||
- type: StatusIcon
|
||||
- type: StatusEffects
|
||||
allowed:
|
||||
- Stun
|
||||
|
||||
6
Resources/Prototypes/StatusIcon/antag.yml
Normal file
6
Resources/Prototypes/StatusIcon/antag.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
- type: statusIcon
|
||||
id: ZombieFaction
|
||||
priority: 11
|
||||
icon:
|
||||
sprite: Interface/Misc/job_icons.rsi
|
||||
state: Zombie
|
||||
19
Resources/Prototypes/StatusIcon/debug.yml
Normal file
19
Resources/Prototypes/StatusIcon/debug.yml
Normal file
@@ -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
|
||||
BIN
Resources/Textures/Interface/Misc/job_icons.rsi/Zombie.png
Normal file
BIN
Resources/Textures/Interface/Misc/job_icons.rsi/Zombie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 B |
@@ -142,6 +142,9 @@
|
||||
{
|
||||
"name": "SecurityCadet"
|
||||
},
|
||||
{
|
||||
"name": "Zombie"
|
||||
},
|
||||
{
|
||||
"name": "Zookeeper"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user