* Content changes for engine delta-state PR (#28134) * Update GasTileOverlayState * Update DecalGridState * Update NavMapState * poke * poke2 * poke3 * Poke dem tests * Update engine to v223.0.0 (#28239) * Update RobustToolbox * Improve InteractionSystem range & BUI checks (#27999) * Improve InteractionSystem range & BUI checks * Ghost fixes * AAA * Fix test * fix nullable * revert to broadcast event * Fixes for eengine PR * Ah buckle code * ) * Update engine to v223.0.0 * Update engine to v223.1.0 * Update engine to v223.1.1 * Hotfix for crashes from bad item names (#28256) * Fix weapon error logs (#28264) * Update engine to v223.1.2 (#28273) * Update RobustToolbox * Update RobustToolbox * Fix dud modular grenade visuals (#28265) * Fix not networking whitelist and blacklist in storage component (#28238) * fix id card console not updating records (#28237) * fix id card console not updating records * test --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * Remove the network tab (#28231) It is useless and bloat, if a user needs to change these settings they are free to modify their cvars manually via the clientconfig.toml file or via the cvar command. * antag objective issuing refactor (#28216) * add AntagObjectives from GenericAntag * add AntagRandomObjectives that traitor and thief can use * make ObjectivesSystem use initial character name which AntagSelection passes * make thief and traitor use AntagRandomObjectives * remove now unused locale * make sleeper agents rule use baseTraitorRule * restore dragon rule oop * bandaid for genericantag * real * typo --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * move nukie profile loading into its own rule (#28208) * move profile loading out of nukeops rule * make BaseNukeopsRule and use AntagLoadProfileRule * untroll --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * fix antagSelect * Objects ordered through cargo system shouldn't start anchored (#28115) * Order normal space heater instead of anchored variant * Make sure ordered objects don't spawn anchored * Order space heater flatpack instead of a regular space heater * Remove obsolete TODO * Remove unnecessary name --------- Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Move PendingZombieComponent to Shared (#28143) * Move PendingZombieComponent to Shared * network me boy --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> * Make it possible to hide full health bars below a total damage threshold (#28127) * Make it possible to hide full health bars below a total damage threshold * Fix not setting state * Fix storage UI interactions (#28291) * Fix storage UI interactions * Add VV support * Fix stripping not marking interactions as handled (#28292) * Make NetworkConfigurator use BoundUserInterfaceCheckRangeEvent (#28293) * Fix hypodarts not injecting with people that have ANY outerclothing (#28301) Update darts.yml * fix borg ui mispredict opening (#28305) move borg ui junk to shared * Add loadout group check (#28311) Forgot to add it back in one of the rewrites. * fix mirror server crashes (#28318) * Remove bogus C# finalizers (#28315) Begging people to learn how this programming language works before throwing random syntax into a file. None of these finalizers ever worked. I also checked whether they were memory leaks and needed *proper* shutdown logic, but they're all instantiated-once UI controls that last for the entire lifetime of the program so it's probably fine. * Cleans up some entity-related spawnmenu stuff (#28234) * cleans up a lot of stuff in the spawnmenu * skibidi dode * spawners update * Revert "spawners update" This reverts commit bc27d9f556b29f6fb1f89cebbe0ac37e28319fd0. * fix antag selection being evil (#28197) * fix antag selection being evil * fix test * untroll the other tests * remove role timer troll * Allow tests to modify antag preferences * Fix antag selection * Misc test fixes * Add AntagPreferenceTest * Fix lazy mistakes * Test cleanup * Try stop players in lobbies from being assigned mid-round antags * ranting * I am going insane --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com> * Revert "fix mirror server crashes (#28318)" This reverts commit bcb0e555b058a4049d0cdb32d64eaf86c35a67be. * fix * Update engine to 223.2.0 (#28329) * Update RobustToolbox * Update RobustToolbox --------- Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: Vasilis <vasilis@pikachu.systems> Co-authored-by: eoineoineoin <github@eoinrul.es> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> Co-authored-by: Ady4ik <141335742+Ady4ik@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com> Co-authored-by: Kara <lunarautomaton6@gmail.com>
433 lines
16 KiB
C#
433 lines
16 KiB
C#
using Content.Client.Verbs;
|
|
using Content.Shared.Eye.Blinding;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.Input;
|
|
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 System.Linq;
|
|
using System.Numerics;
|
|
using System.Threading;
|
|
using Content.Shared.Eye.Blinding.Components;
|
|
using Robust.Client;
|
|
using static Content.Shared.Interaction.SharedInteractionSystem;
|
|
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
|
using Content.Shared.Interaction.Events;
|
|
using Content.Shared.Item;
|
|
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 EntityUid _playerEntity;
|
|
private Popup? _examineTooltipOpen;
|
|
private ScreenCoordinates _popupPos;
|
|
private CancellationTokenSource? _requestCancelTokenSource;
|
|
private int _idCounter;
|
|
|
|
public override void 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 || !_playerEntity.Valid) return;
|
|
|
|
if (!CanExamine(_playerEntity, _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;
|
|
}
|
|
|
|
_playerEntity = _playerManager.LocalEntity ?? default;
|
|
|
|
if (_playerEntity == default || !CanExamine(_playerEntity, 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)
|
|
{
|
|
// TODO: FormattedMessage.RemoveMarkupPermissive
|
|
// var itemName = FormattedMessage.RemoveMarkupPermissive(Identity.Name(target, EntityManager, player));
|
|
var itemName = FormattedMessage.FromMarkupPermissive(Identity.Name(target, EntityManager, player)).ToString();
|
|
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.FromMarkup("[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;
|
|
|
|
// Basically this just predicts that we can't make out the entity if we have poor vision.
|
|
var canSeeClearly = !HasComp<BlurryVisionComponent>(playerEnt);
|
|
|
|
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly);
|
|
|
|
// 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;
|
|
}
|
|
|
|
public 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;
|
|
}
|
|
}
|
|
}
|