Viewport improvements (#3765)

This commit is contained in:
Pieter-Jan Briers
2021-04-19 09:52:40 +02:00
committed by GitHub
parent 8e2fc49357
commit 147a54c642
40 changed files with 1173 additions and 418 deletions

View File

@@ -28,9 +28,9 @@ namespace Content.Client.Atmos
_atmosDebugOverlaySystem = EntitySystem.Get<AtmosDebugOverlaySystem>(); _atmosDebugOverlaySystem = EntitySystem.Get<AtmosDebugOverlaySystem>();
} }
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 mapId = _eyeManager.CurrentMap;
var eye = _eyeManager.CurrentEye; var eye = _eyeManager.CurrentEye;

View File

@@ -25,9 +25,9 @@ namespace Content.Client.Atmos
_gasTileOverlaySystem = EntitySystem.Get<GasTileOverlaySystem>(); _gasTileOverlaySystem = EntitySystem.Get<GasTileOverlaySystem>();
} }
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 mapId = _eyeManager.CurrentMap;
var eye = _eyeManager.CurrentEye; var eye = _eyeManager.CurrentEye;

View File

@@ -47,6 +47,7 @@ namespace Content.Client
IoCManager.Register<EuiManager, EuiManager>(); IoCManager.Register<EuiManager, EuiManager>();
IoCManager.Register<IVoteManager, VoteManager>(); IoCManager.Register<IVoteManager, VoteManager>();
IoCManager.Register<ChangelogManager, ChangelogManager>(); IoCManager.Register<ChangelogManager, ChangelogManager>();
IoCManager.Register<ViewportManager, ViewportManager>();
} }
} }
} }

View File

@@ -33,6 +33,7 @@ using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.State; using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -100,6 +101,7 @@ namespace Content.Client
IoCManager.Resolve<IStylesheetManager>().Initialize(); IoCManager.Resolve<IStylesheetManager>().Initialize();
IoCManager.Resolve<IScreenshotHook>().Initialize(); IoCManager.Resolve<IScreenshotHook>().Initialize();
IoCManager.Resolve<ChangelogManager>().Initialize(); IoCManager.Resolve<ChangelogManager>().Initialize();
IoCManager.Resolve<ViewportManager>().Initialize();
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
@@ -183,6 +185,9 @@ namespace Content.Client
} }
}; };
// Disable engine-default viewport since we use our own custom viewport control.
IoCManager.Resolve<IUserInterfaceManager>().MainViewport.Visible = false;
SwitchToDefaultState(); SwitchToDefaultState();
} }

View File

@@ -11,28 +11,23 @@ namespace Content.Client.GameObjects.Components
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private const float DefaultWidth = 1;
private const string ShaderInRange = "SelectionOutlineInrange"; private const string ShaderInRange = "SelectionOutlineInrange";
private const string ShaderOutOfRange = "SelectionOutline"; private const string ShaderOutOfRange = "SelectionOutline";
public override string Name => "InteractionOutline"; public override string Name => "InteractionOutline";
private ShaderInstance? _selectionShaderInstance; private bool _inRange;
private ShaderInstance? _selectionShaderInRangeInstance; private ShaderInstance? _shader;
private int _lastRenderScale;
/// <inheritdoc /> public void OnMouseEnter(bool inInteractionRange, int renderScale)
public override void Initialize()
{
base.Initialize();
_selectionShaderInRangeInstance = _prototypeManager.Index<ShaderPrototype>(ShaderInRange).Instance();
_selectionShaderInstance = _prototypeManager.Index<ShaderPrototype>(ShaderOutOfRange).Instance();
}
public void OnMouseEnter(bool inInteractionRange)
{ {
_lastRenderScale = renderScale;
_inRange = inInteractionRange;
if (Owner.TryGetComponent(out ISpriteComponent? sprite)) if (Owner.TryGetComponent(out ISpriteComponent? sprite))
{ {
sprite.PostShader = inInteractionRange ? _selectionShaderInRangeInstance : _selectionShaderInstance; sprite.PostShader = MakeNewShader(inInteractionRange, renderScale);
sprite.RenderOrder = Owner.EntityManager.CurrentTick.Value; sprite.RenderOrder = Owner.EntityManager.CurrentTick.Value;
} }
} }
@@ -44,14 +39,30 @@ namespace Content.Client.GameObjects.Components
sprite.PostShader = null; sprite.PostShader = null;
sprite.RenderOrder = 0; 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<ShaderPrototype>(shaderName).InstanceUnique();
instance.SetParameter("outline_width", DefaultWidth * renderScale);
return instance;
} }
} }
} }

View File

@@ -35,17 +35,7 @@ namespace Content.Client.GameObjects.Components.Suspicion
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); _font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
} }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(in OverlayDrawArgs args)
{
switch (currentSpace)
{
case OverlaySpace.ScreenSpace:
DrawScreen((DrawingHandleScreen) handle);
break;
}
}
private void DrawScreen(DrawingHandleScreen screen)
{ {
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
@@ -91,8 +81,8 @@ namespace Content.Client.GameObjects.Components.Suspicion
continue; continue;
} }
var screenCoordinates = _eyeManager.WorldToScreen(physics.GetWorldAABB().TopLeft + (0, 0.5f)); var screenCoordinates = args.ViewportControl!.WorldToScreen(physics.GetWorldAABB().TopLeft + (0, 0.5f));
DrawString(screen, _font, screenCoordinates, _traitorText, Color.OrangeRed); DrawString(args.ScreenHandle, _font, screenCoordinates, _traitorText, Color.OrangeRed);
} }
} }

View File

