Skip to content

Commit 8b19b7f

Browse files
Rev Components are no longer leaked + Rev and Zombie icon visibility to ghosts is now controlled by a cvar (space-wizards#22194)
* Initial work on having the Rev icons not be visible to ghosts depending on a Cvar and a component. This commit just makes it so that the revcomponent and headrev component are not shared with clients that shouldn't know about them. This is due to the concern that clients having access to those components, even if no image was displayed could allow modified clients to have meta knowledge of revs. Currently this has the issue that if a player later for example becomes a rev, none of the existing rev components get networked to them. I am not sure there is currently an effecient solution to this. This is probably in an issue for a lot more stuff. I might just make it so all the logic just moves to the client on whether to put the icon again. Also this commit adds the ShowRevIconsComponent to allow anyone with it to just view rev icons. * Rev components now get communicated to clients that didn't have them previously and the AntagIconSystem is now properly checking whether to give the icons. We now dirty all the rev/headrev components when someone gets converted or gets the ViewRevIcons component. The AntagIconSystem now checks whether it should draw the icons mostly based on an event, this is still done client side. This is not a full proof solution to make it so clients can't know someone is an antag when they shouldn't because: 1. There are other components that need similar treatment, to my knowledge not to for revs but for other antags. Maybe even the mind component. This could be addressed in future PRs. 2. We cannot ensure that clients forget about these components if the client gets deconverted for example. We can of course have code that does this, but it will necessarily need to be done on the client and if the client is modified then there is no way to ensure this. Of course at that point they should already know who their fellow revs are so this might not be an issue. I now need to do the same thing for zombies in a future commit. A similar system for nukies also needs to be looked at but I will not be doing that in the PR this commit ends up in. * Misc name changes and cleaning up the ZombieSystem Changed some names around and decoupled the ZombieSystem from the AntagStatusIconsystem. Now there is a cvar for ghost visibility for them as well. The Zombie Component was not made SessionSpecific because: 1. Zombies are pretty visible anyways 2. The Component is needed to change the appearance of zombie players. * Misc name changes and cleaning up the ZombieSystem Changed some names around and decoupled the ZombieSystem from the AntagStatusIconsystem. Now there is a cvar for ghost visibility for them as well. The Zombie Component was not made SessionSpecific because: 1. Zombies are pretty visible anyways 2. The Component is needed to change the appearance of zombie players. * Merged 2 if statements into 1 on the Zombiesystem. * Cut down on code duplication in AntagStatusIconSystem Now instead of having a seperate function for each component, there is 1 generic function. Functions for special cases like the Rev/Headrev comp can have a separate function that does the special check and then calls the generic one. This is done through the IAntagStatusIconComponent interface which provides a common interface to get the Icon. * Removed some duplication from the SharedRevolutionarySystem with generics. I have no idea why I didn't think of this sooner. * Addressed Reviews I think I think events get unsubbed automatically but I am probably missing something that I have not understood. Either way this is a requested change. * Replace war crimes with actual fixes for reviews It was not clear to me what the reviews meant * Addressed reviews by removing need for cvars. Whether icons are visible to ghosts is now determined by a bool in IAntagStatusIcon which all antag components with status icons should implement. * Update Content.Shared/Revolutionary/SharedRevolutionarySystem.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
1 parent c989340 commit 8b19b7f

File tree

12 files changed

+208
-35
lines changed

12 files changed

+208
-35
lines changed
Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using Content.Shared.Ghost;
1+
using Content.Shared.Antag;
2+
using Content.Shared.Revolutionary.Components;
23
using Content.Shared.StatusIcon;
34
using Content.Shared.StatusIcon.Components;
5+
using Content.Shared.Zombies;
46
using Robust.Client.Player;
57
using Robust.Shared.Prototypes;
68

@@ -9,24 +11,44 @@ namespace Content.Client.Antag;
911
/// <summary>
1012
/// Used for assigning specified icons for antags.
1113
/// </summary>
12-
public abstract class AntagStatusIconSystem<T> : SharedStatusIconSystem
13-
where T : IComponent
14+
public sealed class AntagStatusIconSystem : SharedStatusIconSystem
1415
{
1516
[Dependency] private readonly IPrototypeManager _prototype = default!;
1617
[Dependency] private readonly IPlayerManager _player = default!;
18+
public override void Initialize()
19+
{
20+
base.Initialize();
21+
22+
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
23+
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(GetIcon);
24+
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetIcon);
25+
}
1726

1827
/// <summary>
19-
/// Will check if the local player has the same component as the one who called it and give the status icon.
28+
/// Adds a Status Icon on an entity if the player is supposed to see it.
2029
/// </summary>
21-
/// <param name="antagStatusIcon">The status icon that your antag uses</param>
22-
/// <param name="args">The GetStatusIcon event.</param>
23-
protected virtual void GetStatusIcon(string antagStatusIcon, ref GetStatusIconsEvent args)
30+
private void GetIcon<T>(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent
2431
{
25-
var ent = _player.LocalPlayer?.ControlledEntity;
32+
var ent = _player.LocalSession?.AttachedEntity;
33+
34+
var canEv = new CanDisplayStatusIconsEvent(ent);
35+
RaiseLocalEvent(uid, ref canEv);
36+
37+
if (!canEv.Cancelled)
38+
ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon));
39+
}
2640

