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 =
|
public static readonly CVarDef<bool> GameTableBonk =
|
||||||
CVarDef.Create("game.table_bonk", false, CVar.REPLICATED);
|
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
|
#if EXCEPTION_TOLERANCE
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Amount of times round start must fail before the server is shut down.
|
/// 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: HeatResistance
|
||||||
- type: CombatMode
|
- type: CombatMode
|
||||||
- type: Internals
|
- type: Internals
|
||||||
|
- type: StatusIcon
|
||||||
- type: StatusEffects
|
- type: StatusEffects
|
||||||
allowed:
|
allowed:
|
||||||
- SlowedDown
|
- SlowedDown
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
Bloodloss:
|
Bloodloss:
|
||||||
-1
|
-1
|
||||||
- type: Stamina
|
- type: Stamina
|
||||||
|
- type: StatusIcon
|
||||||
- type: StatusEffects
|
- type: StatusEffects
|
||||||
allowed:
|
allowed:
|
||||||
- Stun
|
- 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": "SecurityCadet"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Zombie"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Zookeeper"
|
"name": "Zookeeper"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user