@@ -464,15 +464,15 @@ namespace Content.Client.GameObjects.EntitySystems.AI
#endregion #endregion
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(in OverlayDrawArgs args)
{ {
if (Modes == 0) if (Modes == 0)
{ {
return; return;
} }
handle.UseShader(_shader); var screenHandle = args.ScreenHandle;
var screenHandle = (DrawingHandleScreen) handle; screenHandle.UseShader(_shader);
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
if ((Modes & PathfindingDebugMode.Route) != 0) if ((Modes & PathfindingDebugMode.Route) != 0)

View File

@@ -10,6 +10,7 @@ using Content.Shared;
using Content.Shared.GameObjects.Verbs; using Content.Shared.GameObjects.Verbs;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.State; using Robust.Client.State;
@@ -35,6 +36,7 @@ namespace Content.Client.GameObjects.EntitySystems
[Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private readonly IContextMenuView _contextMenuView; private readonly IContextMenuView _contextMenuView;
private readonly VerbSystem _verbSystem; private readonly VerbSystem _verbSystem;
@@ -152,7 +154,14 @@ namespace Content.Client.GameObjects.EntitySystems
{ {
var inRange = var inRange =
localPlayer.InRangeUnobstructed(e.ContextEntity, ignoreInsideBlocker: true); 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; var localPlayer = _playerManager.LocalPlayer;
if (localPlayer?.ControlledEntity == null) return; 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) if (e.SpriteComp != null)
{ {
e.SpriteComp.DrawDepth = (int) Shared.GameObjects.DrawDepth.HighlightedItems; e.SpriteComp.DrawDepth = (int) Shared.GameObjects.DrawDepth.HighlightedItems;

View File

@@ -22,12 +22,14 @@ namespace Content.Client.Graphics.Overlays
_shader = _prototypeManager.Index<ShaderPrototype>("CircleMask").Instance(); _shader = _prototypeManager.Index<ShaderPrototype>("CircleMask").Instance();
} }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(in OverlayDrawArgs args)
{ {
if (!CritOverlay.LocalPlayerHasState(_playerManager, false, true)) if (!CritOverlay.LocalPlayerHasState(_playerManager, false, true))
return; return;
handle.UseShader(_shader);
var worldHandle = (DrawingHandleWorld)handle;
var worldHandle = args.WorldHandle;
worldHandle.UseShader(_shader);
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
worldHandle.DrawRect(viewport, Color.White); worldHandle.DrawRect(viewport, Color.White);
} }

View File

@@ -22,10 +22,10 @@ namespace Content.Client.Graphics.Overlays
_shader = _prototypeManager.Index<ShaderPrototype>("ColoredScreenBorder").Instance(); _shader = _prototypeManager.Index<ShaderPrototype>("ColoredScreenBorder").Instance();
} }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(in OverlayDrawArgs args)
{ {
handle.UseShader(_shader); var worldHandle = args.WorldHandle;
var worldHandle = (DrawingHandleWorld)handle; worldHandle.UseShader(_shader);
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
worldHandle.DrawRect(viewport, Color.White); worldHandle.DrawRect(viewport, Color.White);
} }

View File

@@ -45,14 +45,14 @@ namespace Content.Client.Graphics.Overlays
return false; return false;
} }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(in OverlayDrawArgs args)
{ {
if (!LocalPlayerHasState(_playerManager, true, false)) if (!LocalPlayerHasState(_playerManager, true, false))
return; return;
var worldHandle = (DrawingHandleWorld) handle; var worldHandle = args.WorldHandle;
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
handle.UseShader(_gradientCircleShader); worldHandle.UseShader(_gradientCircleShader);
worldHandle.DrawRect(viewport, Color.White); worldHandle.DrawRect(viewport, Color.White);
} }
} }

View File

@@ -1,4 +1,6 @@
using Content.Client.State;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Maths; using Robust.Shared.Maths;
@@ -14,6 +16,7 @@ namespace Content.Client.Graphics.Overlays
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClyde _displayManager = default!; [Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
public override OverlaySpace Space => OverlaySpace.ScreenSpace; public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly ShaderInstance _shader; private readonly ShaderInstance _shader;
@@ -29,29 +32,34 @@ namespace Content.Client.Graphics.Overlays
public void ReceiveFlash(double duration) public void ReceiveFlash(double duration)
{ {
_displayManager.Screenshot(ScreenshotType.BeforeUI, image => if (_stateManager.CurrentState is IMainViewportState state)
{
state.Viewport.Viewport.Screenshot(image =>
{ {
var rgba32Image = image.CloneAs<Rgba32>(Configuration.Default); var rgba32Image = image.CloneAs<Rgba32>(Configuration.Default);
_screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
}); });
}
_startTime = _gameTiming.CurTime.TotalSeconds; _startTime = _gameTiming.CurTime.TotalSeconds;
_lastsFor = duration; _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); var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor);
if (percentComplete >= 1.0f) if (percentComplete >= 1.0f)
return; 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); var screenSize = UIBox2.FromDimensions((0, 0), _displayManager.ScreenSize);
if (_screenshotTexture != null) if (_screenshotTexture != null)
{ {
screenSpaceHandle?.DrawTextureRect(_screenshotTexture, screenSize); screenSpaceHandle.DrawTextureRect(_screenshotTexture, screenSize);
} }
} }

View File

@@ -38,7 +38,7 @@ namespace Content.Client.Graphics.Overlays
return _singularities.Count() > 0; return _singularities.Count() > 0;
} }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) protected override void Draw(in OverlayDrawArgs args)
{ {
SingularityQuery(); SingularityQuery();
@@ -52,8 +52,8 @@ namespace Content.Client.Graphics.Overlays
_shader?.SetParameter("intensity", LevelToIntensity(instance.Level)); _shader?.SetParameter("intensity", LevelToIntensity(instance.Level));
_shader?.SetParameter("falloff", LevelToFalloff(instance.Level)); _shader?.SetParameter("falloff", LevelToFalloff(instance.Level));
handle.UseShader(_shader); var worldHandle = args.WorldHandle;
var worldHandle = (DrawingHandleWorld) handle; worldHandle.UseShader(_shader);
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
worldHandle.DrawRect(viewport, Color.White); worldHandle.DrawRect(viewport, Color.White);
} }

View File

@@ -1,3 +1,4 @@
using System;
using Content.Client.Interfaces.Parallax; using Content.Client.Interfaces.Parallax;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Shared.Enums; using Robust.Shared.Enums;
@@ -18,7 +19,7 @@ namespace Content.Client.Parallax
private Texture? _parallaxTexture; private Texture? _parallaxTexture;
public override OverlaySpace Space => OverlaySpace.ScreenSpaceBelowWorld; public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader; private readonly ShaderInstance _shader;
public ParallaxOverlay() 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; return;
} }
handle.UseShader(_shader); var screenHandle = args.WorldHandle;
var screenHandle = (DrawingHandleScreen) handle; screenHandle.UseShader(_shader);
var (sizeX, sizeY) = _parallaxTexture.Size; var (sizeX, sizeY) = _parallaxTexture.Size / (float) EyeManager.PixelsPerMeter;
var (posX, posY) = _eyeManager.ScreenToMap(Vector2.Zero).Position; var (posX, posY) = args.Viewport.Eye.Position;
var (ox, oy) = (Vector2i) new Vector2(-posX / Slowness, posY / Slowness); var o = new Vector2(posX * Slowness, posY * Slowness);
ox = MathHelper.Mod(ox, sizeX);
oy = MathHelper.Mod(oy, sizeY);
var (screenSizeX, screenSizeY) = _displayManager.ScreenSize; // Remove offset so we can floor.
for (var x = -sizeX; x < screenSizeX; x += sizeX) { var (l, b) = args.WorldBounds.BottomLeft - o;
for (var y = -sizeY; y < screenSizeY; y += sizeY) {
screenHandle.DrawTexture(_parallaxTexture, new Vector2(ox + x, oy + y)); // 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));
} }
} }
} }

View File