27-
if (!HasComp<T>(ent) && !HasComp<GhostComponent>(ent))
41+
42+
/// <summary>
43+
/// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal
44+
/// with a special case where if someone is a head rev we only want to display the headrev icon.
45+
/// </summary>
46+
private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev)
47+
{
48+
if (HasComp<HeadRevolutionaryComponent>(uid))
2849
return;
2950

30-
args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(antagStatusIcon));
51+
GetIcon(uid, comp, ref ev);
52+
3153
}
3254
}
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,44 @@
1+
using Content.Shared.Antag;
12
using Content.Shared.Revolutionary.Components;
2-
using Content.Client.Antag;
3+
using Content.Shared.Ghost;
34
using Content.Shared.StatusIcon.Components;
45

56
namespace Content.Client.Revolutionary;
67

78
/// <summary>
89
/// Used for the client to get status icons from other revs.
910
/// </summary>
10-
public sealed class RevolutionarySystem : AntagStatusIconSystem<RevolutionaryComponent>
11+
public sealed class RevolutionarySystem : EntitySystem
1112
{
13+
1214
public override void Initialize()
1315
{
1416
base.Initialize();
1517

16-
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
17-
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetHeadRevIcon);
18+
SubscribeLocalEvent<RevolutionaryComponent, CanDisplayStatusIconsEvent>(OnCanShowRevIcon);
19+
SubscribeLocalEvent<HeadRevolutionaryComponent, CanDisplayStatusIconsEvent>(OnCanShowRevIcon);
1820
}
1921

2022
/// <summary>
21-
/// Checks if the person who triggers the GetStatusIcon event is also a Rev or a HeadRev.
23+
/// Determine whether a client should display the rev icon.
2224
/// </summary>
23-
private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent args)
25+
private void OnCanShowRevIcon<T>(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent
2426
{
25-
if (!HasComp<HeadRevolutionaryComponent>(uid))
26-
{
27-
GetStatusIcon(comp.RevStatusIcon, ref args);
28-
}
27+
args.Cancelled = !CanDisplayIcon(args.User, comp.IconVisibleToGhost);
2928
}
3029

31-
private void GetHeadRevIcon(EntityUid uid, HeadRevolutionaryComponent comp, ref GetStatusIconsEvent args)
30+
/// <summary>
31+
/// The criteria that determine whether a client should see Rev/Head rev icons.
32+
/// </summary>
33+
private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost)
3234
{
33-
GetStatusIcon(comp.HeadRevStatusIcon, ref args);
35+
if (HasComp<HeadRevolutionaryComponent>(uid) || HasComp<RevolutionaryComponent>(uid))
36+
return true;
37+
38+
if (visibleToGhost && HasComp<GhostComponent>(uid))
39+
return true;
40+
41+
return HasComp<ShowRevIconsComponent>(uid);
3442
}
43+
3544
}

‎Content.Client/Zombies/ZombieSystem.cs‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
using System.Linq;
2-
using Content.Client.Antag;
2+
using Content.Shared.Ghost;
33
using Content.Shared.Humanoid;
44
using Content.Shared.StatusIcon.Components;
55
using Content.Shared.Zombies;
66
using Robust.Client.GameObjects;
77

88
namespace Content.Client.Zombies;
99

10-
public sealed class ZombieSystem : AntagStatusIconSystem<ZombieComponent>
10+
public sealed class ZombieSystem : EntitySystem
1111
{
12-
1312
public override void Initialize()
1413
{
1514
base.Initialize();
1615

1716
SubscribeLocalEvent<ZombieComponent, ComponentStartup>(OnStartup);
18-
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(OnGetStatusIcon);
17+
SubscribeLocalEvent<ZombieComponent, CanDisplayStatusIconsEvent>(OnCanDisplayStatusIcons);
1918
}
2019

2120
private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args)
@@ -32,8 +31,17 @@ private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartu
3231
}
3332
}
3433

