using System.Numerics;
using Content.Shared.Administration.Components;
using Robust.Client.GameObjects;
-using Robust.Shared.Utility;
+using Robust.Client.Player;
namespace Content.Client.Administration.Systems;
public sealed class KillSignSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
public override void Initialize()
{
SubscribeLocalEvent<KillSignComponent, ComponentStartup>(KillSignAdded);
SubscribeLocalEvent<KillSignComponent, ComponentShutdown>(KillSignRemoved);
+ SubscribeLocalEvent<KillSignComponent, AfterAutoHandleStateEvent>(AfterAutoHandleState);
}
- private void KillSignRemoved(EntityUid uid, KillSignComponent component, ComponentShutdown args)
+ private void KillSignRemoved(Entity<KillSignComponent> ent, ref ComponentShutdown args)
{
- if (!TryComp<SpriteComponent>(uid, out var sprite))
- return;
+ RemoveKillsign(ent);
+ }
- if (!_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var layer, false))
- return;
+ private void KillSignAdded(Entity<KillSignComponent> ent, ref ComponentStartup args)
+ {
+ AddKillsign(ent);
+ }
- _sprite.RemoveLayer((uid, sprite), layer);
+ private void AfterAutoHandleState(Entity<KillSignComponent> ent, ref AfterAutoHandleStateEvent args)
+ {
+ // After receiving a new state for the component, we remove the old killsign and build a new one.
+ // This is so changes to the sprite can be displayed live and allowing them to be edited via ViewVariables.
+ // This could just update an existing sprite, but this is both easier and runs rarely anyway.
+ RemoveKillsign(ent);
+ AddKillsign(ent);
}
- private void KillSignAdded(EntityUid uid, KillSignComponent component, ComponentStartup args)
+ private void AddKillsign(Entity<KillSignComponent> ent)
{
- if (!TryComp<SpriteComponent>(uid, out var sprite))
+ // If we hide from owner and we ARE the owner, don't add a killsign.
+ // This could use session specific networking to FULLY hide it, but I am too lazy right now.
+ if (ent.Comp.HideFromOwner && _player.LocalEntity == ent)
+ return;
+
+ if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
- if (_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var _, false))
+ if (_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var _, false))
return;
- var adj = _sprite.GetLocalBounds((uid, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
+ if (ent.Comp.Sprite == null)
+ return;
- var layer = _sprite.AddLayer((uid, sprite), new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
- _sprite.LayerMapSet((uid, sprite), KillSignKey.Key, layer);
+ var adj = _sprite.GetLocalBounds((ent, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
+
+ var layer = _sprite.AddLayer((ent, sprite), ent.Comp.Sprite);
+ _sprite.LayerMapSet((ent, sprite), KillSignKey.Key, layer);
+ _sprite.LayerSetScale((ent, sprite), layer, ent.Comp.Scale);
+ _sprite.LayerSetOffset((ent, sprite), layer, ent.Comp.DoOffset ? new Vector2(0.0f, adj) : new Vector2(0.0f, 0.0f));
+
+ if (ent.Comp.ForceUnshaded)
+ sprite.LayerSetShader(layer, "unshaded");
+ }
+
+ private void RemoveKillsign(Entity<KillSignComponent> ent)
+ {
+ if (!TryComp<SpriteComponent>(ent, out var sprite))
+ return;
+
+ if (!_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var layer, false))
+ return;
- _sprite.LayerSetOffset((uid, sprite), layer, new Vector2(0.0f, adj));
- sprite.LayerSetShader(layer, "unshaded");
+ _sprite.RemoveLayer((ent, sprite), layer);
}
private enum KillSignKey
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/killsign.rsi"), "icon"),
Act = () =>
{
- EnsureComp<KillSignComponent>(args.Target);
+ EnsureComp<KillSignComponent>(args.Target, out var comp);
+ comp.HideFromOwner = false; // We set it to false anyway, in case the hidden smite was used beforehand.
+ Dirty(args.Target, comp);
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", killSignName, Loc.GetString("admin-smite-kill-sign-description"))
};
args.Verbs.Add(killSign);
+ var hiddenKillSignName = Loc.GetString("admin-smite-kill-sign-hidden-name").ToLowerInvariant();
+ Verb hiddenKillSign = new()
+ {
+ Text = hiddenKillSignName,
+ Category = VerbCategory.Smite,
+ Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/killsign.rsi"), "icon-hidden"),
+ Act = () =>
+ {
+ EnsureComp<KillSignComponent>(args.Target, out var comp);
+ comp.HideFromOwner = true;
+ Dirty(args.Target, comp);
+ },
+ Impact = LogImpact.Extreme,
+ Message = string.Join(": ", hiddenKillSignName, Loc.GetString("admin-smite-kill-sign-hidden-description"))
+ };
+ args.Verbs.Add(hiddenKillSign);
+
var cluwneName = Loc.GetString("admin-smite-cluwne-name").ToLowerInvariant();
Verb cluwne = new()
{
-using Robust.Shared.GameStates;
+using System.Numerics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
namespace Content.Shared.Administration.Components;
-[RegisterComponent, NetworkedComponent]
-public sealed partial class KillSignComponent : Component;
+/// <summary>
+/// Displays a sprite above an entity.
+/// By default a huge sign saying "KILL".
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
+public sealed partial class KillSignComponent : Component
+{
+ /// <summary>
+ /// The sprite show above the entity.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public SpriteSpecifier? Sprite = new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "kill");
+
+ /// <summary>
+ /// Whether the granted layer should always be forced to be unshaded.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool ForceUnshaded = true;
+
+ /// <summary>
+ /// Whether the granted layer should be offset to be above the entity.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool DoOffset = true;
+
+ /// <summary>
+ /// Prevents the sign from displaying to the owner of the component, allowing everyone but them to see it.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool HideFromOwner = false;
+
+ /// <summary>
+ /// The scale of the sprite.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Vector2 Scale = Vector2.One;
+}
admin-smite-ghostkick-name = Ghost Kick
admin-smite-nyanify-name = Cat Ears
admin-smite-kill-sign-name = Kill Sign
+admin-smite-kill-sign-hidden-name = Hidden Kill Sign
admin-smite-omni-accent-name = Omni-Accent
admin-smite-crawler-name = Crawler
admin-smite-homing-rod-name = Homing Rod
admin-smite-ghostkick-description = Silently kicks the user, dropping their connection.
admin-smite-nyanify-description = Forcibly add cat ears, there is no escape.
admin-smite-kill-sign-description = Marks a player for death by their fellows.
+admin-smite-kill-sign-hidden-description = Marks a player for death by their fellows. Hidden from the targeted player.
admin-smite-cluwne-description = Cluwnes them. The suit cannot be removed and the station's crew may murder them freely.
admin-smite-anger-pointing-arrows-description = Angers the pointing arrows, causing them to assault this entity explosively.
admin-smite-dust-description = Reduces the target to a small pile of ash.