@@ -1,15 +1,18 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Client.State;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.State;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client namespace Content.Client
{ {
@@ -20,24 +23,30 @@ namespace Content.Client
[Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
public void Initialize() public void Initialize()
{ {
_inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshot, InputCmdHandler.FromDelegate(_ => _inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshot, InputCmdHandler.FromDelegate(_ =>
{ {
Take(ScreenshotType.AfterUI); _clyde.Screenshot(ScreenshotType.Final, Take);
})); }));
_inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshotNoUI, InputCmdHandler.FromDelegate(_ => _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<T>(Image<T> screenshot) where T : unmanaged, IPixel<T>
{ {
var screenshot = await _clyde.ScreenshotAsync(type);
var time = DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss"); var time = DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss");
if (!_resourceManager.UserData.IsDir(BaseScreenshotPath)) if (!_resourceManager.UserData.IsDir(BaseScreenshotPath))

View File

@@ -6,6 +6,7 @@ using Content.Client.UserInterface;
using Content.Client.Voting; using Content.Client.Voting;
using Content.Shared; using Content.Shared;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -13,12 +14,16 @@ using Robust.Shared.Configuration;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Client.State 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 IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IInputManager _inputManager = default!;
@@ -26,6 +31,8 @@ namespace Content.Client.State
[Dependency] private readonly IVoteManager _voteManager = default!; [Dependency] private readonly IVoteManager _voteManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IClientAdminManager _adminManager = default!; [Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[ViewVariables] private ChatBox? _gameChat; [ViewVariables] private ChatBox? _gameChat;
private ConstructionMenuPresenter? _constructionMenu; private ConstructionMenuPresenter? _constructionMenu;
@@ -33,15 +40,27 @@ namespace Content.Client.State
private bool _oocEnabled; private bool _oocEnabled;
private bool _adminOocEnabled; private bool _adminOocEnabled;
public MainViewport Viewport { get; private set; } = default!;
public override void Startup() public override void Startup()
{ {
base.Startup(); base.Startup();
_gameChat = new ChatBox(); _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); _userInterfaceManager.StateRoot.AddChild(_gameChat);
LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10); LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10);
LayoutContainer.SetAnchorAndMarginPreset(_gameChat, LayoutContainer.LayoutPreset.TopRight, margin: 10);
LayoutContainer.SetMarginLeft(_gameChat, -475); LayoutContainer.SetMarginLeft(_gameChat, -475);
LayoutContainer.SetMarginBottom(_gameChat, 235); LayoutContainer.SetMarginBottom(_gameChat, 235);
@@ -65,6 +84,8 @@ namespace Content.Client.State
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated; _adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
SetupPresenters(); SetupPresenters();
_eyeManager.MainViewport = Viewport.Viewport;
} }
public override void Shutdown() public override void Shutdown()
@@ -74,7 +95,10 @@ namespace Content.Client.State
base.Shutdown(); base.Shutdown();
_gameChat?.Dispose(); _gameChat?.Dispose();
Viewport.Dispose();
_gameHud.RootControl.Orphan(); _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.GrabKeyboardFocus();
chat.Input.InsertAtCursor("]"); chat.Input.InsertAtCursor("]");
} }
public override void FrameUpdate(FrameEventArgs e)
{
base.FrameUpdate(e);
Viewport.Viewport.Eye = _eyeManager.CurrentEye;
}
} }
} }

View File

@@ -2,14 +2,15 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Content.Client.GameObjects.Components; using Content.Client.GameObjects.Components;
using Content.Client.UserInterface;
using Content.Client.Utility; using Content.Client.Utility;
using Content.Shared; using Content.Shared;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.State; using Robust.Client.State;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Input; using Robust.Shared.Input;
@@ -28,7 +29,6 @@ namespace Content.Client.State
[Dependency] protected readonly IClientEntityManager EntityManager = default!; [Dependency] protected readonly IClientEntityManager EntityManager = default!;
[Dependency] protected readonly IInputManager InputManager = default!; [Dependency] protected readonly IInputManager InputManager = default!;
[Dependency] protected readonly IPlayerManager PlayerManager = default!; [Dependency] protected readonly IPlayerManager PlayerManager = default!;
[Dependency] protected readonly IEyeManager EyeManager = default!;
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!; [Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[Dependency] protected readonly IGameTiming Timing = default!; [Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] protected readonly IMapManager MapManager = default!; [Dependency] protected readonly IMapManager MapManager = default!;
@@ -68,10 +68,18 @@ namespace Content.Client.State
if (localPlayer == null) if (localPlayer == null)
return; return;
var mousePosWorld = EyeManager.ScreenToMap(InputManager.MouseScreenPosition); IEntity? entityToClick = null;
var entityToClick = UserInterfaceManager.CurrentlyHovered != null var renderScale = 1;
? null if (UserInterfaceManager.CurrentlyHovered is IViewportControl vp)
: GetEntityUnderPosition(mousePosWorld); {
var mousePosWorld = vp.ScreenToMap(InputManager.MouseScreenPosition);
entityToClick = GetEntityUnderPosition(mousePosWorld);
if (vp is ScalingViewport svp)
{
renderScale = svp.CurrentRenderScale;
}
}
var inRange = false; var inRange = false;
if (localPlayer.ControlledEntity != null && entityToClick != null) if (localPlayer.ControlledEntity != null && entityToClick != null)
@@ -93,7 +101,7 @@ namespace Content.Client.State
{ {
if (entityToClick != null && entityToClick.TryGetComponent(out outline)) if (entityToClick != null && entityToClick.TryGetComponent(out outline))
{ {
outline.UpdateInRange(inRange); outline.UpdateInRange(inRange, renderScale);
} }
return; return;
@@ -109,7 +117,7 @@ namespace Content.Client.State
if (_lastHoveredEntity != null && _lastHoveredEntity.TryGetComponent(out outline)) 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. /// Converts a state change event from outside the simulation to inside the simulation.
/// </summary> /// </summary>
/// <param name="args">Event data values for a bound key state change.</param> /// <param name="args">Event data values for a bound key state change.</param>
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 there is no InputSystem, then there is nothing to forward to, and nothing to do here.
if(!EntitySystemManager.TryGetEntitySystem(out InputSystem inputSys)) if(!EntitySystemManager.TryGetEntitySystem(out InputSystem inputSys))
return; return;
var func = args.Function; var kArgs = args.KeyEventArgs;
var func = kArgs.Function;
var funcId = InputManager.NetworkBindMap.KeyFunctionID(func); var funcId = InputManager.NetworkBindMap.KeyFunctionID(func);
var mousePosWorld = EyeManager.ScreenToMap(args.PointerLocation); EntityCoordinates coordinates = default;
var entityToClick = GetEntityUnderPosition(mousePosWorld); 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) : coordinates = MapManager.TryFindGridAt(mousePosWorld, out var grid) ? grid.MapToGrid(mousePosWorld) :
EntityCoordinates.FromMap(EntityManager, MapManager, mousePosWorld); EntityCoordinates.FromMap(EntityManager, MapManager, mousePosWorld);
}
var message = new FullInputCmdMessage(Timing.CurTick, Timing.TickFraction, funcId, args.State, var message = new FullInputCmdMessage(Timing.CurTick, Timing.TickFraction, funcId, kArgs.State,
coordinates , args.PointerLocation, coordinates , kArgs.PointerLocation,
entityToClick?.Uid ?? EntityUid.Invalid); entityToClick);
// client side command handlers will always be sent the local player session. // client side command handlers will always be sent the local player session.
var session = PlayerManager.LocalPlayer?.Session; var session = PlayerManager.LocalPlayer?.Session;
if (inputSys.HandleInputCommand(session, func, message)) if (inputSys.HandleInputCommand(session, func, message))
{ {
args.Handle(); kArgs.Handle();
} }
} }
} }