35-
private void OnGetStatusIcon(EntityUid uid, ZombieComponent component, ref GetStatusIconsEvent args)
34+
/// <summary>
35+
/// Determines whether a player should be able to see the StatusIcon for zombies.
36+
/// </summary>
37+
private void OnCanDisplayStatusIcons(EntityUid uid, ZombieComponent component, ref CanDisplayStatusIconsEvent args)
3638
{
37-
GetStatusIcon(component.ZombieStatusIcon, ref args);
39+
if (HasComp<ZombieComponent>(args.User) || HasComp<ShowZombieIconsComponent>(args.User))
40+
return;
41+
42+
if (component.IconVisibleToGhost && HasComp<GhostComponent>(args.User))
43+
return;
44+
45+
args.Cancelled = true;
3846
}
3947
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Content.Shared.StatusIcon;
2+
using Robust.Shared.Prototypes;
3+
4+
namespace Content.Shared.Antag;
5+
6+
public interface IAntagStatusIconComponent
7+
{
8+
public ProtoId<StatusIconPrototype> StatusIcon { get; set; }
9+
10+
public bool IconVisibleToGhost { get; set; }
11+
}
12+

‎Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Content.Shared.Antag;
12
using Robust.Shared.GameStates;
23
using Content.Shared.StatusIcon;
34
using Robust.Shared.Prototypes;
@@ -8,17 +9,22 @@ namespace Content.Shared.Revolutionary.Components;
89
/// Component used for marking a Head Rev for conversion and winning/losing.
910
/// </summary>
1011
[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
11-
public sealed partial class HeadRevolutionaryComponent : Component
12+
public sealed partial class HeadRevolutionaryComponent : Component, IAntagStatusIconComponent
1213
{
1314
/// <summary>
1415
/// The status icon corresponding to the head revolutionary.
1516
/// </summary>
1617
[DataField, ViewVariables(VVAccess.ReadWrite)]
17-
public ProtoId<StatusIconPrototype> HeadRevStatusIcon = "HeadRevolutionaryFaction";
18+
public ProtoId<StatusIconPrototype> StatusIcon { get; set; } = "HeadRevolutionaryFaction";
1819

1920
/// <summary>
2021
/// How long the stun will last after the user is converted.
2122
/// </summary>
2223
[DataField, ViewVariables(VVAccess.ReadWrite)]
2324
public TimeSpan StunTime = TimeSpan.FromSeconds(3);
25+
26+
public override bool SessionSpecific => true;
27+
28+
[DataField]
29+
public bool IconVisibleToGhost { get; set; } = true;
2430
}

‎Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Content.Shared.Antag;
12
using Robust.Shared.GameStates;
23
using Content.Shared.StatusIcon;
34
using Robust.Shared.Prototypes;
@@ -8,11 +9,16 @@ namespace Content.Shared.Revolutionary.Components;
89
/// Used for marking regular revs as well as storing icon prototypes so you can see fellow revs.
910
/// </summary>
1011
[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
11-
public sealed partial class RevolutionaryComponent : Component
12+
public sealed partial class RevolutionaryComponent : Component, IAntagStatusIconComponent
1213
{
1314
/// <summary>
1415
/// The status icon prototype displayed for revolutionaries
1516
/// </summary>
1617
[DataField, ViewVariables(VVAccess.ReadWrite)]
17-
public ProtoId<StatusIconPrototype> RevStatusIcon = "RevolutionaryFaction";
18+
public ProtoId<StatusIconPrototype> StatusIcon { get; set; } = "RevolutionaryFaction";
19+
20+
public override bool SessionSpecific => true;
21+
22+
[DataField]
23+
public bool IconVisibleToGhost { get; set; } = true;
1824
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Robust.Shared.GameStates;
2+
3+
namespace Content.Shared.Revolutionary.Components;
4+
5+
/// <summary>
6+
/// Determines whether Someone can see rev/headrev icons on revs.
7+
/// </summary>
8+
[RegisterComponent, NetworkedComponent]
9+
public sealed partial class ShowRevIconsComponent: Component
10+
{
11+
}

‎Content.Shared/Revolutionary/SharedRevolutionarySystem.cs‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
using Content.Shared.Ghost;
12
using Content.Shared.IdentityManagement;
23
using Content.Shared.Mindshield.Components;
34
using Content.Shared.Popups;
45
using Content.Shared.Revolutionary.Components;
56
using Content.Shared.Stunnable;
7+
using Robust.Shared.GameStates;
8+
using Robust.Shared.Player;
69

710
namespace Content.Shared.Revolutionary;
811

@@ -14,7 +17,13 @@ public sealed class SharedRevolutionarySystem : EntitySystem
1417
public override void Initialize()
1518
{
1619
base.Initialize();
20+
1721
SubscribeLocalEvent<MindShieldComponent, MapInitEvent>(MindShieldImplanted);
22+
SubscribeLocalEvent<RevolutionaryComponent, ComponentGetStateAttemptEvent>(OnRevCompGetStateAttempt);
23+
SubscribeLocalEvent<HeadRevolutionaryComponent, ComponentGetStateAttemptEvent>(OnRevCompGetStateAttempt);
24+
SubscribeLocalEvent<RevolutionaryComponent, ComponentStartup>(DirtyRevComps);
25+
SubscribeLocalEvent<HeadRevolutionaryComponent, ComponentStartup>(DirtyRevComps);
26+
SubscribeLocalEvent<ShowRevIconsComponent, ComponentStartup>(DirtyRevComps);
1827
}
1928

2029
/// <summary>
@@ -37,4 +46,64 @@ private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, MapIni
3746
_popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid);
3847
}
3948
}
49+
50+
/// <summary>
51+
/// Determines if a HeadRev component should be sent to the client.
52+
/// </summary>
53+
private void OnRevCompGetStateAttempt(EntityUid uid, HeadRevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args)
54+
{
55+
args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost);
56+
}
57+
58+
/// <summary>
59+
/// Determines if a Rev component should be sent to the client.
60+
/// </summary>
61+
private void OnRevCompGetStateAttempt(EntityUid uid, RevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args)
62+
{
63+
args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost);
64+
}
65+
66+
/// <summary>
67+
/// The criteria that determine whether a Rev/HeadRev component should be sent to a client.
68+
/// </summary>
69+
/// <param name="player"> The Player the component will be sent to.</param>
70+
/// <param name="visibleToGhosts"> Whether the component permits the icon to be visible to observers. </param>
71+
/// <returns></returns>
72+
private bool CanGetState(ICommonSession? player, bool visibleToGhosts)
73+
{
74+
//Apparently this can be null in replays so I am just returning true.
75+
if (player is null)
76+
return true;
77+
78+
var uid = player.AttachedEntity;
79+
80+
if (HasComp<RevolutionaryComponent>(uid) || HasComp<HeadRevolutionaryComponent>(uid))
81+
return true;
82+
83+
if (visibleToGhosts && HasComp<GhostComponent>(uid))
84+
return true;
85+
86+
return HasComp<ShowRevIconsComponent>(uid);
87+
}
88+
/// <summary>
89+
/// Dirties all the Rev components so they are sent to clients.
90+
///
91+
/// We need to do this because if a rev component was not earlier sent to a client and for example the client
92+
/// 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
93+
/// per client basis so we are just dirtying all the components.
94+
/// </summary>
95+
private void DirtyRevComps<T>(EntityUid someUid, T someComp, ComponentStartup ev)
96+
{
97+
var revComps = AllEntityQuery<RevolutionaryComponent>();
98+
while (revComps.MoveNext(out var uid, out var comp))
99+
{
100+
Dirty(uid, comp);
101+
}
102+
103+
var headRevComps = AllEntityQuery<HeadRevolutionaryComponent>();
104+
while (headRevComps.MoveNext(out var uid, out var comp))
105+
{
106+
Dirty(uid, comp);
107+
}
108+
}
40109
}

‎Content.Shared/StatusIcon/Components/StatusIconComponent.cs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,15 @@ public sealed partial class StatusIconComponent : Component
2525
/// <param name="StatusIcons"></param>
2626
[ByRefEvent]
2727
public record struct GetStatusIconsEvent(List<StatusIconData> StatusIcons, bool InContainer);
28+
29+
/// <summary>
30+
/// Event raised on the Client-side to determine whether to display a status icon on an entity.
31+
/// </summary>
32+
/// <param name="User">The player that will see the icons</param>
33+
[ByRefEvent]
34+
public record struct CanDisplayStatusIconsEvent(EntityUid? User = null)
35+
{
36+
public EntityUid? User = User;
37+
38+
public bool Cancelled = false;
39+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Robust.Shared.GameStates;
2+
3+
namespace Content.Shared.Zombies;
4+
5+
/// <summary>
6+
/// Makes it so an entity can view ZombieAntagIcons.
7+
/// </summary>
8+
[RegisterComponent, NetworkedComponent]
9+
public sealed partial class ShowZombieIconsComponent: Component
10+
{
11+
12+
}

0 commit comments

Comments
 (0)