diff --git a/Content.Client/Antag/AntagStatusIconSystem.cs b/Content.Client/Antag/AntagStatusIconSystem.cs
index bf3955b49a..5d87837893 100644
--- a/Content.Client/Antag/AntagStatusIconSystem.cs
+++ b/Content.Client/Antag/AntagStatusIconSystem.cs
@@ -1,6 +1,8 @@
-using Content.Shared.Ghost;
+using Content.Shared.Antag;
+using Content.Shared.Revolutionary.Components;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
+using Content.Shared.Zombies;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
@@ -9,24 +11,44 @@ namespace Content.Client.Antag;
///
/// Used for assigning specified icons for antags.
///
-public abstract class AntagStatusIconSystem : SharedStatusIconSystem
- where T : IComponent
+public sealed class AntagStatusIconSystem : SharedStatusIconSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(GetRevIcon);
+ SubscribeLocalEvent(GetIcon);
+ SubscribeLocalEvent(GetIcon);
+ }
///
- /// Will check if the local player has the same component as the one who called it and give the status icon.
+ /// Adds a Status Icon on an entity if the player is supposed to see it.
///
- /// The status icon that your antag uses
- /// The GetStatusIcon event.
- protected virtual void GetStatusIcon(string antagStatusIcon, ref GetStatusIconsEvent args)
+ private void GetIcon(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent
{
- var ent = _player.LocalPlayer?.ControlledEntity;
+ var ent = _player.LocalSession?.AttachedEntity;
+
+ var canEv = new CanDisplayStatusIconsEvent(ent);
+ RaiseLocalEvent(uid, ref canEv);
+
+ if (!canEv.Cancelled)
+ ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon));
+ }
- if (!HasComp(ent) && !HasComp(ent))
+
+ ///
+ /// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal
+ /// with a special case where if someone is a head rev we only want to display the headrev icon.
+ ///
+ private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev)
+ {
+ if (HasComp(uid))
return;
- args.StatusIcons.Add(_prototype.Index(antagStatusIcon));
+ GetIcon(uid, comp, ref ev);
+
}
}
diff --git a/Content.Client/Revolutionary/RevolutionarySystem.cs b/Content.Client/Revolutionary/RevolutionarySystem.cs
index 0818b14bc0..682c73f93e 100644
--- a/Content.Client/Revolutionary/RevolutionarySystem.cs
+++ b/Content.Client/Revolutionary/RevolutionarySystem.cs
@@ -1,5 +1,6 @@
+using Content.Shared.Antag;
using Content.Shared.Revolutionary.Components;
-using Content.Client.Antag;
+using Content.Shared.Ghost;
using Content.Shared.StatusIcon.Components;
namespace Content.Client.Revolutionary;
@@ -7,29 +8,37 @@ namespace Content.Client.Revolutionary;
///
/// Used for the client to get status icons from other revs.
///
-public sealed class RevolutionarySystem : AntagStatusIconSystem
+public sealed class RevolutionarySystem : EntitySystem
{
+
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(GetRevIcon);
- SubscribeLocalEvent(GetHeadRevIcon);
+ SubscribeLocalEvent(OnCanShowRevIcon);
+ SubscribeLocalEvent(OnCanShowRevIcon);
}
///
- /// Checks if the person who triggers the GetStatusIcon event is also a Rev or a HeadRev.
+ /// Determine whether a client should display the rev icon.
///
- private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent args)
+ private void OnCanShowRevIcon(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent
{
- if (!HasComp(uid))
- {
- GetStatusIcon(comp.RevStatusIcon, ref args);
- }
+ args.Cancelled = !CanDisplayIcon(args.User, comp.IconVisibleToGhost);
}
- private void GetHeadRevIcon(EntityUid uid, HeadRevolutionaryComponent comp, ref GetStatusIconsEvent args)
+ ///
+ /// The criteria that determine whether a client should see Rev/Head rev icons.
+ ///
+ private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost)
{
- GetStatusIcon(comp.HeadRevStatusIcon, ref args);
+ if (HasComp(uid) || HasComp(uid))
+ return true;
+
+ if (visibleToGhost && HasComp(uid))
+ return true;
+
+ return HasComp(uid);
}
+
}
diff --git a/Content.Client/Zombies/ZombieSystem.cs b/Content.Client/Zombies/ZombieSystem.cs
index 6d0355f6f8..bd89e978c7 100644
--- a/Content.Client/Zombies/ZombieSystem.cs
+++ b/Content.Client/Zombies/ZombieSystem.cs
@@ -1,5 +1,5 @@
using System.Linq;
-using Content.Client.Antag;
+using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
@@ -7,15 +7,14 @@
namespace Content.Client.Zombies;
-public sealed class ZombieSystem : AntagStatusIconSystem
+public sealed class ZombieSystem : EntitySystem
{
-
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnStartup);
- SubscribeLocalEvent(OnGetStatusIcon);
+ SubscribeLocalEvent(OnCanDisplayStatusIcons);
}
private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args)
@@ -32,8 +31,17 @@ private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartu
}
}
- private void OnGetStatusIcon(EntityUid uid, ZombieComponent component, ref GetStatusIconsEvent args)
+ ///
+ /// Determines whether a player should be able to see the StatusIcon for zombies.
+ ///
+ private void OnCanDisplayStatusIcons(EntityUid uid, ZombieComponent component, ref CanDisplayStatusIconsEvent args)
{
- GetStatusIcon(component.ZombieStatusIcon, ref args);
+ if (HasComp(args.User) || HasComp(args.User))
+ return;
+
+ if (component.IconVisibleToGhost && HasComp(args.User))
+ return;
+
+ args.Cancelled = true;
}
}
diff --git a/Content.Shared/Antag/IAntagStatusIconComponent.cs b/Content.Shared/Antag/IAntagStatusIconComponent.cs
new file mode 100644
index 0000000000..981937c916
--- /dev/null
+++ b/Content.Shared/Antag/IAntagStatusIconComponent.cs
@@ -0,0 +1,12 @@
+using Content.Shared.StatusIcon;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Antag;
+
+public interface IAntagStatusIconComponent
+{
+ public ProtoId StatusIcon { get; set; }
+
+ public bool IconVisibleToGhost { get; set; }
+}
+
diff --git a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs
index 48d7c23097..d2c8374fef 100644
--- a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs
+++ b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Antag;
using Robust.Shared.GameStates;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
@@ -8,17 +9,22 @@ namespace Content.Shared.Revolutionary.Components;
/// Component used for marking a Head Rev for conversion and winning/losing.
///
[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
-public sealed partial class HeadRevolutionaryComponent : Component
+public sealed partial class HeadRevolutionaryComponent : Component, IAntagStatusIconComponent
{
///
/// The status icon corresponding to the head revolutionary.
///
[DataField, ViewVariables(VVAccess.ReadWrite)]
- public ProtoId HeadRevStatusIcon = "HeadRevolutionaryFaction";
+ public ProtoId StatusIcon { get; set; } = "HeadRevolutionaryFaction";
///
/// How long the stun will last after the user is converted.
///
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan StunTime = TimeSpan.FromSeconds(3);
+
+ public override bool SessionSpecific => true;
+
+ [DataField]
+ public bool IconVisibleToGhost { get; set; } = true;
}
diff --git a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs
index e55c87786b..587c148dd7 100644
--- a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs
+++ b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Antag;
using Robust.Shared.GameStates;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
@@ -8,11 +9,16 @@ namespace Content.Shared.Revolutionary.Components;
/// Used for marking regular revs as well as storing icon prototypes so you can see fellow revs.
///
[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
-public sealed partial class RevolutionaryComponent : Component
+public sealed partial class RevolutionaryComponent : Component, IAntagStatusIconComponent
{
///
/// The status icon prototype displayed for revolutionaries
///
[DataField, ViewVariables(VVAccess.ReadWrite)]
- public ProtoId RevStatusIcon = "RevolutionaryFaction";
+ public ProtoId StatusIcon { get; set; } = "RevolutionaryFaction";
+
+ public override bool SessionSpecific => true;
+
+ [DataField]
+ public bool IconVisibleToGhost { get; set; } = true;
}
diff --git a/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs b/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs
new file mode 100644
index 0000000000..11e20db3f8
--- /dev/null
+++ b/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Revolutionary.Components;
+
+///
+/// Determines whether Someone can see rev/headrev icons on revs.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowRevIconsComponent: Component
+{
+}
diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs
index 1399b116e0..ddaf73fcc9 100644
--- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs
+++ b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs
@@ -1,8 +1,11 @@
+using Content.Shared.Ghost;
using Content.Shared.IdentityManagement;
using Content.Shared.Mindshield.Components;
using Content.Shared.Popups;
using Content.Shared.Revolutionary.Components;
using Content.Shared.Stunnable;
+using Robust.Shared.GameStates;
+using Robust.Shared.Player;
namespace Content.Shared.Revolutionary;
@@ -14,7 +17,13 @@ public sealed class SharedRevolutionarySystem : EntitySystem
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent(MindShieldImplanted);
+ SubscribeLocalEvent(OnRevCompGetStateAttempt);
+ SubscribeLocalEvent(OnRevCompGetStateAttempt);
+ SubscribeLocalEvent(DirtyRevComps);
+ SubscribeLocalEvent(DirtyRevComps);
+ SubscribeLocalEvent(DirtyRevComps);
}
///
@@ -37,4 +46,64 @@ private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, MapIni
_popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid);
}
}
+
+ ///
+ /// Determines if a HeadRev component should be sent to the client.
+ ///
+ private void OnRevCompGetStateAttempt(EntityUid uid, HeadRevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args)
+ {
+ args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost);
+ }
+
+ ///
+ /// Determines if a Rev component should be sent to the client.
+ ///
+ private void OnRevCompGetStateAttempt(EntityUid uid, RevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args)
+ {
+ args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost);
+ }
+
+ ///
+ /// The criteria that determine whether a Rev/HeadRev component should be sent to a client.
+ ///
+ /// The Player the component will be sent to.
+ /// Whether the component permits the icon to be visible to observers.
+ ///
+ private bool CanGetState(ICommonSession? player, bool visibleToGhosts)
+ {
+ //Apparently this can be null in replays so I am just returning true.
+ if (player is null)
+ return true;
+
+ var uid = player.AttachedEntity;
+
+ if (HasComp(uid) || HasComp(uid))
+ return true;
+
+ if (visibleToGhosts && HasComp(uid))
+ return true;
+
+ return HasComp(uid);
+ }
+ ///
+ /// Dirties all the Rev components so they are sent to clients.
+ ///
+ /// We need to do this because if a rev component was not earlier sent to a client and for example the client
+ /// becomes a rev then we need to send all the components to it. To my knowledge there is no way to do this on a
+ /// per client basis so we are just dirtying all the components.
+ ///
+ private void DirtyRevComps(EntityUid someUid, T someComp, ComponentStartup ev)
+ {
+ var revComps = AllEntityQuery();
+ while (revComps.MoveNext(out var uid, out var comp))
+ {
+ Dirty(uid, comp);
+ }
+
+ var headRevComps = AllEntityQuery();
+ while (headRevComps.MoveNext(out var uid, out var comp))
+ {
+ Dirty(uid, comp);
+ }
+ }
}
diff --git a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs
index 9efe9731c8..385f9760c6 100644
--- a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs
+++ b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs
@@ -25,3 +25,15 @@ public sealed partial class StatusIconComponent : Component
///
[ByRefEvent]
public record struct GetStatusIconsEvent(List StatusIcons, bool InContainer);
+
+///
+/// Event raised on the Client-side to determine whether to display a status icon on an entity.
+///
+/// The player that will see the icons
+[ByRefEvent]
+public record struct CanDisplayStatusIconsEvent(EntityUid? User = null)
+{
+ public EntityUid? User = User;
+
+ public bool Cancelled = false;
+}
diff --git a/Content.Shared/Zombies/ShowZombieIconsComponent.cs b/Content.Shared/Zombies/ShowZombieIconsComponent.cs
new file mode 100644
index 0000000000..a2bc85c074
--- /dev/null
+++ b/Content.Shared/Zombies/ShowZombieIconsComponent.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Zombies;
+
+///
+/// Makes it so an entity can view ZombieAntagIcons.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowZombieIconsComponent: Component
+{
+
+}
diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs
index bb1f6bec5f..5ae441b132 100644
--- a/Content.Shared/Zombies/ZombieComponent.cs
+++ b/Content.Shared/Zombies/ZombieComponent.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Antag;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
@@ -13,7 +14,7 @@
namespace Content.Shared.Zombies;
[RegisterComponent, NetworkedComponent]
-public sealed partial class ZombieComponent : Component
+public sealed partial class ZombieComponent : Component, IAntagStatusIconComponent
{
///
/// The baseline infection chance you have if you are completely nude
@@ -93,8 +94,11 @@ public sealed partial class ZombieComponent : Component
[DataField("nextTick", customTypeSerializer:typeof(TimeOffsetSerializer))]
public TimeSpan NextTick;
- [DataField("zombieStatusIcon", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ZombieStatusIcon = "ZombieFaction";
+ [DataField("zombieStatusIcon")]
+ public ProtoId StatusIcon { get; set; } = "ZombieFaction";
+
+ [DataField]
+ public bool IconVisibleToGhost { get; set; } = true;
///
/// Healing each second
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index 1583801e98..364d26dddb 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -89,6 +89,8 @@
- type: Stripping
- type: SolutionScanner
- type: IgnoreUIRange
+ - type: ShowRevIcons
+ - type: ShowZombieIcons
- type: Inventory
templateId: aghost
- type: InventorySlots