View File

@@ -0,0 +1,15 @@
using Content.Client.UserInterface;
namespace Content.Client.State
{
/// <summary>
/// Client state that has a main viewport.
/// </summary>
/// <remarks>
/// Used for taking no-UI screenshots (including things like flash overlay).
/// </remarks>
public interface IMainViewportState
{
public MainViewport Viewport { get; }
}
}

View File

@@ -1,251 +1,81 @@
using Content.Client.UserInterface.Controls; using System;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface;
using Content.Client.Utility;
using Robust.Client; using Robust.Client;
using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Network; using Robust.Shared.Network;
using static Content.Client.StaticIoC;
namespace Content.Client.State namespace Content.Client.State
{ {
public class LauncherConnecting : Robust.Client.State.State public class LauncherConnecting : Robust.Client.State.State
{ {
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IStylesheetManager _stylesheetManager = default!;
[Dependency] private readonly IClientNetManager _clientNetManager = default!; [Dependency] private readonly IClientNetManager _clientNetManager = default!;
[Dependency] private readonly IGameController _gameController = default!; [Dependency] private readonly IGameController _gameController = default!;
[Dependency] private readonly IBaseClient _baseClient = default!; [Dependency] private readonly IBaseClient _baseClient = default!;
private Control? _control; private LauncherConnectingGui? _control;
private Label? _connectStatus;
private Control? _connectingStatus; private Page _currentPage;
private Control? _connectFail; private string? _connectFailReason;
private Label? _connectFailReason;
private Control? _disconnected; 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<Page>? PageChanged;
public event Action<string?>? ConnectFailReasonChanged;
public event Action<ClientConnectionState>? ConnectionStateChanged;
public override void Startup() public override void Startup()
{ {
Button exitButton; _control = new LauncherConnectingGui(this);
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}
}
}
},
}
},
}
};
_userInterfaceManager.StateRoot.AddChild(_control); _userInterfaceManager.StateRoot.AddChild(_control);
LayoutContainer.SetAnchorPreset(_control, LayoutContainer.LayoutPreset.Center); _clientNetManager.ConnectFailed += (_, args) =>
LayoutContainer.SetGrowHorizontal(_control, LayoutContainer.GrowDirection.Both);
LayoutContainer.SetGrowVertical(_control, LayoutContainer.GrowDirection.Both);
exitButton.OnPressed += _ =>
{ {
_gameController.Shutdown("Exit button pressed"); ConnectFailReason = args.Reason;
CurrentPage = Page.ConnectFailed;
}; };
void Retry(BaseButton.ButtonEventArgs args) _clientNetManager.ClientConnectStateChanged += state => ConnectionStateChanged?.Invoke(state);
CurrentPage = Page.Connecting;
}
public void RetryConnect()
{ {
if (_gameController.LaunchState.ConnectEndpoint != null) if (_gameController.LaunchState.ConnectEndpoint != null)
{ {
_baseClient.ConnectToServer(_gameController.LaunchState.ConnectEndpoint); _baseClient.ConnectToServer(_gameController.LaunchState.ConnectEndpoint);
SetActivePage(Page.Connecting); CurrentPage = Page.Connecting;
} }
} }
reconnectButton.OnPressed += Retry; public void Exit()
retryButton.OnPressed += Retry;
_clientNetManager.ConnectFailed += (_, args) =>
{ {
_connectFailReason.Text = Loc.GetString("Failed to connect to server:\n{0}", args.Reason); _gameController.Shutdown("Exit button pressed");
SetActivePage(Page.ConnectFailed);
};
_clientNetManager.ClientConnectStateChanged += ConnectStateChanged;
SetActivePage(Page.Connecting);
ConnectStateChanged(_clientNetManager.ClientConnectState);
}
private void ConnectStateChanged(ClientConnectionState state)
{
if (_connectStatus == null) return;
_connectStatus.Text = Loc.GetString(state switch
{
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()
});
} }
public override void Shutdown() public override void Shutdown()
@@ -255,17 +85,10 @@ namespace Content.Client.State
public void SetDisconnected() public void SetDisconnected()
{ {
SetActivePage(Page.Disconnected); CurrentPage = Page.Disconnected;
} }
private void SetActivePage(Page page) public enum Page : byte
{
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
{ {
Connecting, Connecting,
ConnectFailed, ConnectFailed,

View File

@@ -215,6 +215,8 @@ namespace Content.Client.State
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
AddChild(new ParallaxControl());
var layout = new LayoutContainer(); var layout = new LayoutContainer();
AddChild(layout); AddChild(layout);

View File

@@ -107,7 +107,7 @@ namespace Content.Client.StationEvents
_alphaRateOfChange[entity] = 1.0f / (float) (transitionTime - currentTime).TotalSeconds; _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... // PVS should control the overlay pretty well so the overlay doesn't get instantiated unless we're near one...
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
@@ -124,7 +124,7 @@ namespace Content.Client.StationEvents
.EntityQuery<RadiationPulseComponent>(true) .EntityQuery<RadiationPulseComponent>(true)
.ToList(); .ToList();
var screenHandle = (DrawingHandleScreen) handle; var screenHandle = args.ScreenHandle;
var viewport = _eyeManager.GetWorldViewport(); var viewport = _eyeManager.GetWorldViewport();
foreach (var grid in _mapManager.FindGridsIntersecting(playerEntity.Transform.MapID, viewport)) 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; if (!pulse.Draw || grid.Index != pulse.Owner.Transform.GridID) continue;
// TODO: Check if viewport intersects circle // 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 // change to worldhandle when implemented
screenHandle.DrawCircle( screenHandle.DrawCircle(

View File

@@ -29,11 +29,14 @@ namespace Content.Client.UserInterface
public readonly Button CloseButton; public readonly Button CloseButton;
public readonly Button SaveButton; public readonly Button SaveButton;
public CharacterSetupGui(IEntityManager entityManager, public CharacterSetupGui(
IEntityManager entityManager,
IResourceCache resourceCache, IResourceCache resourceCache,
IClientPreferencesManager preferencesManager, IClientPreferencesManager preferencesManager,
IPrototypeManager prototypeManager) IPrototypeManager prototypeManager)
{ {
AddChild(new ParallaxControl());
_entityManager = entityManager; _entityManager = entityManager;
_preferencesManager = preferencesManager; _preferencesManager = preferencesManager;
var margin = new Control var margin = new Control

View File

@@ -0,0 +1,53 @@
<Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:cuic="clr-namespace:Content.Client.UserInterface">
<cuic:ParallaxControl />
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
<PanelContainer StyleClasses="AngleRect" />
<VBoxContainer MinSize="300 200">
<HBoxContainer>
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
StyleClasses="LabelHeading" VAlign="Center" />
<Button Name="ExitButton" Text="{Loc 'connecting-exit'}"
HorizontalAlignment="Right" HorizontalExpand="True" />
</HBoxContainer>
<cui:HighDivider />
<VBoxContainer VerticalExpand="True" Margin="4 4 4 0">
<Control VerticalExpand="True" Margin="0 0 0 8">
<VBoxContainer Name="ConnectingStatus">
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
<!-- Who the fuck named these cont- oh wait I did -->
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
</VBoxContainer>
<VBoxContainer Name="ConnectFail" Visible="False">
<Label Name="ConnectFailReason" Align="Center" />
<Button Name="RetryButton" Text="{Loc 'connecting-retry'}"
HorizontalAlignment="Center"
VerticalExpand="True" VerticalAlignment="Bottom" />
</VBoxContainer>
<VBoxContainer Name="Disconnected">
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
<Label Name="DisconnectReason" Align="Center" />
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
HorizontalAlignment="Center"
VerticalExpand="True" VerticalAlignment="Bottom" />
</VBoxContainer>
</Control>
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
</VBoxContainer>
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#444" ContentMarginTopOverride="2" />
</PanelContainer.PanelOverride>
</PanelContainer>
<HBoxContainer Margin="12 0 4 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'connecting-tip'}" StyleClasses="LabelSubText" />
<Label Text="{Loc 'connecting-version'}" StyleClasses="LabelSubText"
HorizontalAlignment="Right" HorizontalExpand="True" />
</HBoxContainer>
</VBoxContainer>
</Control>
</Control>

View File

@@ -0,0 +1,62 @@
using Content.Client.State;
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Network;
namespace Content.Client.UserInterface
{
[GenerateTypedNameReferences]
public sealed partial class LauncherConnectingGui : Control
{
private readonly LauncherConnecting _state;
public LauncherConnectingGui(LauncherConnecting state)
{
_state = state;
RobustXamlLoader.Load(this);
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
ReconnectButton.OnPressed += _ => _state.RetryConnect();
RetryButton.OnPressed += _ => _state.RetryConnect();
ExitButton.OnPressed += _ => _state.Exit();
var addr = state.Address;
if (addr != null)
ConnectingAddress.Text = addr;
state.PageChanged += OnPageChanged;
state.ConnectFailReasonChanged += ConnectFailReasonChanged;
state.ConnectionStateChanged += ConnectionStateChanged;
ConnectionStateChanged(state.ConnectionState);
}
private void ConnectFailReasonChanged(string? reason)
{
ConnectFailReason.Text = reason == null
? null
: Loc.GetString("connecting-fail-reason", ("reason", reason));
}
private void OnPageChanged(LauncherConnecting.Page page)
{
ConnectingStatus.Visible = page == LauncherConnecting.Page.Connecting;
ConnectFail.Visible = page == LauncherConnecting.Page.ConnectFailed;
Disconnected.Visible = page == LauncherConnecting.Page.Disconnected;
}
private void ConnectionStateChanged(ClientConnectionState state)
{
ConnectStatus.Text = Loc.GetString($"connecting-state-{state}");
}
}
}

View File

@@ -7,6 +7,10 @@
xmlns:maths="clr-namespace:Robust.Shared.Maths;assembly=Robust.Shared.Maths" xmlns:maths="clr-namespace:Robust.Shared.Maths;assembly=Robust.Shared.Maths"
xmlns:voting="clr-namespace:Content.Client.Voting"> xmlns:voting="clr-namespace:Content.Client.Voting">
<Control>
<!-- Parallax background -->
<cui:ParallaxControl />
<!-- One day I'll code a Margin property for controls. --> <!-- One day I'll code a Margin property for controls. -->
<MarginContainer MarginBottomOverride="20" MarginLeftOverride="20" MarginRightOverride="20" <MarginContainer MarginBottomOverride="20" MarginLeftOverride="20" MarginRightOverride="20"
MarginTopOverride="20"> MarginTopOverride="20">
@@ -85,4 +89,6 @@
</HBoxContainer> </HBoxContainer>
</VBoxContainer> </VBoxContainer>
</MarginContainer> </MarginContainer>
</Control>
</Control> </Control>

View File

@@ -0,0 +1,153 @@
using System;
using Content.Shared;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface
{
/// <summary>
/// Wrapper for <see cref="ScalingViewport"/> that listens to configuration variables.
/// Also does NN-snapping within tolerances.
/// </summary>
public sealed class MainViewport : Control
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ViewportManager _vpManager = default!;
public ScalingViewport Viewport { get; }
public MainViewport()
{
IoCManager.InjectDependencies(this);
Viewport = new ScalingViewport
{
AlwaysRender = true,
RenderScaleMode = ScalingViewportRenderScaleMode.CeilInt,
MouseFilter = MouseFilterMode.Stop
};
AddChild(Viewport);
}
protected override void EnteredTree()
{
base.EnteredTree();
_vpManager.AddViewport(this);
}
protected override void ExitedTree()
{
base.ExitedTree();
_vpManager.RemoveViewport(this);
}
public void UpdateCfg()
{
var stretch = _cfg.GetCVar(CCVars.ViewportStretch);
var renderScaleUp = _cfg.GetCVar(CCVars.ViewportScaleRender);
var fixedFactor = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
if (stretch)
{
var snapFactor = CalcSnappingFactor();
if (snapFactor == null)
{
// Did not find a snap, enable stretching.
Viewport.FixedStretchSize = null;
Viewport.StretchMode = ScalingViewportStretchMode.Bilinear;
if (renderScaleUp)
{
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.CeilInt;
}
else
{
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.Fixed;
Viewport.FixedRenderScale = 1;
}
return;
}
// Found snap, set fixed factor and run non-stretching code.
fixedFactor = snapFactor.Value;
}
Viewport.FixedStretchSize = Viewport.ViewportSize * fixedFactor;
Viewport.StretchMode = ScalingViewportStretchMode.Nearest;
if (renderScaleUp)
{
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.Fixed;
Viewport.FixedRenderScale = fixedFactor;
}
else
{
// Snapping but forced to render scale at scale 1 so...
// At least we can NN.
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.Fixed;
Viewport.FixedRenderScale = 1;
}
}
private int? CalcSnappingFactor()
{
// Margin tolerance is tolerance of "the window is too big"
// where we add a margin to the viewport to make it fit.
var cfgToleranceMargin = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin);
// Clip tolerance is tolerance of "the window is too small"
// where we are clipping the viewport to make it fit.
var cfgToleranceClip = _cfg.GetCVar(CCVars.ViewportSnapToleranceClip);
// Calculate if the viewport, when rendered at an integer scale,
// is close enough to the control size to enable "snapping" to NN,
// potentially cutting a tiny bit off/leaving a margin.
//
// Idea here is that if you maximize the window at 1080p or 1440p
// we are close enough to an integer scale (2x and 3x resp) that we should "snap" to it.
// Just do it iteratively.
// I'm sure there's a smarter approach that needs one try with math but I'm dumb.
for (var i = 1; i <= 10; i++)
{
var toleranceMargin = i * cfgToleranceMargin;
var toleranceClip = i * cfgToleranceClip;
var scaled = (Vector2) Viewport.ViewportSize * i;
var (dx, dy) = PixelSize - scaled;
// The rule for which snap fits is that at LEAST one axis needs to be in the tolerance size wise.
// One axis MAY be larger but not smaller than tolerance.
// Obviously if it's too small it's bad, and if it's too big on both axis we should stretch up.
if (Fits(dx) && Fits(dy) || Fits(dx) && Larger(dy) || Larger(dx) && Fits(dy))
{
// Found snap that fits.
return i;
}
bool Larger(float a)
{
return a > toleranceMargin;
}
bool Fits(float a)
{
return a <= toleranceMargin && a >= -toleranceClip;
}
}
return null;
}
protected override void Resized()
{
base.Resized();
UpdateCfg();
}
}
}

View File

@@ -1,14 +1,10 @@
using System;
using Content.Client.GameObjects.Components.HUD.Inventory;
using Content.Shared; using Content.Shared;
using Content.Shared.Prototypes.HUD; using Content.Shared.Prototypes.HUD;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -39,6 +35,11 @@ namespace Content.Client.UserInterface
private readonly OptionButton LightingPresetOption; private readonly OptionButton LightingPresetOption;
private readonly OptionButton _uiScaleOption; private readonly OptionButton _uiScaleOption;
private readonly OptionButton _hudThemeOption; private readonly OptionButton _hudThemeOption;
private readonly CheckBox _viewportStretchCheckBox;
private readonly CheckBox _viewportLowResCheckBox;
private readonly Slider _viewportScaleSlider;
private readonly Control _viewportScaleBox;
private readonly Label _viewportScaleText;
public GraphicsControl(IConfigurationManager cfg, IPrototypeManager proMan) public GraphicsControl(IConfigurationManager cfg, IPrototypeManager proMan)
{ {
@@ -121,12 +122,55 @@ namespace Content.Client.UserInterface
} }
}); });
contents.AddChild(new Placeholder() _viewportStretchCheckBox = new CheckBox
{ {
VerticalExpand = true, Text = Loc.GetString("ui-options-vp-stretch")
PlaceholderText = Loc.GetString("ui-options-placeholder-viewport") };
_viewportStretchCheckBox.OnToggled += _ =>
{
UpdateViewportScale();
UpdateApplyButton();
};
_viewportScaleSlider = new Slider
{
MinValue = 1,
MaxValue = 5,
Rounded = true,
MinWidth = 200
};
_viewportScaleSlider.OnValueChanged += _ =>
{
UpdateApplyButton();
UpdateViewportScale();
};
_viewportLowResCheckBox = new CheckBox { Text = Loc.GetString("ui-options-vp-low-res")};
_viewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
contents.AddChild(new HBoxContainer
{
Children =
{
_viewportStretchCheckBox,
(_viewportScaleBox = new HBoxContainer
{
Children =
{
(_viewportScaleText = new Label
{
Margin = new Thickness(8, 0)
}),
_viewportScaleSlider,
}
})
}
}); });
contents.AddChild(_viewportLowResCheckBox);
vBox.AddChild(contents); vBox.AddChild(contents);
vBox.AddChild(new StripeBack vBox.AddChild(new StripeBack
@@ -145,6 +189,12 @@ namespace Content.Client.UserInterface
LightingPresetOption.SelectId(GetConfigLightingQuality()); LightingPresetOption.SelectId(GetConfigLightingQuality());
_uiScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale)); _uiScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale));
_hudThemeOption.SelectId(_cfg.GetCVar(CCVars.HudTheme)); _hudThemeOption.SelectId(_cfg.GetCVar(CCVars.HudTheme));
_viewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
_viewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
_viewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
UpdateViewportScale();
UpdateApplyButton();
AddChild(vBox); AddChild(vBox);
} }
@@ -173,6 +223,9 @@ namespace Content.Client.UserInterface
_cfg.SetCVar(CVars.DisplayWindowMode, _cfg.SetCVar(CVars.DisplayWindowMode,
(int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed)); (int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed));
_cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[_uiScaleOption.SelectedId]); _cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[_uiScaleOption.SelectedId]);
_cfg.SetCVar(CCVars.ViewportStretch, _viewportStretchCheckBox.Pressed);
_cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) _viewportScaleSlider.Value);
_cfg.SetCVar(CCVars.ViewportScaleRender, !_viewportLowResCheckBox.Pressed);
_cfg.SaveToFile(); _cfg.SaveToFile();
UpdateApplyButton(); UpdateApplyButton();
} }
@@ -195,8 +248,18 @@ namespace Content.Client.UserInterface
var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality(); var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality();
var isHudThemeSame = _hudThemeOption.SelectedId == _cfg.GetCVar(CCVars.HudTheme); var isHudThemeSame = _hudThemeOption.SelectedId == _cfg.GetCVar(CCVars.HudTheme);
var isUIScaleSame = MathHelper.CloseTo(UIScaleOptions[_uiScaleOption.SelectedId], ConfigUIScale); var isUIScaleSame = MathHelper.CloseTo(UIScaleOptions[_uiScaleOption.SelectedId], ConfigUIScale);
ApplyButton.Disabled = isVSyncSame && isFullscreenSame && isLightingQualitySame && isHudThemeSame && var isVPStretchSame = _viewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
isUIScaleSame; var isVPScaleSame = (int) _viewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
var isVPResSame = _viewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
ApplyButton.Disabled = isVSyncSame &&
isFullscreenSame &&
isLightingQualitySame &&
isUIScaleSame &&
isVPStretchSame &&
isVPScaleSame &&
isVPResSame &&
isHudThemeSame;
} }
private bool ConfigIsFullscreen => private bool ConfigIsFullscreen =>
@@ -261,6 +324,12 @@ namespace Content.Client.UserInterface
return 0; return 0;
} }
private void UpdateViewportScale()
{
_viewportScaleBox.Visible = !_viewportStretchCheckBox.Pressed;
_viewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", _viewportScaleSlider.Value));
}
} }
} }
} }

