Add HUD button that displays your SSS role and allies (#1895)

* Add button that displays your SSS role and allies

* Capitalize button name

* Add cases for 0, 1 and invalid number of allies

* Make the ally syncing system saner
This commit is contained in:
DrSmugleaf
2020-08-27 16:39:29 +02:00
committed by GitHub
parent 388e717a53
commit 548ef3dedb
17 changed files with 616 additions and 15 deletions

View File

@@ -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;
/// <inheritdoc />
private readonly List<Hand> _hands = new List<Hand>();

View File

@@ -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<IEntity> Allies { get; } = new HashSet<IEntity>();
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();
}
}
}

View File

@@ -47,6 +47,7 @@ namespace Content.Client.UserInterface
Action<bool> 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()

View File

@@ -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();
}
}
}

View File

@@ -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<SuspicionRoleComponent> _allies = new HashSet<SuspicionRoleComponent>();
[ViewVariables]
public Role? Role
{
get => _role;
set
{
if (_role == value)
{
return;
}
_role = value;
Dirty();
var suspicionRoleSystem = EntitySystem.Get<SuspicionRoleSystem>();
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<SuspicionInnocentRole>();
}
public bool IsTraitor()
{
return Owner.TryGetComponent(out MindComponent mind) &&
return Owner.TryGetComponent(out MindComponent? mind) &&
mind.HasMind &&
mind.Mind!.HasRole<SuspicionTraitorRole>();
}
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<SuspicionRoleComponent> 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;
}
}
}
}

View File

@@ -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<SuspicionRoleComponent> _traitors = new HashSet<SuspicionRoleComponent>();
public IReadOnlyCollection<SuspicionRoleComponent> 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();
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
/// <summary>
/// Gives this mind a new role.
/// </summary>
/// <param name="t">The type of the role to give.</param>
/// <param name="role">The type of the role to give.</param>
/// <returns>The instance of the role.</returns>
/// <exception cref="ArgumentException">
/// 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;
}
/// <summary>
/// Removes a role from this mind.
/// </summary>
/// <param name="t">The type of the role to remove.</param>
/// <param name="role">The type of the role to remove.</param>
/// <exception cref="ArgumentException">
/// Thrown if we do not have this role.
/// </exception>
@@ -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<T>() where T : Role

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Mobs.Roles
{
public class RoleAddedMessage : RoleMessage
{
public RoleAddedMessage(Role role) : base(role) { }
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Mobs.Roles
{
public class RoleRemovedMessage : RoleMessage
{
public RoleRemovedMessage(Role role) : base(role) { }
}
}

View File

@@ -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; }

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Mobs.Roles.Suspicion
{
public abstract class SuspicionRole : Role
{
protected SuspicionRole(Mind mind) : base(mind) { }
}
}

View File

@@ -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; }

View File

@@ -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<EntityUid> Allies;
public SuspicionAlliesMessage(HashSet<EntityUid> allies)
{
Directed = true;
Allies = allies;
}
public SuspicionAlliesMessage(IEnumerable<EntityUid> 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;
}
}
}

View File

@@ -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;