diff --git a/Content.Client/Atmos/AtmosDebugOverlay.cs b/Content.Client/Atmos/AtmosDebugOverlay.cs index 728257d448..c0987545c1 100644 --- a/Content.Client/Atmos/AtmosDebugOverlay.cs +++ b/Content.Client/Atmos/AtmosDebugOverlay.cs @@ -28,9 +28,9 @@ namespace Content.Client.Atmos _atmosDebugOverlaySystem = EntitySystem.Get(); } - protected override void Draw(DrawingHandleBase handle, OverlaySpace overlay) + protected override void Draw(in OverlayDrawArgs args) { - var drawHandle = (DrawingHandleWorld) handle; + var drawHandle = args.WorldHandle; var mapId = _eyeManager.CurrentMap; var eye = _eyeManager.CurrentEye; diff --git a/Content.Client/Atmos/GasTileOverlay.cs b/Content.Client/Atmos/GasTileOverlay.cs index 296afc6527..30e31ef0c2 100644 --- a/Content.Client/Atmos/GasTileOverlay.cs +++ b/Content.Client/Atmos/GasTileOverlay.cs @@ -25,9 +25,9 @@ namespace Content.Client.Atmos _gasTileOverlaySystem = EntitySystem.Get(); } - protected override void Draw(DrawingHandleBase handle, OverlaySpace overlay) + protected override void Draw(in OverlayDrawArgs args) { - var drawHandle = (DrawingHandleWorld) handle; + var drawHandle = args.WorldHandle; var mapId = _eyeManager.CurrentMap; var eye = _eyeManager.CurrentEye; @@ -41,7 +41,7 @@ namespace Content.Client.Atmos continue; var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight)); - + foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds)) { foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices)) diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index 1828775f23..9c72a54940 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -47,6 +47,7 @@ namespace Content.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 23eb13d10e..247b304213 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -33,6 +33,7 @@ using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; +using Robust.Client.UserInterface; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -100,6 +101,7 @@ namespace Content.Client IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); IoCManager.InjectDependencies(this); @@ -183,6 +185,9 @@ namespace Content.Client } }; + // Disable engine-default viewport since we use our own custom viewport control. + IoCManager.Resolve().MainViewport.Visible = false; + SwitchToDefaultState(); } diff --git a/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs b/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs index d192de5a97..fc944474ef 100644 --- a/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs +++ b/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs @@ -11,28 +11,23 @@ namespace Content.Client.GameObjects.Components { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private const float DefaultWidth = 1; private const string ShaderInRange = "SelectionOutlineInrange"; private const string ShaderOutOfRange = "SelectionOutline"; public override string Name => "InteractionOutline"; - private ShaderInstance? _selectionShaderInstance; - private ShaderInstance? _selectionShaderInRangeInstance; + private bool _inRange; + private ShaderInstance? _shader; + private int _lastRenderScale; - /// - public override void Initialize() - { - base.Initialize(); - - _selectionShaderInRangeInstance = _prototypeManager.Index(ShaderInRange).Instance(); - _selectionShaderInstance = _prototypeManager.Index(ShaderOutOfRange).Instance(); - } - - public void OnMouseEnter(bool inInteractionRange) + public void OnMouseEnter(bool inInteractionRange, int renderScale) { + _lastRenderScale = renderScale; + _inRange = inInteractionRange; if (Owner.TryGetComponent(out ISpriteComponent? sprite)) { - sprite.PostShader = inInteractionRange ? _selectionShaderInRangeInstance : _selectionShaderInstance; + sprite.PostShader = MakeNewShader(inInteractionRange, renderScale); sprite.RenderOrder = Owner.EntityManager.CurrentTick.Value; } } @@ -44,14 +39,30 @@ namespace Content.Client.GameObjects.Components sprite.PostShader = null; sprite.RenderOrder = 0; } + + _shader?.Dispose(); + _shader = null; } - public void UpdateInRange(bool inInteractionRange) + public void UpdateInRange(bool inInteractionRange, int renderScale) { - if (Owner.TryGetComponent(out ISpriteComponent? sprite)) + if (Owner.TryGetComponent(out ISpriteComponent? sprite) + && (inInteractionRange != _inRange || _lastRenderScale != renderScale)) { - sprite.PostShader = inInteractionRange ? _selectionShaderInRangeInstance : _selectionShaderInstance; + _inRange = inInteractionRange; + _lastRenderScale = renderScale; + _shader = MakeNewShader(_inRange, _lastRenderScale); + sprite.PostShader = _shader; } } + + private ShaderInstance MakeNewShader(bool inRange, int renderScale) + { + var shaderName = inRange ? ShaderInRange : ShaderOutOfRange; + + var instance = _prototypeManager.Index(shaderName).InstanceUnique(); + instance.SetParameter("outline_width", DefaultWidth * renderScale); + return instance; + } } } diff --git a/Content.Client/GameObjects/Components/Suspicion/TraitorOverlay.cs b/Content.Client/GameObjects/Components/Suspicion/TraitorOverlay.cs index 1c29cc2872..ffb6934382 100644 --- a/Content.Client/GameObjects/Components/Suspicion/TraitorOverlay.cs +++ b/Content.Client/GameObjects/Components/Suspicion/TraitorOverlay.cs @@ -35,17 +35,7 @@ namespace Content.Client.GameObjects.Components.Suspicion _font = new VectorFont(resourceCache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) - { - switch (currentSpace) - { - case OverlaySpace.ScreenSpace: - DrawScreen((DrawingHandleScreen) handle); - break; - } - } - - private void DrawScreen(DrawingHandleScreen screen) + protected override void Draw(in OverlayDrawArgs args) { var viewport = _eyeManager.GetWorldViewport(); @@ -91,8 +81,8 @@ namespace Content.Client.GameObjects.Components.Suspicion continue; } - var screenCoordinates = _eyeManager.WorldToScreen(physics.GetWorldAABB().TopLeft + (0, 0.5f)); - DrawString(screen, _font, screenCoordinates, _traitorText, Color.OrangeRed); + var screenCoordinates = args.ViewportControl!.WorldToScreen(physics.GetWorldAABB().TopLeft + (0, 0.5f)); + DrawString(args.ScreenHandle, _font, screenCoordinates, _traitorText, Color.OrangeRed); } } diff --git a/Content.Client/GameObjects/EntitySystems/AI/ClientPathfindingDebugSystem.cs b/Content.Client/GameObjects/EntitySystems/AI/ClientPathfindingDebugSystem.cs index e02cfee438..492407dfb7 100644 --- a/Content.Client/GameObjects/EntitySystems/AI/ClientPathfindingDebugSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/AI/ClientPathfindingDebugSystem.cs @@ -464,15 +464,15 @@ namespace Content.Client.GameObjects.EntitySystems.AI #endregion - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { if (Modes == 0) { return; } - handle.UseShader(_shader); - var screenHandle = (DrawingHandleScreen) handle; + var screenHandle = args.ScreenHandle; + screenHandle.UseShader(_shader); var viewport = _eyeManager.GetWorldViewport(); if ((Modes & PathfindingDebugMode.Route) != 0) diff --git a/Content.Client/GameObjects/EntitySystems/ContextMenuPresenter.cs b/Content.Client/GameObjects/EntitySystems/ContextMenuPresenter.cs index 87279a23c6..11d4063f4f 100644 --- a/Content.Client/GameObjects/EntitySystems/ContextMenuPresenter.cs +++ b/Content.Client/GameObjects/EntitySystems/ContextMenuPresenter.cs @@ -10,6 +10,7 @@ using Content.Shared; using Content.Shared.GameObjects.Verbs; using Content.Shared.Input; using Robust.Client.GameObjects; +using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; @@ -35,6 +36,7 @@ namespace Content.Client.GameObjects.EntitySystems [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; private readonly IContextMenuView _contextMenuView; private readonly VerbSystem _verbSystem; @@ -152,7 +154,14 @@ namespace Content.Client.GameObjects.EntitySystems { var inRange = localPlayer.InRangeUnobstructed(e.ContextEntity, ignoreInsideBlocker: true); - e.OutlineComponent?.UpdateInRange(inRange); + + // BUG: This assumes that the main viewport is the viewport that the context menu is active on. + // This is not necessarily true but we currently have no way to find the viewport (reliably) + // from the input event. + // + // This might be particularly important in the future with a more advanced mapping mode. + var renderScale = _eyeManager.MainViewport.GetRenderScale(); + e.OutlineComponent?.UpdateInRange(inRange, renderScale); } } @@ -170,7 +179,8 @@ namespace Content.Client.GameObjects.EntitySystems var localPlayer = _playerManager.LocalPlayer; if (localPlayer?.ControlledEntity == null) return; - e.OutlineComponent?.OnMouseEnter(localPlayer.InRangeUnobstructed(entity, ignoreInsideBlocker: true)); + var renderScale = _eyeManager.MainViewport.GetRenderScale(); + e.OutlineComponent?.OnMouseEnter(localPlayer.InRangeUnobstructed(entity, ignoreInsideBlocker: true), renderScale); if (e.SpriteComp != null) { e.SpriteComp.DrawDepth = (int) Shared.GameObjects.DrawDepth.HighlightedItems; diff --git a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs index 580d7b90d8..0060230f23 100644 --- a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs +++ b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs @@ -22,12 +22,14 @@ namespace Content.Client.Graphics.Overlays _shader = _prototypeManager.Index("CircleMask").Instance(); } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { if (!CritOverlay.LocalPlayerHasState(_playerManager, false, true)) return; - handle.UseShader(_shader); - var worldHandle = (DrawingHandleWorld)handle; + + + var worldHandle = args.WorldHandle; + worldHandle.UseShader(_shader); var viewport = _eyeManager.GetWorldViewport(); worldHandle.DrawRect(viewport, Color.White); } diff --git a/Content.Client/Graphics/Overlays/ColoredScreenBorderOverlay.cs b/Content.Client/Graphics/Overlays/ColoredScreenBorderOverlay.cs index da75dc46d2..e7c28fb1c2 100644 --- a/Content.Client/Graphics/Overlays/ColoredScreenBorderOverlay.cs +++ b/Content.Client/Graphics/Overlays/ColoredScreenBorderOverlay.cs @@ -22,10 +22,10 @@ namespace Content.Client.Graphics.Overlays _shader = _prototypeManager.Index("ColoredScreenBorder").Instance(); } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { - handle.UseShader(_shader); - var worldHandle = (DrawingHandleWorld)handle; + var worldHandle = args.WorldHandle; + worldHandle.UseShader(_shader); var viewport = _eyeManager.GetWorldViewport(); worldHandle.DrawRect(viewport, Color.White); } diff --git a/Content.Client/Graphics/Overlays/CritOverlay.cs b/Content.Client/Graphics/Overlays/CritOverlay.cs index d6fbbb881a..efccb878fe 100644 --- a/Content.Client/Graphics/Overlays/CritOverlay.cs +++ b/Content.Client/Graphics/Overlays/CritOverlay.cs @@ -45,14 +45,14 @@ namespace Content.Client.Graphics.Overlays return false; } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { if (!LocalPlayerHasState(_playerManager, true, false)) return; - var worldHandle = (DrawingHandleWorld) handle; + var worldHandle = args.WorldHandle; var viewport = _eyeManager.GetWorldViewport(); - handle.UseShader(_gradientCircleShader); + worldHandle.UseShader(_gradientCircleShader); worldHandle.DrawRect(viewport, Color.White); } } diff --git a/Content.Client/Graphics/Overlays/FlashOverlay.cs b/Content.Client/Graphics/Overlays/FlashOverlay.cs index 5316ebe465..89c8383294 100644 --- a/Content.Client/Graphics/Overlays/FlashOverlay.cs +++ b/Content.Client/Graphics/Overlays/FlashOverlay.cs @@ -1,4 +1,6 @@ +using Content.Client.State; using Robust.Client.Graphics; +using Robust.Client.State; using Robust.Shared.Enums; using Robust.Shared.IoC; using Robust.Shared.Maths; @@ -14,6 +16,7 @@ namespace Content.Client.Graphics.Overlays [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClyde _displayManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IStateManager _stateManager = default!; public override OverlaySpace Space => OverlaySpace.ScreenSpace; private readonly ShaderInstance _shader; @@ -29,29 +32,34 @@ namespace Content.Client.Graphics.Overlays public void ReceiveFlash(double duration) { - _displayManager.Screenshot(ScreenshotType.BeforeUI, image => + if (_stateManager.CurrentState is IMainViewportState state) { - var rgba32Image = image.CloneAs(Configuration.Default); - _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); - }); + state.Viewport.Viewport.Screenshot(image => + { + var rgba32Image = image.CloneAs(Configuration.Default); + _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); + }); + } + _startTime = _gameTiming.CurTime.TotalSeconds; _lastsFor = duration; } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor); if (percentComplete >= 1.0f) return; - handle.UseShader(_shader); - _shader?.SetParameter("percentComplete", percentComplete); - var screenSpaceHandle = handle as DrawingHandleScreen; + var screenSpaceHandle = args.ScreenHandle; + screenSpaceHandle.UseShader(_shader); + _shader.SetParameter("percentComplete", percentComplete); + var screenSize = UIBox2.FromDimensions((0, 0), _displayManager.ScreenSize); if (_screenshotTexture != null) { - screenSpaceHandle?.DrawTextureRect(_screenshotTexture, screenSize); + screenSpaceHandle.DrawTextureRect(_screenshotTexture, screenSize); } } diff --git a/Content.Client/Graphics/Overlays/SingularityOverlay.cs b/Content.Client/Graphics/Overlays/SingularityOverlay.cs index eac861eb4c..263100a85f 100644 --- a/Content.Client/Graphics/Overlays/SingularityOverlay.cs +++ b/Content.Client/Graphics/Overlays/SingularityOverlay.cs @@ -38,7 +38,7 @@ namespace Content.Client.Graphics.Overlays return _singularities.Count() > 0; } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { SingularityQuery(); @@ -52,8 +52,8 @@ namespace Content.Client.Graphics.Overlays _shader?.SetParameter("intensity", LevelToIntensity(instance.Level)); _shader?.SetParameter("falloff", LevelToFalloff(instance.Level)); - handle.UseShader(_shader); - var worldHandle = (DrawingHandleWorld) handle; + var worldHandle = args.WorldHandle; + worldHandle.UseShader(_shader); var viewport = _eyeManager.GetWorldViewport(); worldHandle.DrawRect(viewport, Color.White); } diff --git a/Content.Client/Parallax/ParallaxOverlay.cs b/Content.Client/Parallax/ParallaxOverlay.cs index e3c41019b0..5feedc4409 100644 --- a/Content.Client/Parallax/ParallaxOverlay.cs +++ b/Content.Client/Parallax/ParallaxOverlay.cs @@ -1,3 +1,4 @@ +using System; using Content.Client.Interfaces.Parallax; using Robust.Client.Graphics; using Robust.Shared.Enums; @@ -18,7 +19,7 @@ namespace Content.Client.Parallax private Texture? _parallaxTexture; - public override OverlaySpace Space => OverlaySpace.ScreenSpaceBelowWorld; + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld; private readonly ShaderInstance _shader; public ParallaxOverlay() @@ -36,26 +37,36 @@ namespace Content.Client.Parallax } } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { - if (_parallaxTexture == null) + if (_parallaxTexture == null || args.Viewport.Eye == null) { return; } - handle.UseShader(_shader); - var screenHandle = (DrawingHandleScreen) handle; + var screenHandle = args.WorldHandle; + screenHandle.UseShader(_shader); - var (sizeX, sizeY) = _parallaxTexture.Size; - var (posX, posY) = _eyeManager.ScreenToMap(Vector2.Zero).Position; - var (ox, oy) = (Vector2i) new Vector2(-posX / Slowness, posY / Slowness); - ox = MathHelper.Mod(ox, sizeX); - oy = MathHelper.Mod(oy, sizeY); + var (sizeX, sizeY) = _parallaxTexture.Size / (float) EyeManager.PixelsPerMeter; + var (posX, posY) = args.Viewport.Eye.Position; + var o = new Vector2(posX * Slowness, posY * Slowness); - var (screenSizeX, screenSizeY) = _displayManager.ScreenSize; - for (var x = -sizeX; x < screenSizeX; x += sizeX) { - for (var y = -sizeY; y < screenSizeY; y += sizeY) { - screenHandle.DrawTexture(_parallaxTexture, new Vector2(ox + x, oy + y)); + // Remove offset so we can floor. + var (l, b) = args.WorldBounds.BottomLeft - o; + + // Floor to background size. + l = sizeX * MathF.Floor(l / sizeX); + b = sizeY * MathF.Floor(b / sizeY); + + // Re-offset. + l += o.X; + b += o.Y; + + for (var x = l; x < args.WorldBounds.Right; x += sizeX) + { + for (var y = b; y < args.WorldBounds.Top; y += sizeY) + { + screenHandle.DrawTexture(_parallaxTexture, (x, y)); } } } diff --git a/Content.Client/ScreenshotHook.cs b/Content.Client/ScreenshotHook.cs index 56fc7560f3..2bf3c8b78d 100644 --- a/Content.Client/ScreenshotHook.cs +++ b/Content.Client/ScreenshotHook.cs @@ -1,15 +1,18 @@ using System; using System.IO; using System.Threading.Tasks; +using Content.Client.State; using Content.Shared.Input; using Robust.Client.Graphics; using Robust.Client.Input; +using Robust.Client.State; using Robust.Shared.ContentPack; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace Content.Client { @@ -20,24 +23,30 @@ namespace Content.Client [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IResourceManager _resourceManager = default!; + [Dependency] private readonly IStateManager _stateManager = default!; public void Initialize() { _inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshot, InputCmdHandler.FromDelegate(_ => { - Take(ScreenshotType.AfterUI); + _clyde.Screenshot(ScreenshotType.Final, Take); })); _inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshotNoUI, InputCmdHandler.FromDelegate(_ => { - Take(ScreenshotType.BeforeUI); + if (_stateManager.CurrentState is IMainViewportState state) + { + state.Viewport.Viewport.Screenshot(Take); + } + else + { + Logger.InfoS("screenshot", "Can't take no-UI screenshot: current state is not GameScreen"); + } })); } - private async void Take(ScreenshotType type) + private async void Take(Image screenshot) where T : unmanaged, IPixel { - var screenshot = await _clyde.ScreenshotAsync(type); - var time = DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss"); if (!_resourceManager.UserData.IsDir(BaseScreenshotPath)) diff --git a/Content.Client/State/GameScreen.cs b/Content.Client/State/GameScreen.cs index 6aae189100..291c0e66da 100644 --- a/Content.Client/State/GameScreen.cs +++ b/Content.Client/State/GameScreen.cs @@ -6,6 +6,7 @@ using Content.Client.UserInterface; using Content.Client.Voting; using Content.Shared; using Content.Shared.Input; +using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -13,12 +14,16 @@ using Robust.Shared.Configuration; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Timing; using Robust.Shared.ViewVariables; namespace Content.Client.State { - public class GameScreen : GameScreenBase + public class GameScreen : GameScreenBase, IMainViewportState { + public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15); + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IInputManager _inputManager = default!; @@ -26,6 +31,8 @@ namespace Content.Client.State [Dependency] private readonly IVoteManager _voteManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IClientAdminManager _adminManager = default!; + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; [ViewVariables] private ChatBox? _gameChat; private ConstructionMenuPresenter? _constructionMenu; @@ -33,15 +40,27 @@ namespace Content.Client.State private bool _oocEnabled; private bool _adminOocEnabled; + public MainViewport Viewport { get; private set; } = default!; + public override void Startup() { base.Startup(); _gameChat = new ChatBox(); + Viewport = new MainViewport + { + Viewport = + { + ViewportSize = ViewportSize + } + }; + + _userInterfaceManager.StateRoot.AddChild(Viewport); + LayoutContainer.SetAnchorPreset(Viewport, LayoutContainer.LayoutPreset.Wide); + Viewport.SetPositionFirst(); _userInterfaceManager.StateRoot.AddChild(_gameChat); LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10); - LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10); LayoutContainer.SetMarginLeft(_gameChat, -475); LayoutContainer.SetMarginBottom(_gameChat, 235); @@ -65,6 +84,8 @@ namespace Content.Client.State _adminManager.AdminStatusUpdated += OnAdminStatusUpdated; SetupPresenters(); + + _eyeManager.MainViewport = Viewport.Viewport; } public override void Shutdown() @@ -74,7 +95,10 @@ namespace Content.Client.State base.Shutdown(); _gameChat?.Dispose(); + Viewport.Dispose(); _gameHud.RootControl.Orphan(); + // Clear viewport to some fallback, whatever. + _eyeManager.MainViewport = _userInterfaceManager.MainViewport; } @@ -168,5 +192,12 @@ namespace Content.Client.State chat.Input.GrabKeyboardFocus(); chat.Input.InsertAtCursor("]"); } + + public override void FrameUpdate(FrameEventArgs e) + { + base.FrameUpdate(e); + + Viewport.Viewport.Eye = _eyeManager.CurrentEye; + } } } diff --git a/Content.Client/State/GameScreenBase.cs b/Content.Client/State/GameScreenBase.cs index 448fdcc38c..f9b6883c55 100644 --- a/Content.Client/State/GameScreenBase.cs +++ b/Content.Client/State/GameScreenBase.cs @@ -2,14 +2,15 @@ using System.Collections.Immutable; using System.Linq; using Content.Client.GameObjects.Components; +using Content.Client.UserInterface; using Content.Client.Utility; using Content.Shared; using Robust.Client.GameObjects; -using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Input; @@ -28,7 +29,6 @@ namespace Content.Client.State [Dependency] protected readonly IClientEntityManager EntityManager = default!; [Dependency] protected readonly IInputManager InputManager = default!; [Dependency] protected readonly IPlayerManager PlayerManager = default!; - [Dependency] protected readonly IEyeManager EyeManager = default!; [Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!; [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] protected readonly IMapManager MapManager = default!; @@ -68,10 +68,18 @@ namespace Content.Client.State if (localPlayer == null) return; - var mousePosWorld = EyeManager.ScreenToMap(InputManager.MouseScreenPosition); - var entityToClick = UserInterfaceManager.CurrentlyHovered != null - ? null - : GetEntityUnderPosition(mousePosWorld); + IEntity? entityToClick = null; + var renderScale = 1; + if (UserInterfaceManager.CurrentlyHovered is IViewportControl vp) + { + var mousePosWorld = vp.ScreenToMap(InputManager.MouseScreenPosition); + entityToClick = GetEntityUnderPosition(mousePosWorld); + + if (vp is ScalingViewport svp) + { + renderScale = svp.CurrentRenderScale; + } + } var inRange = false; if (localPlayer.ControlledEntity != null && entityToClick != null) @@ -93,7 +101,7 @@ namespace Content.Client.State { if (entityToClick != null && entityToClick.TryGetComponent(out outline)) { - outline.UpdateInRange(inRange); + outline.UpdateInRange(inRange, renderScale); } return; @@ -109,7 +117,7 @@ namespace Content.Client.State if (_lastHoveredEntity != null && _lastHoveredEntity.TryGetComponent(out outline)) { - outline.OnMouseEnter(inRange); + outline.OnMouseEnter(inRange, renderScale); } } @@ -206,30 +214,36 @@ namespace Content.Client.State /// Converts a state change event from outside the simulation to inside the simulation. /// /// Event data values for a bound key state change. - private void OnKeyBindStateChanged(BoundKeyEventArgs args) + private void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args) { // If there is no InputSystem, then there is nothing to forward to, and nothing to do here. if(!EntitySystemManager.TryGetEntitySystem(out InputSystem inputSys)) return; - var func = args.Function; + var kArgs = args.KeyEventArgs; + var func = kArgs.Function; var funcId = InputManager.NetworkBindMap.KeyFunctionID(func); - var mousePosWorld = EyeManager.ScreenToMap(args.PointerLocation); - var entityToClick = GetEntityUnderPosition(mousePosWorld); + EntityCoordinates coordinates = default; + EntityUid entityToClick = default; + if (args.Viewport is IViewportControl vp) + { + var mousePosWorld = vp.ScreenToMap(kArgs.PointerLocation.Position); + entityToClick = GetEntityUnderPosition(mousePosWorld)?.Uid ?? EntityUid.Invalid; - var coordinates = MapManager.TryFindGridAt(mousePosWorld, out var grid) ? grid.MapToGrid(mousePosWorld) : - EntityCoordinates.FromMap(EntityManager, MapManager, mousePosWorld); + coordinates = MapManager.TryFindGridAt(mousePosWorld, out var grid) ? grid.MapToGrid(mousePosWorld) : + EntityCoordinates.FromMap(EntityManager, MapManager, mousePosWorld); + } - var message = new FullInputCmdMessage(Timing.CurTick, Timing.TickFraction, funcId, args.State, - coordinates , args.PointerLocation, - entityToClick?.Uid ?? EntityUid.Invalid); + var message = new FullInputCmdMessage(Timing.CurTick, Timing.TickFraction, funcId, kArgs.State, + coordinates , kArgs.PointerLocation, + entityToClick); // client side command handlers will always be sent the local player session. var session = PlayerManager.LocalPlayer?.Session; if (inputSys.HandleInputCommand(session, func, message)) { - args.Handle(); + kArgs.Handle(); } } } diff --git a/Content.Client/State/IMainViewport.cs b/Content.Client/State/IMainViewport.cs new file mode 100644 index 0000000000..3dc46ba6b3 --- /dev/null +++ b/Content.Client/State/IMainViewport.cs @@ -0,0 +1,15 @@ +using Content.Client.UserInterface; + +namespace Content.Client.State +{ + /// + /// Client state that has a main viewport. + /// + /// + /// Used for taking no-UI screenshots (including things like flash overlay). + /// + public interface IMainViewportState + { + public MainViewport Viewport { get; } + } +} diff --git a/Content.Client/State/LauncherConnecting.cs b/Content.Client/State/LauncherConnecting.cs index 51ff4cc735..877f3ce6d6 100644 --- a/Content.Client/State/LauncherConnecting.cs +++ b/Content.Client/State/LauncherConnecting.cs @@ -1,251 +1,81 @@ -using Content.Client.UserInterface.Controls; -using Content.Client.UserInterface.Stylesheets; -using Content.Client.Utility; +using System; +using Content.Client.UserInterface; using Robust.Client; -using Robust.Client.Graphics; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; using Robust.Shared.Network; -using static Content.Client.StaticIoC; namespace Content.Client.State { public class LauncherConnecting : Robust.Client.State.State { [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IStylesheetManager _stylesheetManager = default!; [Dependency] private readonly IClientNetManager _clientNetManager = default!; [Dependency] private readonly IGameController _gameController = default!; [Dependency] private readonly IBaseClient _baseClient = default!; - private Control? _control; - private Label? _connectStatus; + private LauncherConnectingGui? _control; - private Control? _connectingStatus; - private Control? _connectFail; - private Label? _connectFailReason; - private Control? _disconnected; + private Page _currentPage; + private string? _connectFailReason; + + public string? Address => _gameController.LaunchState.Ss14Address ?? _gameController.LaunchState.ConnectAddress; + + public string? ConnectFailReason + { + get => _connectFailReason; + private set + { + _connectFailReason = value; + ConnectFailReasonChanged?.Invoke(value); + } + } + + public Page CurrentPage + { + get => _currentPage; + private set + { + _currentPage = value; + PageChanged?.Invoke(value); + } + } + + public ClientConnectionState ConnectionState => _clientNetManager.ClientConnectState; + + public event Action? PageChanged; + public event Action? ConnectFailReasonChanged; + public event Action? ConnectionStateChanged; public override void Startup() { - Button exitButton; - Button reconnectButton; - Button retryButton; - - var address = _gameController.LaunchState.Ss14Address ?? _gameController.LaunchState.ConnectAddress; - - _control = new Control - { - Stylesheet = _stylesheetManager.SheetSpace, - Children = - { - new PanelContainer {StyleClasses = {StyleBase.ClassAngleRect}}, - new VBoxContainer - { - SeparationOverride = 0, - MinSize = (300, 200), - Children = - { - new HBoxContainer - { - Children = - { - new Label - { - Margin = new Thickness(8, 0, 0, 0), - Text = Loc.GetString("Space Station 14"), - StyleClasses = {StyleBase.StyleClassLabelHeading}, - VAlign = Label.VAlignMode.Center - }, - - (exitButton = new Button - { - Text = Loc.GetString("Exit"), - HorizontalAlignment = Control.HAlignment.Right, - HorizontalExpand = true, - }), - } - }, - - // Line - new HighDivider(), - - new VBoxContainer - { - VerticalExpand = true, - Margin = new Thickness(4, 4, 4, 0), - SeparationOverride = 0, - Children = - { - new Control - { - VerticalExpand = true, - Children = - { - (_connectingStatus = new VBoxContainer - { - SeparationOverride = 0, - Children = - { - new Label - { - Text = Loc.GetString("Connecting to server..."), - Align = Label.AlignMode.Center, - }, - - (_connectStatus = new Label - { - StyleClasses = {StyleBase.StyleClassLabelSubText}, - Align = Label.AlignMode.Center, - }), - } - }), - (_connectFail = new VBoxContainer - { - Visible = false, - SeparationOverride = 0, - Children = - { - (_connectFailReason = new Label - { - Align = Label.AlignMode.Center - }), - - (retryButton = new Button - { - Text = "Retry", - HorizontalAlignment = Control.HAlignment.Center, - VerticalExpand = true, - VerticalAlignment = Control.VAlignment.Bottom, - }) - } - }), - - (_disconnected = new VBoxContainer - { - SeparationOverride = 0, - Children = - { - new Label - { - Text = "Disconnected from server:", - Align = Label.AlignMode.Center - }, - new Label - { - Text = _baseClient.LastDisconnectReason, - Align = Label.AlignMode.Center - }, - (reconnectButton = new Button - { - Text = "Reconnect", - HorizontalAlignment = Control.HAlignment.Center, - VerticalExpand = true, - VerticalAlignment = Control.VAlignment.Bottom, - }) - } - }) - } - }, - - // Padding. - new Control {MinSize = (0, 8)}, - - new Label - { - Text = address, - StyleClasses = {StyleBase.StyleClassLabelSubText}, - HorizontalAlignment = Control.HAlignment.Center - } - } - }, - - // Line - new PanelContainer - { - PanelOverride = new StyleBoxFlat - { - BackgroundColor = Color.FromHex("#444"), - ContentMarginTopOverride = 2 - }, - }, - new HBoxContainer - { - Margin = new Thickness(12, 0, 4, 0), - VerticalAlignment = Control.VAlignment.Bottom, - Children = - { - new Label - { - Text = Loc.GetString("Don't die!"), - StyleClasses = {StyleBase.StyleClassLabelSubText} - }, - new Label - { - Text = "ver 0.1", - HorizontalExpand = true, - HorizontalAlignment = Control.HAlignment.Right, - StyleClasses = {StyleBase.StyleClassLabelSubText} - } - } - }, - } - }, - } - }; + _control = new LauncherConnectingGui(this); _userInterfaceManager.StateRoot.AddChild(_control); - LayoutContainer.SetAnchorPreset(_control, LayoutContainer.LayoutPreset.Center); - LayoutContainer.SetGrowHorizontal(_control, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(_control, LayoutContainer.GrowDirection.Both); - - exitButton.OnPressed += _ => - { - _gameController.Shutdown("Exit button pressed"); - }; - - void Retry(BaseButton.ButtonEventArgs args) - { - if (_gameController.LaunchState.ConnectEndpoint != null) - { - _baseClient.ConnectToServer(_gameController.LaunchState.ConnectEndpoint); - SetActivePage(Page.Connecting); - } - } - - reconnectButton.OnPressed += Retry; - retryButton.OnPressed += Retry; - _clientNetManager.ConnectFailed += (_, args) => { - _connectFailReason.Text = Loc.GetString("Failed to connect to server:\n{0}", args.Reason); - SetActivePage(Page.ConnectFailed); + ConnectFailReason = args.Reason; + CurrentPage = Page.ConnectFailed; }; - _clientNetManager.ClientConnectStateChanged += ConnectStateChanged; + _clientNetManager.ClientConnectStateChanged += state => ConnectionStateChanged?.Invoke(state); - SetActivePage(Page.Connecting); - - ConnectStateChanged(_clientNetManager.ClientConnectState); + CurrentPage = Page.Connecting; } - private void ConnectStateChanged(ClientConnectionState state) + public void RetryConnect() { - if (_connectStatus == null) return; - - _connectStatus.Text = Loc.GetString(state switch + if (_gameController.LaunchState.ConnectEndpoint != null) { - ClientConnectionState.NotConnecting => "You should not be seeing this", - ClientConnectionState.ResolvingHost => "Resolving server address...", - ClientConnectionState.EstablishingConnection => "Establishing initial connection...", - ClientConnectionState.Handshake => "Doing handshake...", - ClientConnectionState.Connected => "Synchronizing game state...", - _ => state.ToString() - }); + _baseClient.ConnectToServer(_gameController.LaunchState.ConnectEndpoint); + CurrentPage = Page.Connecting; + } + } + + public void Exit() + { + _gameController.Shutdown("Exit button pressed"); } public override void Shutdown() @@ -255,17 +85,10 @@ namespace Content.Client.State public void SetDisconnected() { - SetActivePage(Page.Disconnected); + CurrentPage = Page.Disconnected; } - private void SetActivePage(Page page) - { - if (_connectingStatus != null) _connectingStatus.Visible = page == Page.Connecting; - if (_connectFail != null) _connectFail.Visible = page == Page.ConnectFailed; - if (_disconnected != null) _disconnected.Visible = page == Page.Disconnected; - } - - private enum Page : byte + public enum Page : byte { Connecting, ConnectFailed, diff --git a/Content.Client/State/MainMenu.cs b/Content.Client/State/MainMenu.cs index b12619f72c..ea4af74ac3 100644 --- a/Content.Client/State/MainMenu.cs +++ b/Content.Client/State/MainMenu.cs @@ -215,6 +215,8 @@ namespace Content.Client.State LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); + AddChild(new ParallaxControl()); + var layout = new LayoutContainer(); AddChild(layout); diff --git a/Content.Client/StationEvents/RadiationPulseOverlay.cs b/Content.Client/StationEvents/RadiationPulseOverlay.cs index 679c8c3ba4..a3aaac651a 100644 --- a/Content.Client/StationEvents/RadiationPulseOverlay.cs +++ b/Content.Client/StationEvents/RadiationPulseOverlay.cs @@ -107,7 +107,7 @@ namespace Content.Client.StationEvents _alphaRateOfChange[entity] = 1.0f / (float) (transitionTime - currentTime).TotalSeconds; } - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) + protected override void Draw(in OverlayDrawArgs args) { // PVS should control the overlay pretty well so the overlay doesn't get instantiated unless we're near one... var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; @@ -124,7 +124,7 @@ namespace Content.Client.StationEvents .EntityQuery(true) .ToList(); - var screenHandle = (DrawingHandleScreen) handle; + var screenHandle = args.ScreenHandle; var viewport = _eyeManager.GetWorldViewport(); foreach (var grid in _mapManager.FindGridsIntersecting(playerEntity.Transform.MapID, viewport)) @@ -134,7 +134,7 @@ namespace Content.Client.StationEvents if (!pulse.Draw || grid.Index != pulse.Owner.Transform.GridID) continue; // TODO: Check if viewport intersects circle - var circlePosition = _eyeManager.WorldToScreen(pulse.Owner.Transform.WorldPosition); + var circlePosition = args.ViewportControl!.WorldToScreen(pulse.Owner.Transform.WorldPosition); // change to worldhandle when implemented screenHandle.DrawCircle( diff --git a/Content.Client/UserInterface/CharacterSetupGui.cs b/Content.Client/UserInterface/CharacterSetupGui.cs index 2dcaf3d406..fcd7acd97d 100644 --- a/Content.Client/UserInterface/CharacterSetupGui.cs +++ b/Content.Client/UserInterface/CharacterSetupGui.cs @@ -29,11 +29,14 @@ namespace Content.Client.UserInterface public readonly Button CloseButton; public readonly Button SaveButton; - public CharacterSetupGui(IEntityManager entityManager, + public CharacterSetupGui( + IEntityManager entityManager, IResourceCache resourceCache, IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager) { + AddChild(new ParallaxControl()); + _entityManager = entityManager; _preferencesManager = preferencesManager; var margin = new Control diff --git a/Content.Client/UserInterface/LauncherConnectingGui.xaml b/Content.Client/UserInterface/LauncherConnectingGui.xaml new file mode 100644 index 0000000000..e141d8e391 --- /dev/null +++ b/Content.Client/UserInterface/LauncherConnectingGui.xaml @@ -0,0 +1,53 @@ + + + + + + +