View File

@@ -0,0 +1,47 @@
using Content.Client.Interfaces.Parallax;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.ViewVariables;
namespace Content.Client.UserInterface
{
/// <summary>
/// Renders the parallax background as a UI control.
/// </summary>
public sealed class ParallaxControl : Control
{
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[ViewVariables(VVAccess.ReadWrite)] public Vector2i Offset { get; set; }
public ParallaxControl()
{
IoCManager.InjectDependencies(this);
Offset = (_random.Next(0, 1000), _random.Next(0, 1000));
RectClipContent = true;
}
protected override void Draw(DrawingHandleScreen handle)
{
var tex = _parallaxManager.ParallaxTexture;
if (tex == null)
return;
var size = tex.Size;
var ourSize = PixelSize;
for (var x = -size.X + Offset.X; x < ourSize.X; x += size.X)
{
for (var y = -size.Y + Offset.Y; y < ourSize.Y; y += size.Y)
{
handle.DrawTexture(tex, (x, y));
}
}
}
}
}

View File

@@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.UserInterface
{
/// <summary>
/// Viewport control that has a fixed viewport size and scales it appropriately.
/// </summary>
public sealed class ScalingViewport : Control, IViewportControl
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
// Internal viewport creation is deferred.
private IClydeViewport? _viewport;
private IEye? _eye;
private Vector2i _viewportSize;
private int _curRenderScale;
private ScalingViewportStretchMode _stretchMode = ScalingViewportStretchMode.Bilinear;
private ScalingViewportRenderScaleMode _renderScaleMode = ScalingViewportRenderScaleMode.Fixed;
private int _fixedRenderScale = 1;
private readonly List<CopyPixelsDelegate<Rgba32>> _queuedScreenshots = new();
public int CurrentRenderScale => _curRenderScale;
/// <summary>
/// The eye to render.
/// </summary>
public IEye? Eye
{
get => _eye;
set
{
_eye = value;
if (_viewport != null)
_viewport.Eye = value;
}
}
/// <summary>
/// The size, in unscaled pixels, of the internal viewport.
/// </summary>
/// <remarks>
/// The actual viewport may have render scaling applied based on parameters.
/// </remarks>
public Vector2i ViewportSize
{
get => _viewportSize;
set
{
_viewportSize = value;
InvalidateViewport();
}
}
// Do not need to InvalidateViewport() since it doesn't affect viewport creation.
[ViewVariables(VVAccess.ReadWrite)] public Vector2i? FixedStretchSize { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public ScalingViewportStretchMode StretchMode
{
get => _stretchMode;
set
{
_stretchMode = value;
InvalidateViewport();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public ScalingViewportRenderScaleMode RenderScaleMode
{
get => _renderScaleMode;
set
{
_renderScaleMode = value;
InvalidateViewport();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public int FixedRenderScale
{
get => _fixedRenderScale;
set
{
_fixedRenderScale = value;
InvalidateViewport();
}
}
public ScalingViewport()
{
IoCManager.InjectDependencies(this);
RectClipContent = true;
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Handled)
return;
_inputManager.ViewportKeyEvent(this, args);
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (args.Handled)
return;
_inputManager.ViewportKeyEvent(this, args);
}
protected override void FrameUpdate(FrameEventArgs args)
{
EnsureViewportCreated();
}
protected override void Draw(DrawingHandleScreen handle)
{
DebugTools.AssertNotNull(_viewport);
_viewport!.Render();
if (_queuedScreenshots.Count != 0)
{
var callbacks = _queuedScreenshots.ToArray();
_viewport.RenderTarget.CopyPixelsToMemory<Rgba32>(image =>
{
foreach (var callback in callbacks)
{
callback(image);
}
});
_queuedScreenshots.Clear();
}
var drawBox = GetDrawBox();
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
}
public void Screenshot(CopyPixelsDelegate<Rgba32> callback)
{
_queuedScreenshots.Add(callback);
}
// Draw box in pixel coords to draw the viewport at.
private UIBox2i GetDrawBox()
{
DebugTools.AssertNotNull(_viewport);
var vpSize = _viewport!.Size;
var ourSize = (Vector2) PixelSize;
if (FixedStretchSize == null)
{
var (ratioX, ratioY) = ourSize / vpSize;
var ratio = Math.Min(ratioX, ratioY);
var size = vpSize * ratio;
// Size
var pos = (ourSize - size) / 2;
return (UIBox2i) UIBox2.FromDimensions(pos, size);
}
else
{
// Center only, no scaling.
var pos = (ourSize - FixedStretchSize.Value) / 2;
return (UIBox2i) UIBox2.FromDimensions(pos, FixedStretchSize.Value);
}
}
private void RegenerateViewport()
{
DebugTools.AssertNull(_viewport);
var vpSizeBase = ViewportSize;
var ourSize = PixelSize;
var (ratioX, ratioY) = ourSize / (Vector2) vpSizeBase;
var ratio = Math.Min(ratioX, ratioY);
var renderScale = 1;
switch (_renderScaleMode)
{
case ScalingViewportRenderScaleMode.CeilInt:
renderScale = (int) Math.Ceiling(ratio);
break;
case ScalingViewportRenderScaleMode.FloorInt:
renderScale = (int) Math.Floor(ratio);
break;
case ScalingViewportRenderScaleMode.Fixed:
renderScale = _fixedRenderScale;
break;
}
// Always has to be at least one to avoid passing 0,0 to the viewport constructor
renderScale = Math.Max(1, renderScale);
_curRenderScale = renderScale;
_viewport = _clyde.CreateViewport(
ViewportSize * renderScale,
new TextureSampleParameters
{
Filter = StretchMode == ScalingViewportStretchMode.Bilinear,
});
_viewport.RenderScale = (renderScale, renderScale);
_viewport.Eye = _eye;
}
protected override void Resized()
{
base.Resized();
InvalidateViewport();
}
private void InvalidateViewport()
{
_viewport?.Dispose();
_viewport = null;
}
public MapCoordinates ScreenToMap(Vector2 coords)
{
if (_eye == null)
return default;
EnsureViewportCreated();
var matrix = Matrix3.Invert(LocalToScreenMatrix());
return _viewport!.LocalToWorld(matrix.Transform(coords));
}
public Vector2 WorldToScreen(Vector2 map)
{
if (_eye == null)
return default;
EnsureViewportCreated();
var vpLocal = _viewport!.WorldToLocal(map);
var matrix = LocalToScreenMatrix();
return matrix.Transform(vpLocal);
}
private Matrix3 LocalToScreenMatrix()
{
DebugTools.AssertNotNull(_viewport);
var drawBox = GetDrawBox();
var scaleFactor = drawBox.Size / (Vector2) _viewport!.Size;
if (scaleFactor == (0, 0))
// Basically a nonsense scenario, at least make sure to return something that can be inverted.
return Matrix3.Identity;
var scale = Matrix3.CreateScale(scaleFactor);
var translate = Matrix3.CreateTranslation(GlobalPixelPosition + drawBox.TopLeft);
return scale * translate;
}
private void EnsureViewportCreated()
{
if (_viewport == null)
{
RegenerateViewport();
}
DebugTools.AssertNotNull(_viewport);
}
}
/// <summary>
/// Defines how the viewport is stretched if it does not match the size of the control perfectly.
/// </summary>
public enum ScalingViewportStretchMode
{
/// <summary>
/// Bilinear sampling is used.
/// </summary>
Bilinear = 0,
/// <summary>
/// Nearest neighbor sampling is used.
/// </summary>
Nearest,
}
/// <summary>
/// Defines how the base render scale of the viewport is selected.
/// </summary>
public enum ScalingViewportRenderScaleMode
{
/// <summary>
/// <see cref="ScalingViewport.FixedRenderScale"/> is used.
/// </summary>
Fixed = 0,
/// <summary>
/// Floor to the closest integer scale possible.
/// </summary>
FloorInt,
/// <summary>
/// Ceiling to the closest integer scale possible.
/// </summary>
CeilInt
}
}

View File

@@ -0,0 +1,15 @@
using Robust.Client.UserInterface.CustomControls;
namespace Content.Client.UserInterface
{
public static class ViewportExt
{
public static int GetRenderScale(this IViewportControl viewport)
{
if (viewport is ScalingViewport svp)
return svp.CurrentRenderScale;
return 1;
}
}
}

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using Content.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
namespace Content.Client.UserInterface
{
/// <summary>
/// Event proxy for <see cref="MainViewport"/> to listen to config events.
/// </summary>
// ReSharper disable once ClassNeverInstantiated.Global
public sealed class ViewportManager
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly List<MainViewport> _viewports = new();
public void Initialize()
{
_cfg.OnValueChanged(CCVars.ViewportStretch, _ => UpdateCfg());
_cfg.OnValueChanged(CCVars.ViewportSnapToleranceClip, _ => UpdateCfg());
_cfg.OnValueChanged(CCVars.ViewportSnapToleranceMargin, _ => UpdateCfg());
_cfg.OnValueChanged(CCVars.ViewportScaleRender, _ => UpdateCfg());
_cfg.OnValueChanged(CCVars.ViewportFixedScaleFactor, _ => UpdateCfg());
}
private void UpdateCfg()
{
_viewports.ForEach(v => v.UpdateCfg());
}
public void AddViewport(MainViewport vp)
{
_viewports.Add(vp);
}
public void RemoveViewport(MainViewport vp)
{
_viewports.Remove(vp);
}
}
}

View File

@@ -318,5 +318,24 @@ namespace Content.Shared
public static readonly CVarDef<bool> BanHardwareIds = public static readonly CVarDef<bool> BanHardwareIds =
CVarDef.Create("ban.hardware_ids", false, CVar.SERVERONLY); CVarDef.Create("ban.hardware_ids", false, CVar.SERVERONLY);
/*
* VIEWPORT
*/
public static readonly CVarDef<bool> ViewportStretch =
CVarDef.Create("viewport.stretch", true, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<int> ViewportFixedScaleFactor =
CVarDef.Create("viewport.fixed_scale_factor", 2, CVar.CLIENTONLY | CVar.ARCHIVE);
// This default is basically specifically chosen so fullscreen/maximized 1080p hits a 2x snap and does NN.
public static readonly CVarDef<int> ViewportSnapToleranceMargin =
CVarDef.Create("viewport.snap_tolerance_margin", 64, CVar.CLIENTONLY);
public static readonly CVarDef<int> ViewportSnapToleranceClip =
CVarDef.Create("viewport.snap_tolerance_clip", 32, CVar.CLIENTONLY);
public static readonly CVarDef<bool> ViewportScaleRender =
CVarDef.Create("viewport.scale_render", true, CVar.CLIENTONLY | CVar.ARCHIVE);
} }
} }

