| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- using System.Linq;
- using System.Numerics;
- using System.Threading;
- using Content.Client.Verbs;
- using Content.Shared.Examine;
- using Content.Shared.IdentityManagement;
- using Content.Shared.Input;
- using Content.Shared.Interaction.Events;
- using Content.Shared.Item;
- using Content.Shared.Verbs;
- using JetBrains.Annotations;
- using Robust.Client.GameObjects;
- using Robust.Client.Graphics;
- using Robust.Client.Player;
- using Robust.Client.UserInterface;
- using Robust.Client.UserInterface.Controls;
- using Robust.Shared.Input.Binding;
- using Robust.Shared.Map;
- using Robust.Shared.Utility;
- using static Content.Shared.Interaction.SharedInteractionSystem;
- using static Robust.Client.UserInterface.Controls.BoxContainer;
- using Direction = Robust.Shared.Maths.Direction;
- namespace Content.Client.Examine
- {
- [UsedImplicitly]
- public sealed class ExamineSystem : ExamineSystemShared
- {
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IEyeManager _eyeManager = default!;
- [Dependency] private readonly VerbSystem _verbSystem = default!;
- public const string StyleClassEntityTooltip = "entity-tooltip";
- private EntityUid _examinedEntity;
- private EntityUid _lastExaminedEntity;
- private Popup? _examineTooltipOpen;
- private ScreenCoordinates _popupPos;
- private CancellationTokenSource? _requestCancelTokenSource;
- private int _idCounter;
- public override void Initialize()
- {
- base.Initialize();
- UpdatesOutsidePrediction = true;
- SubscribeLocalEvent<GetVerbsEvent<ExamineVerb>>(AddExamineVerb);
- SubscribeNetworkEvent<ExamineSystemMessages.ExamineInfoResponseMessage>(OnExamineInfoResponse);
- SubscribeLocalEvent<ItemComponent, DroppedEvent>(OnExaminedItemDropped);
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine, outsidePrediction: true))
- .Register<ExamineSystem>();
- _idCounter = 0;
- }
- private void OnExaminedItemDropped(EntityUid item, ItemComponent comp, DroppedEvent args)
- {
- if (!args.User.Valid)
- return;
- if (_examineTooltipOpen == null)
- return;
- if (item == _examinedEntity && args.User == _playerManager.LocalEntity)
- CloseTooltip();
- }
- public override void Update(float frameTime)
- {
- if (_examineTooltipOpen is not {Visible: true}) return;
- if (!_examinedEntity.Valid || _playerManager.LocalEntity is not { } player) return;
- if (!CanExamine(player, _examinedEntity))
- CloseTooltip();
- }
- public override void Shutdown()
- {
- CommandBinds.Unregister<ExamineSystem>();
- base.Shutdown();
- }
- public override bool CanExamine(EntityUid examiner, MapCoordinates target, Ignored? predicate = null, EntityUid? examined = null, ExaminerComponent? examinerComp = null)
- {
- if (!Resolve(examiner, ref examinerComp, false))
- return false;
- if (examinerComp.SkipChecks)
- return true;
- if (examinerComp.CheckInRangeUnOccluded)
- {
- // TODO fix this. This should be using the examiner's eye component, not eye manager.
- var b = _eyeManager.GetWorldViewbounds();
- if (!b.Contains(target.Position))
- return false;
- }
- return base.CanExamine(examiner, target, predicate, examined, examinerComp);
- }
- private bool HandleExamine(in PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- var entity = args.EntityUid;
- if (!args.EntityUid.IsValid() || !EntityManager.EntityExists(entity))
- {
- return false;
- }
- if (_playerManager.LocalEntity is not { } player ||
- !CanExamine(player, entity))
- {
- return false;
- }
- DoExamine(entity);
- return true;
- }
- private void AddExamineVerb(GetVerbsEvent<ExamineVerb> args)
- {
- if (!CanExamine(args.User, args.Target))
- return;
- // Basic examine verb.
- ExamineVerb verb = new();
- verb.Category = VerbCategory.Examine;
- verb.Priority = 10;
- // Center it on the entity if they use the verb instead.
- verb.Act = () => DoExamine(args.Target, false);
- verb.Text = Loc.GetString("examine-verb-name");
- verb.Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/examine.svg.192dpi.png"));
- verb.ShowOnExamineTooltip = false;
- verb.ClientExclusive = true;
- args.Verbs.Add(verb);
- }
- private void OnExamineInfoResponse(ExamineSystemMessages.ExamineInfoResponseMessage ev)
- {
- var player = _playerManager.LocalEntity;
- if (player == null)
- return;
- // Prevent updating a new tooltip.
- if (ev.Id != 0 && ev.Id != _idCounter)
- return;
- // Tooltips coming in from the server generally prioritize
- // opening at the old tooltip rather than the cursor/another entity,
- // since there's probably one open already if it's coming in from the server.
- var entity = GetEntity(ev.EntityUid);
- OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
- UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs);
- }
- public override void SendExamineTooltip(EntityUid player, EntityUid target, FormattedMessage message, bool getVerbs, bool centerAtCursor)
- {
- OpenTooltip(player, target, centerAtCursor, false);
- UpdateTooltipInfo(player, target, message);
- }
- /// <summary>
- /// Opens the tooltip window and sets spriteview/name/etc, but does
- /// not fill it with information. This is done when the server sends examine info/verbs,
- /// or immediately if it's entirely clientside.
- /// </summary>
- public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCursor=true, bool openAtOldTooltip=true, bool knowTarget = true)
- {
- // Close any examine tooltip that might already be opened
- // Before we do that, save its position. We'll prioritize opening any new popups there if
- // openAtOldTooltip is true.
- ScreenCoordinates? oldTooltipPos = _examineTooltipOpen != null ? _popupPos : null;
- CloseTooltip();
- // cache entity for Update function
- _examinedEntity = target;
- const float minWidth = 300;
- if (openAtOldTooltip && oldTooltipPos != null)
- {
- _popupPos = oldTooltipPos.Value;
- }
- else if (centeredOnCursor)
- {
- _popupPos = _userInterfaceManager.MousePositionScaled;
- }
- else
- {
- _popupPos = _eyeManager.CoordinatesToScreen(Transform(target).Coordinates);
- _popupPos = _userInterfaceManager.ScreenToUIPosition(_popupPos);
- }
- // Actually open the tooltip.
- _examineTooltipOpen = new Popup { MaxWidth = 400 };
- _userInterfaceManager.ModalRoot.AddChild(_examineTooltipOpen);
- var panel = new PanelContainer() { Name = "ExaminePopupPanel" };
- panel.AddStyleClass(StyleClassEntityTooltip);
- panel.ModulateSelfOverride = Color.LightGray.WithAlpha(0.90f);
- _examineTooltipOpen.AddChild(panel);
- var vBox = new BoxContainer
- {
- Name = "ExaminePopupVbox",
- Orientation = LayoutOrientation.Vertical,
- MaxWidth = _examineTooltipOpen.MaxWidth
- };
- panel.AddChild(vBox);
- var hBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- SeparationOverride = 5,
- Margin = new Thickness(6, 0, 6, 0)
- };
- vBox.AddChild(hBox);
- if (EntityManager.HasComponent<SpriteComponent>(target))
- {
- var spriteView = new SpriteView
- {
- OverrideDirection = Direction.South,
- SetSize = new Vector2(32, 32)
- };
- spriteView.SetEntity(target);
- hBox.AddChild(spriteView);
- }
- if (knowTarget)
- {
- var itemName = FormattedMessage.EscapeText(Identity.Name(target, EntityManager, player));
- var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]");
- var label = new RichTextLabel();
- label.SetMessage(labelMessage);
- hBox.AddChild(label);
- }
- else
- {
- var label = new RichTextLabel();
- label.SetMessage(FormattedMessage.FromMarkupOrThrow("[bold]???[/bold]"));
- hBox.AddChild(label);
- }
- panel.Measure(Vector2Helpers.Infinity);
- var size = Vector2.Max(new Vector2(minWidth, 0), panel.DesiredSize);
- _examineTooltipOpen.Open(UIBox2.FromDimensions(_popupPos.Position, size));
- }
- /// <summary>
- /// Fills the examine tooltip with a message and buttons if applicable.
- /// </summary>
- public void UpdateTooltipInfo(EntityUid player, EntityUid target, FormattedMessage message, List<Verb>? verbs=null)
- {
- var vBox = _examineTooltipOpen?.GetChild(0).GetChild(0);
- if (vBox == null)
- {
- return;
- }
- foreach (var msg in message.Nodes)
- {
- if (msg.Name != null)
- continue;
- var text = msg.Value.StringValue ?? "";
- if (string.IsNullOrWhiteSpace(text))
- continue;
- var richLabel = new RichTextLabel() { Margin = new Thickness(4, 4, 0, 4)};
- richLabel.SetMessage(message);
- vBox.AddChild(richLabel);
- break;
- }
- verbs ??= new List<Verb>();
- var totalVerbs = _verbSystem.GetLocalVerbs(target, player, typeof(ExamineVerb));
- totalVerbs.UnionWith(verbs);
- AddVerbsToTooltip(totalVerbs);
- }
- private void AddVerbsToTooltip(IEnumerable<Verb> verbs)
- {
- if (_examineTooltipOpen == null)
- return;
- var buttonsHBox = new BoxContainer
- {
- Name = "ExamineButtonsHBox",
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = Control.HAlignment.Right,
- VerticalAlignment = Control.VAlignment.Bottom,
- };
- // Examine button time
- foreach (var verb in verbs)
- {
- if (verb is not ExamineVerb examine)
- continue;
- if (examine.Icon == null)
- continue;
- if (!examine.ShowOnExamineTooltip)
- continue;
- var button = new ExamineButton(examine);
- button.OnPressed += VerbButtonPressed;
- buttonsHBox.AddChild(button);
- }
- var vbox = _examineTooltipOpen?.GetChild(0).GetChild(0);
- if (vbox == null)
- {
- buttonsHBox.Dispose();
- return;
- }
- // Remove any existing buttons hbox, in case we generated it from the client
- // then received ones from the server
- var hbox = vbox.Children.Where(c => c.Name == "ExamineButtonsHBox").ToArray();
- if (hbox.Any())
- {
- vbox.Children.Remove(hbox.First());
- }
- vbox.AddChild(buttonsHBox);
- }
- public void VerbButtonPressed(BaseButton.ButtonEventArgs obj)
- {
- if (obj.Button is ExamineButton button)
- {
- _verbSystem.ExecuteVerb(_examinedEntity, button.Verb);
- if (button.Verb.CloseMenu ?? button.Verb.CloseMenuDefault)
- CloseTooltip();
- }
- }
- public void DoExamine(EntityUid entity, bool centeredOnCursor = true, EntityUid? userOverride = null)
- {
- var playerEnt = userOverride ?? _playerManager.LocalEntity;
- if (playerEnt == null)
- return;
- FormattedMessage message;
- OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false);
- // Always update tooltip info from client first.
- // If we get it wrong, server will correct us later anyway.
- // This will usually be correct (barring server-only components, which generally only adds, not replaces text)
- message = GetExamineText(entity, playerEnt);
- UpdateTooltipInfo(playerEnt.Value, entity, message);
- if (!IsClientSide(entity))
- {
- // Ask server for extra examine info.
- if (entity != _lastExaminedEntity)
- _idCounter += 1;
- if (_idCounter == int.MaxValue)
- _idCounter = 0;
- RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(GetNetEntity(entity), _idCounter, true));
- }
- RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value));
- _lastExaminedEntity = entity;
- }
- private void CloseTooltip()
- {
- if (_examineTooltipOpen != null)
- {
- foreach (var control in _examineTooltipOpen.Children)
- {
- if (control is ExamineButton button)
- {
- button.OnPressed -= VerbButtonPressed;
- }
- }
- _examineTooltipOpen.Dispose();
- _examineTooltipOpen = null;
- }
- if (_requestCancelTokenSource != null)
- {
- _requestCancelTokenSource.Cancel();
- _requestCancelTokenSource = null;
- }
- }
- }
- /// <summary>
- /// An entity was examined on the client.
- /// </summary>
- public sealed class ClientExaminedEvent : EntityEventArgs
- {
- /// <summary>
- /// The entity performing the examining.
- /// </summary>
- public readonly EntityUid Examiner;
- /// <summary>
- /// Entity being examined, for broadcast event purposes.
- /// </summary>
- public readonly EntityUid Examined;
- public ClientExaminedEvent(EntityUid examined, EntityUid examiner)
- {
- Examined = examined;
- Examiner = examiner;
- }
- }
- }
|