diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 16c1964505..b21bff8ec5 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -92,6 +92,7 @@ + diff --git a/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs b/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs new file mode 100644 index 0000000000..aad58b3e4a --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs @@ -0,0 +1,139 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Shared.GameObjects.EntitySystemMessages; +using Content.Shared.Input; +using JetBrains.Annotations; +using SS14.Client.GameObjects.EntitySystems; +using SS14.Client.Interfaces.GameObjects.Components; +using SS14.Client.Interfaces.Input; +using SS14.Client.Interfaces.UserInterface; +using SS14.Client.UserInterface; +using SS14.Client.UserInterface.Controls; +using SS14.Shared.GameObjects; +using SS14.Shared.GameObjects.Systems; +using SS14.Shared.Input; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Map; +using SS14.Shared.Maths; +using SS14.Shared.Players; + +namespace Content.Client.GameObjects.EntitySystems +{ + [UsedImplicitly] + internal sealed class ExamineSystem : EntitySystem + { + public const string StyleClassEntityTooltip = "entity-tooltip"; + +#pragma warning disable 649 + [Dependency] private IInputManager _inputManager; + [Dependency] private IUserInterfaceManager _userInterfaceManager; + [Dependency] private IEntityManager _entityManager; +#pragma warning restore 649 + + private Popup _examineTooltipOpen; + private CancellationTokenSource _requestCancelTokenSource; + + public override void Initialize() + { + IoCManager.InjectDependencies(this); + + var inputSys = EntitySystemManager.GetEntitySystem(); + inputSys.BindMap.BindFunction(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine)); + } + + public override void RegisterMessageTypes() + { + base.RegisterMessageTypes(); + + RegisterMessageType(); + } + + private void HandleExamine(ICommonSession session, GridCoordinates coords, EntityUid uid) + { + if (!uid.IsValid() || !_entityManager.TryGetEntity(uid, out var entity)) + { + return; + } + + DoExamine(entity); + } + + public async void DoExamine(IEntity entity) + { + CloseTooltip(); + + var mousePos = _inputManager.MouseScreenPosition; + + // Actually open the tooltip. + _examineTooltipOpen = new Popup(); + _userInterfaceManager.StateRoot.AddChild(_examineTooltipOpen); + var panel = new PanelContainer(); + panel.AddStyleClass(StyleClassEntityTooltip); + _examineTooltipOpen.AddChild(panel); + panel.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide); + var vBox = new VBoxContainer(); + panel.AddChild(vBox); + var hBox = new HBoxContainer { SeparationOverride = 5}; + vBox.AddChild(hBox); + if (entity.TryGetComponent(out ISpriteComponent sprite)) + { + hBox.AddChild(new SpriteView {Sprite = sprite}); + } + + hBox.AddChild(new Label + { + Text = entity.Name, + SizeFlagsHorizontal = Control.SizeFlags.FillExpand, + }); + + const float minWidth = 300; + var size = Vector2.ComponentMax((minWidth, 0), panel.CombinedMinimumSize); + _examineTooltipOpen.Open(UIBox2.FromDimensions(mousePos, size)); + + if (entity.Uid.IsClientSide()) + { + return; + } + + // Ask server for extra examine info. + RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(entity.Uid)); + + ExamineSystemMessages.ExamineInfoResponseMessage response; + try + { + _requestCancelTokenSource = new CancellationTokenSource(); + response = + await AwaitNetMessage(_requestCancelTokenSource + .Token); + } + catch (TaskCanceledException) + { + return; + } + finally + { + _requestCancelTokenSource = null; + } + + var richLabel = new RichTextLabel(); + richLabel.SetMessage(response.Message); + vBox.AddChild(richLabel); + } + + public void CloseTooltip() + { + if (_examineTooltipOpen != null) + { + _examineTooltipOpen.Dispose(); + _examineTooltipOpen = null; + } + + if (_requestCancelTokenSource != null) + { + _requestCancelTokenSource.Cancel(); + _requestCancelTokenSource = null; + } + } + } +} diff --git a/Content.Client/UserInterface/NanoStyle.cs b/Content.Client/UserInterface/NanoStyle.cs index 7762a1cc84..a235bc407f 100644 --- a/Content.Client/UserInterface/NanoStyle.cs +++ b/Content.Client/UserInterface/NanoStyle.cs @@ -1,3 +1,4 @@ +using Content.Client.GameObjects.EntitySystems; using Content.Client.Utility; using SS14.Client.Graphics.Drawing; using SS14.Client.Interfaces.ResourceManagement; @@ -131,6 +132,15 @@ namespace Content.Client.UserInterface var checkBoxTextureChecked = resCache.GetTexture("/Nano/checkbox_checked.svg.96dpi.png"); var checkBoxTextureUnchecked = resCache.GetTexture("/Nano/checkbox_unchecked.svg.96dpi.png"); + // Tooltip box + var tooltipTexture = resCache.GetTexture("/Nano/tooltip.png"); + var tooltipBox = new StyleBoxTexture + { + Texture = tooltipTexture, + }; + tooltipBox.SetPatchMargin(StyleBox.Margin.All, 2); + tooltipBox.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5); + Stylesheet = new Stylesheet(new[] { // Default font. @@ -337,7 +347,13 @@ namespace Content.Client.UserInterface // Tooltip new StyleRule(new SelectorElement(typeof(Tooltip), null, null, null), new [] { - new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = new Color(21, 21, 26)}) + new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox) + }), + + // Entity tooltip + new StyleRule(new SelectorElement(typeof(PanelContainer), new []{ExamineSystem.StyleClassEntityTooltip}, null, null), new [] + { + new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox) }), // ItemList @@ -357,6 +373,20 @@ namespace Content.Client.UserInterface { new StyleProperty(ItemList.StylePropertySelectedItemBackground, new StyleBoxFlat { BackgroundColor = new Color(75, 75, 86)}) }), + + // Tree + new StyleRule(new SelectorElement(typeof(Tree), null, null, null), new [] + { + new StyleProperty(Tree.StylePropertyBackground, new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40)}) + }), + new StyleRule(new SelectorElement(typeof(Tree), null, null, null), new [] + { + new StyleProperty(Tree.StylePropertyItemBoxSelected, new StyleBoxFlat + { + BackgroundColor = new Color(55, 55, 68), + ContentMarginLeftOverride = 4 + }) + }), }); } } diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index 0e2a7cda69..39f47be303 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -7,6 +7,7 @@ using SS14.Server.GameObjects.Components.Container; using SS14.Shared.Enums; using SS14.Shared.GameObjects; using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.Utility; using SS14.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Interactable @@ -53,11 +54,12 @@ namespace Content.Server.GameObjects.Components.Interactable return _cellContainer.Insert(eventArgs.AttackWith); } - string IExamine.Examine() + void IExamine.Examine(FormattedMessage message) { - if (Activated) return "The light is currently on."; - - return null; + if (Activated) + { + message.AddText("The light is currently on."); + } } bool IUse.UseEntity(UseEntityEventArgs eventArgs) diff --git a/Content.Server/GameObjects/Components/Interactable/Tools/WelderComponent.cs b/Content.Server/GameObjects/Components/Interactable/Tools/WelderComponent.cs index 6e44644212..45b1239d61 100644 --- a/Content.Server/GameObjects/Components/Interactable/Tools/WelderComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/Tools/WelderComponent.cs @@ -1,9 +1,11 @@ using System; +using System.Text; using SS14.Shared.Interfaces.GameObjects; using SS14.Shared.Utility; using YamlDotNet.RepresentationModel; using SS14.Server.GameObjects; using Content.Server.GameObjects.EntitySystems; +using SS14.Shared.Maths; using SS14.Shared.Serialization; using SS14.Shared.ViewVariables; @@ -145,13 +147,22 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools } } - string IExamine.Examine() + void IExamine.Examine(FormattedMessage message) { if (Activated) { - return "The welding tool is currently lit"; + message.PushColor(Color.Orange); + message.AddText("Lit\n"); + message.Pop(); } - return null; + else + { + message.AddText("Not lit\n"); + } + message.AddText("Fuel: "); + message.PushColor(Fuel < FuelCapacity / 4f ? Color.DarkOrange : Color.Orange); + message.AddText($"{Math.Round(Fuel)}/{FuelCapacity}"); + message.Pop(); } } } diff --git a/Content.Server/GameObjects/Components/Power/PowerDevice.cs b/Content.Server/GameObjects/Components/Power/PowerDevice.cs index e2b56703e8..74c61c557a 100644 --- a/Content.Server/GameObjects/Components/Power/PowerDevice.cs +++ b/Content.Server/GameObjects/Components/Power/PowerDevice.cs @@ -181,13 +181,12 @@ namespace Content.Server.GameObjects.Components.Power serializer.DataField(ref _priority, "priority", Powernet.Priority.Medium); } - string IExamine.Examine() + void IExamine.Examine(FormattedMessage message) { if (!Powered) { - return "The device is not powered"; + message.AddText("The device is not powered"); } - return null; } private void UpdateLoad(float value) diff --git a/Content.Server/GameObjects/Components/Stack/StackComponent.cs b/Content.Server/GameObjects/Components/Stack/StackComponent.cs index 8cb276d9f9..d0d8dae690 100644 --- a/Content.Server/GameObjects/Components/Stack/StackComponent.cs +++ b/Content.Server/GameObjects/Components/Stack/StackComponent.cs @@ -4,6 +4,7 @@ using SS14.Shared.GameObjects; using SS14.Shared.Interfaces.Reflection; using SS14.Shared.IoC; using SS14.Shared.Serialization; +using SS14.Shared.Utility; using SS14.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Stack @@ -113,9 +114,9 @@ namespace Content.Server.GameObjects.Components.Stack return false; } - public string Examine() + void IExamine.Examine(FormattedMessage message) { - return $"There are {Count} things in the stack."; + message.AddText($"There are {Count} things in the stack."); } } diff --git a/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs index 7c24ef48d2..c932773110 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using Content.Shared.GameObjects.EntitySystemMessages; using Content.Shared.Input; using SS14.Server.GameObjects.EntitySystems; using SS14.Server.Interfaces.Chat; @@ -7,11 +8,15 @@ using SS14.Server.Interfaces.Player; using SS14.Shared.GameObjects; using SS14.Shared.GameObjects.Systems; using SS14.Shared.Input; +using SS14.Shared.Interfaces.GameObjects; using SS14.Shared.Interfaces.GameObjects.Components; +using SS14.Shared.Interfaces.Network; using SS14.Shared.IoC; using SS14.Shared.Log; using SS14.Shared.Map; +using SS14.Shared.Maths; using SS14.Shared.Players; +using SS14.Shared.Utility; namespace Content.Server.GameObjects.EntitySystems { @@ -20,63 +25,89 @@ namespace Content.Server.GameObjects.EntitySystems /// /// Returns an status examine value for components appended to the end of the description of the entity /// - /// - string Examine(); + void Examine(FormattedMessage message); } public class ExamineSystem : EntitySystem { - /// - public override void Initialize() +#pragma warning disable 649 + [Dependency] private IEntityManager _entityManager; +#pragma warning restore 649 + + private static readonly FormattedMessage _entityNotFoundMessage; + + static ExamineSystem() { - var inputSys = EntitySystemManager.GetEntitySystem(); - inputSys.BindMap.BindFunction(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine)); + _entityNotFoundMessage = new FormattedMessage(); + _entityNotFoundMessage.AddText("That entity doesn't exist"); } - private void HandleExamine(ICommonSession session, GridCoordinates coords, EntityUid uid) + public override void Initialize() { - if (!(session is IPlayerSession svSession)) - return; + base.Initialize(); - var playerEnt = svSession.AttachedEntity; - if (!EntityManager.TryGetEntity(uid, out var examined)) - return; + IoCManager.InjectDependencies(this); + } - //Verify player has a transform component - if (!playerEnt.TryGetComponent(out var playerTransform)) - { - return; - } + public override void RegisterMessageTypes() + { + base.RegisterMessageTypes(); - //Verify player is on the same map as the entity he clicked on - if (coords.MapID != playerTransform.MapID) - { - Logger.WarningS("sys.examine", $"Player named {session.Name} clicked on a map he isn't located on"); - return; - } + RegisterMessageType(); + } - //Start a StringBuilder since we have no idea how many times this could be appended to - var fullExamineText = new StringBuilder("This is " + examined.Name); + private FormattedMessage GetExamineText(IEntity entity) + { + var message = new FormattedMessage(); + + var doNewline = false; //Add an entity description if one is declared - if (!string.IsNullOrEmpty(examined.Description)) + if (!string.IsNullOrEmpty(entity.Description)) { - fullExamineText.Append(Environment.NewLine + examined.Description); + message.AddText(entity.Description); + doNewline = true; } + message.PushColor(Color.DarkGray); + + var subMessage = new FormattedMessage(); //Add component statuses from components that report one - foreach (var examineComponents in examined.GetAllComponents()) + foreach (var examineComponents in entity.GetAllComponents()) { - var componentDescription = examineComponents.Examine(); - if (string.IsNullOrEmpty(componentDescription)) + examineComponents.Examine(subMessage); + if (subMessage.Tags.Count == 0) continue; - fullExamineText.Append(Environment.NewLine); - fullExamineText.Append(componentDescription); + if (doNewline) + { + message.AddText("\n"); + doNewline = false; + } + message.AddMessage(subMessage); } - //Send to client chat channel - IoCManager.Resolve().DispatchMessage(svSession.ConnectedClient, SS14.Shared.Console.ChatChannel.Visual, fullExamineText.ToString(), session.SessionId); + message.Pop(); + + return message; + } + + public override void HandleNetMessage(INetChannel channel, EntitySystemMessage message) + { + base.HandleNetMessage(channel, message); + + if (message is ExamineSystemMessages.RequestExamineInfoMessage request) + { + if (!_entityManager.TryGetEntity(request.EntityUid, out var entity)) + { + RaiseNetworkEvent(new ExamineSystemMessages.ExamineInfoResponseMessage( + request.EntityUid, _entityNotFoundMessage)); + return; + } + + var text = GetExamineText(entity); + RaiseNetworkEvent(new ExamineSystemMessages.ExamineInfoResponseMessage(request.EntityUid, text)); + } } } } diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index a8ec723674..62021de942 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -81,6 +81,7 @@ + diff --git a/Content.Shared/GameObjects/EntitySystemMessages/ExamineSystemMessages.cs b/Content.Shared/GameObjects/EntitySystemMessages/ExamineSystemMessages.cs new file mode 100644 index 0000000000..e08061e0ed --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystemMessages/ExamineSystemMessages.cs @@ -0,0 +1,34 @@ +using System; +using SS14.Shared.GameObjects; +using SS14.Shared.Serialization; +using SS14.Shared.Utility; + +namespace Content.Shared.GameObjects.EntitySystemMessages +{ + public static class ExamineSystemMessages + { + [Serializable, NetSerializable] + public class RequestExamineInfoMessage : EntitySystemMessage + { + public readonly EntityUid EntityUid; + + public RequestExamineInfoMessage(EntityUid entityUid) + { + EntityUid = entityUid; + } + } + + [Serializable, NetSerializable] + public class ExamineInfoResponseMessage : EntitySystemMessage + { + public readonly EntityUid EntityUid; + public readonly FormattedMessage Message; + + public ExamineInfoResponseMessage(EntityUid entityUid, FormattedMessage message) + { + EntityUid = entityUid; + Message = message; + } + } + } +} diff --git a/Resources/Nano/tooltip.png b/Resources/Nano/tooltip.png new file mode 100644 index 0000000000..eadfc72e37 Binary files /dev/null and b/Resources/Nano/tooltip.png differ diff --git a/RobustToolbox b/RobustToolbox index e40cda6c99..4b663fd508 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit e40cda6c990eb1c42c4e5422155c45ab7dd84845 +Subproject commit 4b663fd508bed17d5af78e4274ed93740d139561