View File

@@ -0,0 +1,18 @@
### Connecting dialog when you start up the game
connecting-title = Space Station 14
connecting-exit = Exit
connecting-retry = Retry
connecting-reconnect = Reconnect
connecting-in-progress = Connecting to server...
connecting-disconnected = Disconnected from server:
connecting-tip = Don't die!
connecting-version = ver 0.1
connecting-fail-reason = Failed to connect to server:
{ $reason }
connecting-state-NotConnecting = You should not be seeing this
connecting-state-ResolvingHost = Resolving server address...
connecting-state-EstablishingConnection = Establishing initial connection...
connecting-state-Handshake = Doing handshake...
connecting-state-Connected = Synchronizing game state...

View File

@@ -36,7 +36,9 @@ ui-options-hud-theme = HUD Theme:
ui-options-hud-theme-default = Default ui-options-hud-theme-default = Default
ui-options-hud-theme-modernized = Modernized ui-options-hud-theme-modernized = Modernized
ui-options-hud-theme-classic = Classic ui-options-hud-theme-classic = Classic
ui-options-placeholder-viewport = Viewport settings ui-options-vp-stretch = Stretch viewport to fit game window?
ui-options-vp-scale = Fixed viewport scale: x{ $scale }
ui-options-vp-low-res = Low-resolution viewport?
## Controls menu ## Controls menu
ui-options-binds-reset-all = Reset ALL keybinds ui-options-binds-reset-all = Reset ALL keybinds

