diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs
index 4a11eeb584..2750547ad3 100644
--- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs
+++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs
@@ -17,10 +17,10 @@ namespace Content.Client.GameObjects.Components.Items
[ComponentReference(typeof(ISharedHandsComponent))]
public class HandsComponent : SharedHandsComponent
{
- private HandsGui? _gui;
-
[Dependency] private readonly IGameHud _gameHud = default!;
+ private HandsGui? _gui;
+
///
private readonly List _hands = new List();
diff --git a/Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs
new file mode 100644
index 0000000000..82cf727f90
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs
@@ -0,0 +1,109 @@
+#nullable enable
+using System.Collections.Generic;
+using System.Linq;
+using Content.Client.UserInterface;
+using Content.Client.UserInterface.Suspicion;
+using Content.Shared.GameObjects.Components.Suspicion;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Network;
+using Robust.Shared.IoC;
+using Robust.Shared.Players;
+
+namespace Content.Client.GameObjects.Components.Suspicion
+{
+ [RegisterComponent]
+ public class SuspicionRoleComponent : SharedSuspicionRoleComponent
+ {
+ [Dependency] private readonly IGameHud _gameHud = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+
+ private SuspicionGui? _gui;
+ private string? _role;
+ private bool? _antagonist;
+
+ public string? Role
+ {
+ get => _role;
+ set
+ {
+ _role = value;
+ _gui?.UpdateLabel();
+ Dirty();
+ }
+ }
+
+ public bool? Antagonist
+ {
+ get => _antagonist;
+ set
+ {
+ _antagonist = value;
+ _gui?.UpdateLabel();
+ Dirty();
+ }
+ }
+
+ public HashSet Allies { get; } = new HashSet();
+
+ public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
+ {
+ base.HandleComponentState(curState, nextState);
+
+ if (!(curState is SuspicionRoleComponentState state))
+ {
+ return;
+ }
+
+ _role = state.Role;
+ _antagonist = state.Antagonist;
+ }
+
+ public override void HandleMessage(ComponentMessage message, IComponent? component)
+ {
+ base.HandleMessage(message, component);
+
+ switch (message)
+ {
+ case PlayerAttachedMsg _:
+ if (_gui == null)
+ {
+ _gui = new SuspicionGui();
+ }
+ else
+ {
+ _gui.Parent?.RemoveChild(_gui);
+ }
+
+ _gameHud.SuspicionContainer.AddChild(_gui);
+ _gui.UpdateLabel();
+
+ break;
+ case PlayerDetachedMsg _:
+ _gui?.Parent?.RemoveChild(_gui);
+ break;
+ }
+ }
+
+ public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
+ {
+ base.HandleNetworkMessage(message, netChannel, session);
+
+ switch (message)
+ {
+ case SuspicionAlliesMessage msg:
+ Allies.Clear();
+ Allies.UnionWith(msg.Allies.Select(_entityManager.GetEntity));
+ break;
+ }
+ }
+
+ public override void OnRemove()
+ {
+ base.OnRemove();
+
+ _gui?.Dispose();
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/GameHud.cs b/Content.Client/UserInterface/GameHud.cs
index ced6e8a15b..51ed48c2ad 100644
--- a/Content.Client/UserInterface/GameHud.cs
+++ b/Content.Client/UserInterface/GameHud.cs
@@ -47,6 +47,7 @@ namespace Content.Client.UserInterface
Action SandboxButtonToggled { get; set; }
Control HandsContainer { get; }
+ Control SuspicionContainer { get; }
Control InventoryQuickButtonContainer { get; }
bool CombatPanelVisible { get; set; }
@@ -79,6 +80,7 @@ namespace Content.Client.UserInterface
[Dependency] private readonly IInputManager _inputManager = default!;
public Control HandsContainer { get; private set; }
+ public Control SuspicionContainer { get; private set; }
public Control InventoryQuickButtonContainer { get; private set; }
public bool CombatPanelVisible
@@ -242,6 +244,17 @@ namespace Content.Client.UserInterface
LayoutContainer.SetAnchorAndMarginPreset(HandsContainer, LayoutContainer.LayoutPreset.CenterBottom);
LayoutContainer.SetGrowHorizontal(HandsContainer, LayoutContainer.GrowDirection.Both);
LayoutContainer.SetGrowVertical(HandsContainer, LayoutContainer.GrowDirection.Begin);
+
+ SuspicionContainer = new MarginContainer
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter
+ };
+
+ RootControl.AddChild(SuspicionContainer);
+
+ LayoutContainer.SetAnchorAndMarginPreset(SuspicionContainer, LayoutContainer.LayoutPreset.BottomLeft, margin: 10);
+ LayoutContainer.SetGrowHorizontal(SuspicionContainer, LayoutContainer.GrowDirection.End);
+ LayoutContainer.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin);
}
private void ButtonTutorialOnOnToggled()
diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.cs b/Content.Client/UserInterface/Suspicion/SuspicionGui.cs
new file mode 100644
index 0000000000..e466eb8074
--- /dev/null
+++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Globalization;
+using Content.Client.GameObjects.Components.Suspicion;
+using Content.Shared.Interfaces;
+using Robust.Client.Player;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using Robust.Shared.Timing;
+using static Robust.Client.UserInterface.Controls.BaseButton;
+
+namespace Content.Client.UserInterface.Suspicion
+{
+ public class SuspicionGui : Control
+ {
+#pragma warning disable 0649
+ [Dependency] private readonly IPlayerManager _playerManager;
+#pragma warning restore 0649
+
+ private readonly VBoxContainer _container;
+ private readonly Button _roleButton;
+
+ private string _previousRoleName;
+ private bool _previousAntagonist;
+
+ public SuspicionGui()
+ {
+ IoCManager.InjectDependencies(this);
+
+ AddChild(_container = new VBoxContainer
+ {
+ SeparationOverride = 0,
+ Children =
+ {
+ (_roleButton = new Button
+ {
+ Name = "Suspicion Role Button"
+ })
+ }
+ });
+
+ _roleButton.CustomMinimumSize = (200, 60);
+ _roleButton.OnPressed += RoleButtonPressed;
+ }
+
+ private void RoleButtonPressed(ButtonEventArgs obj)
+ {
+ if (!TryGetComponent(out var role))
+ {
+ return;
+ }
+
+ if (!role.Antagonist ?? false)
+ {
+ return;
+ }
+
+ var allies = string.Join(", ", role.Allies);
+ var message = role.Allies.Count switch
+ {
+ 0 => Loc.GetString("You have no allies"),
+ 1 => Loc.GetString("Your ally is {0}", allies),
+ var n when n > 2 => Loc.GetString("Your allies are {0}", allies),
+ _ => throw new ArgumentException($"Invalid number of allies: {role.Allies.Count}")
+ };
+
+ role.Owner.PopupMessage(role.Owner, message);
+ }
+
+ private bool TryGetComponent(out SuspicionRoleComponent suspicion)
+ {
+ suspicion = default;
+
+ return _playerManager?.LocalPlayer?.ControlledEntity?.TryGetComponent(out suspicion) == true;
+ }
+
+ public void UpdateLabel()
+ {
+ if (!TryGetComponent(out var suspicion))
+ {
+ Visible = false;
+ return;
+ }
+
+ if (suspicion.Role == null || suspicion.Antagonist == null)
+ {
+ Visible = false;
+ return;
+ }
+
+ if (_previousRoleName == suspicion.Role && _previousAntagonist == suspicion.Antagonist)
+ {
+ return;
+ }
+
+ _previousRoleName = suspicion.Role;
+ _previousAntagonist = suspicion.Antagonist.Value;
+
+ var buttonText = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_previousRoleName);
+ buttonText = Loc.GetString(buttonText);
+
+ _roleButton.Text = buttonText;
+ _roleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.Green;
+
+ Visible = true;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ UpdateLabel();
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs
index d982130afa..70071bc28b 100644
--- a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs
+++ b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs
@@ -1,31 +1,185 @@
-using Content.Server.GameObjects.Components.Mobs;
+#nullable enable
+using System.Collections.Generic;
+using System.Linq;
+using Content.Server.GameObjects.Components.Mobs;
+using Content.Server.GameObjects.EntitySystems;
+using Content.Server.Mobs;
using Content.Server.Mobs.Roles;
+using Content.Server.Mobs.Roles.Suspicion;
using Content.Shared.GameObjects.Components.Damage;
+using Content.Shared.GameObjects.Components.Suspicion;
using Content.Shared.GameObjects.EntitySystems;
+using Robust.Server.GameObjects;
+using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
+using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Suspicion
{
[RegisterComponent]
- public class SuspicionRoleComponent : Component, IExamine
+ public class SuspicionRoleComponent : SharedSuspicionRoleComponent, IExamine
{
- public override string Name => "SuspicionRole";
+ private Role? _role;
+ private readonly HashSet _allies = new HashSet();
+
+ [ViewVariables]
+ public Role? Role
+ {
+ get => _role;
+ set
+ {
+ if (_role == value)
+ {
+ return;
+ }
+
+ _role = value;
+
+ Dirty();
+
+ var suspicionRoleSystem = EntitySystem.Get();
+
+ if (value == null || !value.Antagonist)
+ {
+ ClearAllies();
+ suspicionRoleSystem.RemoveTraitor(this);
+ }
+ else if (value.Antagonist)
+ {
+ SetAllies(suspicionRoleSystem.Traitors);
+ suspicionRoleSystem.AddTraitor(this);
+ }
+ }
+ }
+
+ [ViewVariables] public bool KnowsAllies => IsTraitor();
public bool IsDead()
{
- return Owner.TryGetComponent(out IDamageableComponent damageable) &&
+ return Owner.TryGetComponent(out IDamageableComponent? damageable) &&
damageable.CurrentDamageState == DamageState.Dead;
}
+ public bool IsInnocent()
+ {
+ return Owner.TryGetComponent(out MindComponent? mind) &&
+ mind.HasMind &&
+ mind.Mind!.HasRole();
+ }
+
public bool IsTraitor()
{
- return Owner.TryGetComponent(out MindComponent mind) &&
+ return Owner.TryGetComponent(out MindComponent? mind) &&
mind.HasMind &&
mind.Mind!.HasRole();
}
+ public void SyncRoles()
+ {
+ if (!Owner.TryGetComponent(out MindComponent? mind) ||
+ !mind.HasMind)
+ {
+ return;
+ }
+
+ Role = mind.Mind!.AllRoles.First(role => role is SuspicionRole);
+ }
+
+ public void AddAlly(SuspicionRoleComponent ally)
+ {
+ if (ally == this)
+ {
+ return;
+ }
+
+ _allies.Add(ally);
+
+ if (KnowsAllies && Owner.TryGetComponent(out IActorComponent? actor))
+ {
+ var channel = actor.playerSession.ConnectedClient;
+ DebugTools.AssertNotNull(channel);
+
+ var message = new SuspicionAllyAddedMessage(ally.Owner.Uid);
+
+ SendNetworkMessage(message, channel);
+ }
+ }
+
+ public bool RemoveAlly(SuspicionRoleComponent ally)
+ {
+ if (ally == this)
+ {
+ return false;
+ }
+
+ if (_allies.Remove(ally))
+ {
+ if (KnowsAllies && Owner.TryGetComponent(out IActorComponent? actor))
+ {
+ var channel = actor.playerSession.ConnectedClient;
+ DebugTools.AssertNotNull(channel);
+
+ var message = new SuspicionAllyRemovedMessage(ally.Owner.Uid);
+
+ SendNetworkMessage(message, channel);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void SetAllies(IEnumerable allies)
+ {
+ _allies.Clear();
+
+ foreach (var ally in allies)
+ {
+ if (ally == this)
+ {
+ continue;
+ }
+
+ _allies.Add(ally);
+ }
+
+ if (!KnowsAllies ||
+ !Owner.TryGetComponent(out IActorComponent? actor))
+ {
+ return;
+ }
+
+ var channel = actor.playerSession.ConnectedClient;
+ DebugTools.AssertNotNull(channel);
+
+ var message = new SuspicionAlliesMessage(_allies.Select(role => role.Owner.Uid));
+
+ SendNetworkMessage(message, channel);
+ }
+
+ public void ClearAllies()
+ {
+ _allies.Clear();
+
+ if (!KnowsAllies ||
+ !Owner.TryGetComponent(out IActorComponent? actor))
+ {
+ return;
+ }
+
+ var channel = actor.playerSession.ConnectedClient;
+ DebugTools.AssertNotNull(channel);
+
+ var message = new SuspicionAlliesClearedMessage();
+
+ SendNetworkMessage(message, channel);
+ }
+
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
if (!IsDead())
@@ -39,5 +193,43 @@ namespace Content.Server.GameObjects.Components.Suspicion
message.AddMarkup(tooltip);
}
+
+ public override void OnRemove()
+ {
+ Role = null;
+ base.OnRemove();
+ }
+
+ public override ComponentState GetComponentState()
+ {
+ return Role == null
+ ? new SuspicionRoleComponentState(null, null)
+ : new SuspicionRoleComponentState(Role?.Name, Role?.Antagonist);
+ }
+
+ public override void HandleMessage(ComponentMessage message, IComponent? component)
+ {
+ base.HandleMessage(message, component);
+
+ if (!(message is RoleMessage msg) ||
+ !(msg.Role is SuspicionRole role))
+ {
+ return;
+ }
+
+ switch (message)
+ {
+ case PlayerAttachedMsg _:
+ case PlayerDetachedMsg _:
+ SyncRoles();
+ break;
+ case RoleAddedMessage _:
+ Role = role;
+ break;
+ case RoleRemovedMessage _:
+ Role = null;
+ break;
+ }
+ }
}
}
diff --git a/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs b/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs
new file mode 100644
index 0000000000..936fe24185
--- /dev/null
+++ b/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using Content.Server.GameObjects.Components.Suspicion;
+using JetBrains.Annotations;
+using Robust.Shared.GameObjects.Systems;
+
+namespace Content.Server.GameObjects.EntitySystems
+{
+ [UsedImplicitly]
+ public class SuspicionRoleSystem : EntitySystem
+ {
+ private readonly HashSet _traitors = new HashSet();
+
+ public IReadOnlyCollection Traitors => _traitors;
+
+ public void AddTraitor(SuspicionRoleComponent role)
+ {
+ if (!_traitors.Add(role))
+ {
+ return;
+ }
+
+ foreach (var traitor in _traitors)
+ {
+ traitor.AddAlly(role);
+ }
+
+ role.SetAllies(_traitors);
+ }
+
+ public void RemoveTraitor(SuspicionRoleComponent role)
+ {
+ if (!_traitors.Remove(role))
+ {
+ return;
+ }
+
+ foreach (var traitor in _traitors)
+ {
+ traitor.RemoveAlly(role);
+ }
+
+ role.ClearAllies();
+ }
+
+ public override void Shutdown()
+ {
+ _traitors.Clear();
+ base.Shutdown();
+ }
+ }
+}
diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs
index 81eeb43d22..eb192dbea6 100644
--- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs
+++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs
@@ -1,7 +1,6 @@
using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
-using Content.Server.Mobs.Roles;
using Content.Server.Players;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.Random;
@@ -11,6 +10,8 @@ using Robust.Shared.Random;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Suspicion;
+using Content.Server.Mobs.Roles;
+using Content.Server.Mobs.Roles.Suspicion;
using Content.Shared.Roles;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Configuration;
diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs
index b67888d6f0..0161ab409a 100644
--- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs
+++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs
@@ -5,6 +5,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Mobs.Roles;
+using Content.Server.Mobs.Roles.Suspicion;
using Content.Server.Players;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Server.GameObjects.EntitySystems;
diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs
index b0247fc202..1fc6e5f71c 100644
--- a/Content.Server/Mobs/Mind.cs
+++ b/Content.Server/Mobs/Mind.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Mobs;
+using Content.Server.Mobs.Roles;
using Content.Server.Players;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
@@ -95,7 +96,7 @@ namespace Content.Server.Mobs
///
/// Gives this mind a new role.
///
- /// The type of the role to give.
+ /// The type of the role to give.
/// The instance of the role.
///
/// Thrown if we already have a role with this type.
@@ -109,13 +110,17 @@ namespace Content.Server.Mobs
_roles.Add(role);
role.Greet();
+
+ var message = new RoleAddedMessage(role);
+ OwnedEntity?.SendMessage(OwnedMob, message);
+
return role;
}
///
/// Removes a role from this mind.
///
- /// The type of the role to remove.
+ /// The type of the role to remove.
///
/// Thrown if we do not have this role.
///
@@ -126,9 +131,10 @@ namespace Content.Server.Mobs
throw new ArgumentException($"We do not have this role: {role}");
}
- // This can definitely get more complex removal hooks later,
- // when we need it.
_roles.Remove(role);
+
+ var message = new RoleRemovedMessage(role);
+ OwnedEntity?.SendMessage(OwnedMob, message);
}
public bool HasRole() where T : Role
diff --git a/Content.Server/Mobs/Roles/RoleAddedMessage.cs b/Content.Server/Mobs/Roles/RoleAddedMessage.cs
new file mode 100644
index 0000000000..c4de66e333
--- /dev/null
+++ b/Content.Server/Mobs/Roles/RoleAddedMessage.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.Mobs.Roles
+{
+ public class RoleAddedMessage : RoleMessage
+ {
+ public RoleAddedMessage(Role role) : base(role) { }
+ }
+}
diff --git a/Content.Server/Mobs/Roles/RoleMessage.cs b/Content.Server/Mobs/Roles/RoleMessage.cs
new file mode 100644
index 0000000000..1a4489ddc7
--- /dev/null
+++ b/Content.Server/Mobs/Roles/RoleMessage.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameObjects;
+
+namespace Content.Server.Mobs.Roles
+{
+ public class RoleMessage : ComponentMessage
+ {
+ public readonly Role Role;
+
+ public RoleMessage(Role role)
+ {
+ Role = role;
+ }
+ }
+}
diff --git a/Content.Server/Mobs/Roles/RoleRemovedMessage.cs b/Content.Server/Mobs/Roles/RoleRemovedMessage.cs
new file mode 100644
index 0000000000..0e8cd0ef85
--- /dev/null
+++ b/Content.Server/Mobs/Roles/RoleRemovedMessage.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.Mobs.Roles
+{
+ public class RoleRemovedMessage : RoleMessage
+ {
+ public RoleRemovedMessage(Role role) : base(role) { }
+ }
+}
diff --git a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs b/Content.Server/Mobs/Roles/Suspicion/SuspicionInnocentRole.cs
similarity index 89%
rename from Content.Server/Mobs/Roles/SuspicionInnocentRole.cs
rename to Content.Server/Mobs/Roles/Suspicion/SuspicionInnocentRole.cs
index 570e307c79..0fab5b7059 100644
--- a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs
+++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionInnocentRole.cs
@@ -2,9 +2,9 @@ using Content.Server.Interfaces.Chat;
using Content.Shared.Roles;
using Robust.Shared.IoC;
-namespace Content.Server.Mobs.Roles
+namespace Content.Server.Mobs.Roles.Suspicion
{
- public class SuspicionInnocentRole : Role
+ public class SuspicionInnocentRole : SuspicionRole
{
public AntagPrototype Prototype { get; }
diff --git a/Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs b/Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs
new file mode 100644
index 0000000000..f9b46aecd3
--- /dev/null
+++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.Mobs.Roles.Suspicion
+{
+ public abstract class SuspicionRole : Role
+ {
+ protected SuspicionRole(Mind mind) : base(mind) { }
+ }
+}
diff --git a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs b/Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs
similarity index 94%
rename from Content.Server/Mobs/Roles/SuspicionTraitorRole.cs
rename to Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs
index f10bf273e8..d265bb23bf 100644
--- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs
+++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Interfaces.Chat;
+using Content.Server.Mobs.Roles.Suspicion;
using Content.Shared.Roles;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
@@ -9,7 +10,7 @@ using Robust.Shared.Localization;
namespace Content.Server.Mobs.Roles
{
- public sealed class SuspicionTraitorRole : Role
+ public sealed class SuspicionTraitorRole : SuspicionRole
{
public AntagPrototype Prototype { get; }
diff --git a/Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs b/Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs
new file mode 100644
index 0000000000..c31e2e7cce
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs
@@ -0,0 +1,75 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.GameObjects.Components.Suspicion
+{
+ public abstract class SharedSuspicionRoleComponent : Component
+ {
+ public sealed override string Name => "SuspicionRole";
+ public sealed override uint? NetID => ContentNetIDs.SUSPICION_ROLE;
+ }
+
+ [Serializable, NetSerializable]
+ public class SuspicionRoleComponentState : ComponentState
+ {
+ public readonly string? Role;
+ public readonly bool? Antagonist;
+
+ public SuspicionRoleComponentState(string? role, bool? antagonist) : base(ContentNetIDs.SUSPICION_ROLE)
+ {
+ Role = role;
+ Antagonist = antagonist;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public class SuspicionAlliesMessage : ComponentMessage
+ {
+ public readonly HashSet Allies;
+
+ public SuspicionAlliesMessage(HashSet allies)
+ {
+ Directed = true;
+ Allies = allies;
+ }
+
+ public SuspicionAlliesMessage(IEnumerable allies) : this(allies.ToHashSet()) { }
+ }
+
+ [Serializable, NetSerializable]
+ public class SuspicionAllyAddedMessage : ComponentMessage
+ {
+ public readonly EntityUid Ally;
+
+ public SuspicionAllyAddedMessage(EntityUid ally)
+ {
+ Directed = true;
+ Ally = ally;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public class SuspicionAllyRemovedMessage : ComponentMessage
+ {
+ public readonly EntityUid Ally;
+
+ public SuspicionAllyRemovedMessage(EntityUid ally)
+ {
+ Directed = true;
+ Ally = ally;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public class SuspicionAlliesClearedMessage : ComponentMessage
+ {
+ public SuspicionAlliesClearedMessage()
+ {
+ Directed = true;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index 32ede76f3d..3f127c68c4 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -71,6 +71,7 @@
public const uint CUFFED = 1065;
public const uint HANDCUFFS = 1066;
public const uint BATTERY_BARREL = 1067;
+ public const uint SUSPICION_ROLE = 1068;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;