using System.Linq; using Content.Client.CharacterInfo; using Content.Client.Gameplay; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Systems.Character.Controls; using Content.Client.UserInterface.Systems.Character.Windows; using Content.Client.UserInterface.Systems.Objectives.Controls; using Content.Shared.Input; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input.Binding; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using static Content.Client.CharacterInfo.CharacterInfoSystem; using static Robust.Client.UserInterface.Controls.BaseButton; namespace Content.Client.UserInterface.Systems.Character; [UsedImplicitly] public sealed class CharacterUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged { [Dependency] private readonly IEntityManager _ent = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!; [UISystemDependency] private readonly SpriteSystem _sprite = default!; public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(OnRoleTypeChanged); } private CharacterWindow? _window; private MenuButton? CharacterButton => UIManager.GetActiveUIWidgetOrNull()?.CharacterButton; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_window == null); _window = UIManager.CreateWindow(); LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); _window.OnClose += DeactivateButton; _window.OnOpen += ActivateButton; CommandBinds.Builder .Bind(ContentKeyFunctions.OpenCharacterMenu, InputCmdHandler.FromDelegate(_ => ToggleWindow())) .Register(); } public void OnStateExited(GameplayState state) { if (_window != null) { _window.Close(); _window = null; } CommandBinds.Unregister(); } public void OnSystemLoaded(CharacterInfoSystem system) { system.OnCharacterUpdate += CharacterUpdated; _player.LocalPlayerDetached += CharacterDetached; } public void OnSystemUnloaded(CharacterInfoSystem system) { system.OnCharacterUpdate -= CharacterUpdated; _player.LocalPlayerDetached -= CharacterDetached; } public void UnloadButton() { if (CharacterButton == null) { return; } CharacterButton.OnPressed -= CharacterButtonPressed; } public void LoadButton() { if (CharacterButton == null) { return; } CharacterButton.OnPressed += CharacterButtonPressed; } private void DeactivateButton() { if (CharacterButton == null) { return; } CharacterButton.Pressed = false; } private void ActivateButton() { if (CharacterButton == null) { return; } CharacterButton.Pressed = true; } private void CharacterUpdated(CharacterData data) { if (_window == null) { return; } var (entity, job, objectives, briefing, entityName) = data; _window.SpriteView.SetEntity(entity); UpdateRoleType(); _window.NameLabel.Text = entityName; _window.SubText.Text = job; _window.Objectives.RemoveAllChildren(); _window.ObjectivesLabel.Visible = objectives.Any(); foreach (var (groupId, conditions) in objectives) { var objectiveControl = new CharacterObjectiveControl { Orientation = BoxContainer.LayoutOrientation.Vertical, Modulate = Color.Gray }; var objectiveText = new FormattedMessage(); objectiveText.TryAddMarkup(groupId, out _); var objectiveLabel = new RichTextLabel { StyleClasses = { StyleNano.StyleClassTooltipActionTitle } }; objectiveLabel.SetMessage(objectiveText); objectiveControl.AddChild(objectiveLabel); foreach (var condition in conditions) { var conditionControl = new ObjectiveConditionsControl(); conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.Icon); conditionControl.ProgressTexture.Progress = condition.Progress; var titleMessage = new FormattedMessage(); var descriptionMessage = new FormattedMessage(); titleMessage.AddText(condition.Title); descriptionMessage.AddText(condition.Description); conditionControl.Title.SetMessage(titleMessage); conditionControl.Description.SetMessage(descriptionMessage); objectiveControl.AddChild(conditionControl); } _window.Objectives.AddChild(objectiveControl); } if (briefing != null) { var briefingControl = new ObjectiveBriefingControl(); var text = new FormattedMessage(); text.PushColor(Color.Yellow); text.AddText(briefing); briefingControl.Label.SetMessage(text); _window.Objectives.AddChild(briefingControl); } var controls = _characterInfo.GetCharacterInfoControls(entity); foreach (var control in controls) { _window.Objectives.AddChild(control); } _window.RolePlaceholder.Visible = briefing == null && !controls.Any() && !objectives.Any(); } private void OnRoleTypeChanged(MindRoleTypeChangedEvent ev, EntitySessionEventArgs _) { UpdateRoleType(); } private void UpdateRoleType() { if (_window == null || !_window.IsOpen) return; if (!_ent.TryGetComponent(_player.LocalEntity, out var container) || container.Mind is null) return; if (!_ent.TryGetComponent(container.Mind.Value, out var mind)) return; if (!_prototypeManager.TryIndex(mind.RoleType, out var proto)) Log.Error($"Player '{_player.LocalSession}' has invalid Role Type '{mind.RoleType}'. Displaying default instead"); _window.RoleType.Text = Loc.GetString(proto?.Name ?? "role-type-crew-aligned-name"); _window.RoleType.FontColorOverride = proto?.Color ?? Color.White; } private void CharacterDetached(EntityUid uid) { CloseWindow(); } private void CharacterButtonPressed(ButtonEventArgs args) { ToggleWindow(); } private void CloseWindow() { _window?.Close(); } private void ToggleWindow() { if (_window == null) return; CharacterButton?.SetClickPressed(!_window.IsOpen); if (_window.IsOpen) { CloseWindow(); } else { _characterInfo.RequestCharacterInfo(); _window.Open(); } } }