View File

@@ -18,7 +18,6 @@
- Disarm - Disarm
- HumanScream - HumanScream
- type: Eye - type: Eye
zoom: 0.5, 0.5
- type: CameraRecoil - type: CameraRecoil
- type: Examiner - type: Examiner
- type: HumanInventoryController - type: HumanInventoryController

View File

@@ -20,7 +20,6 @@
status: InAir status: InAir
- type: PlayerInputMover - type: PlayerInputMover
- type: Eye - type: Eye
zoom: 0.5, 0.5
drawFov: false drawFov: false
- type: Input - type: Input
context: "ghost" context: "ghost"

View File

@@ -15,7 +15,6 @@
- HumanScream - HumanScream
- Disarm - Disarm
- type: Eye - type: Eye
zoom: 0.5, 0.5
- type: CameraRecoil - type: CameraRecoil
- type: Examiner - type: Examiner
- type: HumanInventoryController - type: HumanInventoryController

View File

@@ -14,7 +14,6 @@
- VoxScream - VoxScream
- Disarm - Disarm
- type: Eye - type: Eye
zoom: 0.5, 0.5
- type: CameraRecoil - type: CameraRecoil
- type: Examiner - type: Examiner
- type: HumanInventoryController - type: HumanInventoryController

View File

@@ -3,7 +3,6 @@
kind: source kind: source
path: "/Textures/Shaders/outline.swsl" path: "/Textures/Shaders/outline.swsl"
params: params:
outline_width: 2
outline_color: "#FF000055" outline_color: "#FF000055"
- type: shader - type: shader
@@ -11,5 +10,4 @@
kind: source kind: source
path: "/Textures/Shaders/outline.swsl" path: "/Textures/Shaders/outline.swsl"
params: params:
outline_width: 2
outline_color: "#00FF0055" outline_color: "#00FF0055"