Compare commits

...

16 Commits

Author SHA1 Message Date
Pieter-Jan Briers
cd01ca924b Fix broken PanelContainer due to previous commit.
God I am tired.
2021-01-24 04:20:51 +01:00
Pieter-Jan Briers
38ad8ce132 Fix UI scaling weirdness with PanelContainer. 2021-01-23 21:51:35 +01:00
20kdc
ee440c2df9 Make the lighting manager much more configurable, including a console lock (#1506) 2021-01-23 21:17:49 +01:00
Pieter-Jan Briers
32f3c863fb Fix UI scaling bugs with ProgressBar 2021-01-23 20:15:45 +01:00
Pieter-Jan Briers
b00e0bef5a Fix UI scaling bug with RichTextLabel 2021-01-23 16:02:31 +01:00
Pieter-Jan Briers
0a09b27918 Fix UI scaling bug with GridContainer 2021-01-23 16:02:23 +01:00
Paul
c9f6a4e32a fixes enum VV 2021-01-21 15:33:42 +01:00
Pieter-Jan Briers
b205a14f69 Exception tolerance for NetManager.OnDisconnect 2021-01-20 21:07:02 +01:00
Pieter-Jan Briers
d5f3292e0a Unregister OnSessionOnPlayerStatusChanged on bound user interfaces.
I am frankly flabbergasted this is only a problem now.
2021-01-20 21:00:24 +01:00
Pieter-Jan Briers
561e4b330e Fix ALL components memory leaking.
:irrational:
2021-01-20 20:45:02 +01:00
Paul
36a5d102ff prevent one error from killing the entire namegenerator from running 2021-01-17 17:17:02 +01:00
Pieter-Jan Briers
b9c39e0953 Fix reconnecting. 2021-01-17 16:08:48 +01:00
Pieter-Jan Briers
ad4c8be132 Add system for preserving Map UIDs across edits. 2021-01-17 15:51:32 +01:00
kira-er
988cbf9a87 VV Enum (#1503) 2021-01-17 01:50:26 +01:00
Acruid
e26512001a Completely removed MsgSetTickRate, the NetConfigurationManager replaces the functionality. This builds on top of the previous commit.
Fixes bug where server was not sending the entire set of replicated cvars.
2021-01-16 15:33:44 -08:00
Pieter-Jan Briers
8e97982f1e Fix net.tickrate not being replicated correctly to clients. 2021-01-16 21:46:10 +01:00
27 changed files with 373 additions and 168 deletions

View File

@@ -153,7 +153,7 @@ namespace {nameSpace}
DiagnosticSeverity.Error,
true),
typeSymbol.Locations[0]));
return;
continue;
}
var txt = relevantXamlFile.GetText()?.ToString();
@@ -169,7 +169,7 @@ namespace {nameSpace}
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
return;
continue;
}
try
@@ -189,7 +189,7 @@ namespace {nameSpace}
DiagnosticSeverity.Error,
true),
typeSymbol.Locations[0]));
return;
continue;
}
}
}

View File

@@ -15,7 +15,6 @@ using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Utility;
namespace Robust.Client
@@ -50,16 +49,28 @@ namespace Robust.Client
/// <inheritdoc />
public void Initialize()
{
_net.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME, HandleSetTickRate);
_net.Connected += OnConnected;
_net.ConnectFailed += OnConnectFailed;
_net.Disconnect += OnNetDisconnect;
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged);
_playMan.Initialize();
_debugDrawMan.Initialize();
Reset();
}
private void TickRateChanged(int tickrate)
{
if (GameInfo != null)
{
GameInfo.TickRate = (byte) tickrate;
}
_timing.TickRate = (byte) tickrate;
Logger.InfoS("client", $"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
}
/// <inheritdoc />
public void ConnectToServer(DnsEndPoint endPoint)
{
@@ -119,11 +130,6 @@ namespace Robust.Client
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
info.ServerMaxPlayers = maxPlayers;
var tickrate = _configManager.GetCVar<int>("net.tickrate");
info.TickRate = (byte) tickrate;
_timing.TickRate = (byte) tickrate;
Logger.InfoS("client", $"Tickrate changed to: {tickrate}");
var userName = _net.ServerChannel!.UserName;
var userId = _net.ServerChannel.UserId;
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
@@ -168,6 +174,7 @@ namespace Robust.Client
private void Reset()
{
_configManager.ClearReceivedInitialNwVars();
OnRunLevelChanged(ClientRunLevel.Initialize);
}
@@ -194,12 +201,6 @@ namespace Robust.Client
Reset();
}
private void HandleSetTickRate(MsgSetTickRate message)
{
_timing.TickRate = message.NewTickRate;
Logger.InfoS("client", $"Tickrate changed to: {message.NewTickRate} on tick {_timing.CurTick}");
}
private void OnLocalStatusChanged(object? obj, StatusEventArgs eventArgs)
{
// player finished fully connecting to the server.

View File

@@ -709,7 +709,8 @@ namespace Robust.Client.Console.Commands
public bool Execute(IDebugConsole console, params string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
mgr.Enabled = !mgr.Enabled;
if (!mgr.LockConsoleAccess)
mgr.Enabled = !mgr.Enabled;
return false;
}
}
@@ -722,10 +723,12 @@ namespace Robust.Client.Console.Commands
public bool Execute(IDebugConsole console, params string[] args)
{
var mgr = IoCManager.Resolve<IEyeManager>();
if (mgr.CurrentEye != null)
mgr.CurrentEye.DrawFov = !mgr.CurrentEye.DrawFov;
return false;
var lmgr = IoCManager.Resolve<ILightManager>();
var mgr = IoCManager.Resolve<IEyeManager>();
if (!lmgr.LockConsoleAccess)
if (mgr.CurrentEye != null)
mgr.CurrentEye.DrawFov = !mgr.CurrentEye.DrawFov;
return false;
}
}
@@ -738,7 +741,8 @@ namespace Robust.Client.Console.Commands
public bool Execute(IDebugConsole console, params string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
mgr.DrawHardFov = !mgr.DrawHardFov;
if (!mgr.LockConsoleAccess)
mgr.DrawHardFov = !mgr.DrawHardFov;
return false;
}
}
@@ -752,7 +756,22 @@ namespace Robust.Client.Console.Commands
public bool Execute(IDebugConsole console, params string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
mgr.DrawShadows = !mgr.DrawShadows;
if (!mgr.LockConsoleAccess)
mgr.DrawShadows = !mgr.DrawShadows;
return false;
}
}
internal class ToggleLightBuf : IConsoleCommand
{
public string Command => "togglelightbuf";
public string Description => "Toggles lighting rendering. This includes shadows but not FOV.";
public string Help => "togglelightbuf";
public bool Execute(IDebugConsole console, params string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
if (!mgr.LockConsoleAccess)
mgr.DrawLighting = !mgr.DrawLighting;
return false;
}
}

View File

@@ -27,8 +27,6 @@ namespace Robust.Client.Graphics.Clyde
private const string UniProjViewMatrices = "projectionViewMatrices";
private const string UniUniformConstants = "uniformConstants";
private static readonly Color AmbientLightColor = Color.Black;
private const int BindingIndexProjView = 0;
private const int BindingIndexUniformConstants = 1;

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Buffers;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
@@ -30,7 +31,11 @@ namespace Robust.Client.Graphics.Clyde
// Horizontal width, in pixels, of the shadow maps used to render FOV.
// I figured this was more accuracy sensitive than lights so resolution is significantly higher.
private const int FovMapSize = 2048;
private const int MaxLightsPerScene = 128;
// The maximum possible amount of lights in the light list.
// In the average case, the only cost of increasing this value is memory.
// If you are ever in a situation where this value needs to be increased, however, it will also implicitly cost some CPU time to sort the additional lights.
private const int LightsToRenderListSize = 2048;
private ClydeShaderInstance _fovDebugShaderInstance = default!;
@@ -80,16 +85,18 @@ namespace Robust.Client.Graphics.Clyde
// For depth calculation of lighting shadows.
private RenderTexture _shadowRenderTarget = default!;
// Used because otherwise a MaxLightsPerScene change callback getting hit on startup causes interesting issues (read: bugs)
private bool _shadowRenderTargetCanInitializeSafely = false;
// Proxies to textures of the above render targets.
private ClydeTexture FovTexture => _fovRenderTarget.Texture;
private ClydeTexture ShadowTexture => _shadowRenderTarget.Texture;
private readonly (PointLightComponent light, Vector2 pos)[] _lightsToRenderList
= new (PointLightComponent light, Vector2 pos)[MaxLightsPerScene];
private (PointLightComponent light, Vector2 pos, float distanceSquared)[] _lightsToRenderList = new (PointLightComponent light, Vector2 pos, float distanceSquared)[LightsToRenderListSize];
private unsafe void InitLighting()
{
// Other...
LoadLightingShaders();
{
@@ -160,10 +167,8 @@ namespace Robust.Client.Graphics.Clyde
}
// Shadow FBO.
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, MaxLightsPerScene),
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
nameof(_shadowRenderTarget));
_shadowRenderTargetCanInitializeSafely = true;
MaxLightsPerSceneChanged(_maxLightsPerScene);
}
private void LoadLightingShaders()
@@ -334,6 +339,14 @@ namespace Robust.Client.Graphics.Clyde
DrawFov(viewport, eye);
if (!_lightManager.DrawLighting)
{
BindRenderTargetFull(viewport.RenderTarget);
GL.Viewport(0, 0, viewport.Size.X, viewport.Size.Y);
CheckGlError();
return;
}
using (DebugGroup("Draw shadow depth"))
{
PrepareDepthDraw(RtToLoaded(_shadowRenderTarget));
@@ -344,7 +357,7 @@ namespace Robust.Client.Graphics.Clyde
{
for (var i = 0; i < count; i++)
{
var (light, lightPos) = lights[i];
var (light, lightPos, _) = lights[i];
DrawOcclusionDepth(lightPos, ShadowMapSize, light.Radius, i);
}
@@ -355,7 +368,7 @@ namespace Robust.Client.Graphics.Clyde
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(Color.FromSrgb(AmbientLightColor));
GLClearColor(_lightManager.AmbientLightColor);
GL.Clear(ClearBufferMask.ColorBufferBit);
CheckGlError();
@@ -382,7 +395,7 @@ namespace Robust.Client.Graphics.Clyde
for (var i = 0; i < count; i++)
{
var (component, lightPos) = lights[i];
var (component, lightPos, _) = lights[i];
var transform = component.Owner.Transform;
@@ -473,25 +486,24 @@ namespace Robust.Client.Graphics.Clyde
_lightingReady = true;
}
private ((PointLightComponent light, Vector2 pos)[] lights, int count, Box2 expandedBounds)
private ((PointLightComponent light, Vector2 pos, float distanceSquared)[] lights, int count, Box2 expandedBounds)
GetLightsToRender(MapId map, in Box2 worldBounds)
{
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
// As they could still affect the shadows of (large) light sources.
// We expand the world bounds so that it encompasses the center of every light source.
// This should make it so no culled occluder can make a difference.
// (if the occluder is in the current lights at all, it's still not between the light and the world bounds).
var expandedBounds = worldBounds;
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var lightTree = renderingTreeSystem.GetLightTreeForMap(map);
var state = (this, expandedBounds, count: 0);
var state = (this, worldBounds, count: 0);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 expandedBounds, int count) state, in PointLightComponent light) =>
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
{
var transform = light.Owner.Transform;
if (state.count >= LightsToRenderListSize)
{
// There are too many lights to fit in the static memory.
return false;
}
if (!light.Enabled || light.ContainerOccluded)
{
return true;
@@ -501,26 +513,45 @@ namespace Robust.Client.Graphics.Clyde
var circle = new Circle(lightPos, light.Radius);
if (!circle.Intersects(state.expandedBounds))
// If the light doesn't touch anywhere the camera can see, it doesn't matter.
if (!circle.Intersects(state.worldBounds))
{
return true;
}
state.clyde._lightsToRenderList[state.count] = (light, lightPos);
state.count += 1;
state.expandedBounds = state.expandedBounds.ExtendToContain(lightPos);
if (state.count == MaxLightsPerScene)
{
// TODO: Allow more than MaxLightsPerScene lights.
return false;
}
float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared;
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}, expandedBounds);
}, worldBounds);
return (_lightsToRenderList, state.count, state.expandedBounds);
if (state.count > _maxLightsPerScene)
{
// There are too many lights to fit in the scene.
// This check must occur before occluder expansion, or else bad things happen.
// Sort lights by distance.
Array.Sort(_lightsToRenderList, 0, state.count, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}));
// Then effectively delete the furthest lights.
state.count = _maxLightsPerScene;
}
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
// As they could still affect the shadows of (large) light sources.
// We expand the world bounds so that it encompasses the center of every light source.
// This should make it so no culled occluder can make a difference.
// (if the occluder is in the current lights at all, it's still not between the light and the world bounds).
var expandedBounds = worldBounds;
for (var i = 0; i < state.count; i++)
{
var (_, lightPos, _) = _lightsToRenderList[i];
expandedBounds = expandedBounds.ExtendToContain(lightPos);
}
return (_lightsToRenderList, state.count, expandedBounds);
}
private void BlurOntoWalls(Viewport viewport, IEye eye)
@@ -959,6 +990,25 @@ namespace Robust.Client.Graphics.Clyde
RegenAllLightRts();
}
protected override void MaxLightsPerSceneChanged(int newValue)
{
_maxLightsPerScene = newValue;
// This guard is in place because otherwise the shadow FBO is initialized before GL is initialized.
if (!_shadowRenderTargetCanInitializeSafely)
return;
if (_shadowRenderTarget != null)
{
DeleteRenderTexture(_shadowRenderTarget.Handle);
}
// Shadow FBO.
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, _maxLightsPerScene),
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
nameof(_shadowRenderTarget));
}
protected override void SoftShadowsChanged(bool newValue)
{
_enableSoftShadows = newValue;

View File

@@ -63,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
private GLShaderProgram? _currentProgram;
private int _lightmapDivider = 2;
private int _maxLightsPerScene = 128;
private bool _enableSoftShadows = true;
private bool _checkGLErrors;
@@ -133,6 +134,7 @@ namespace Robust.Client.Graphics.Clyde
{
base.ReadConfig();
_lightmapDivider = _configurationManager.GetCVar(CVars.DisplayLightMapDivider);
_maxLightsPerScene = _configurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
_enableSoftShadows = _configurationManager.GetCVar(CVars.DisplaySoftShadows);
}

View File

@@ -34,6 +34,7 @@ namespace Robust.Client.Graphics
_configurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
_configurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
return true;
@@ -76,6 +77,10 @@ namespace Robust.Client.Graphics
{
}
protected virtual void MaxLightsPerSceneChanged(int newValue)
{
}
protected virtual void SoftShadowsChanged(bool newValue)
{
}

View File

@@ -1,4 +1,5 @@
using Robust.Client.Interfaces.Graphics.Lighting;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Lighting
{
@@ -7,5 +8,8 @@ namespace Robust.Client.Graphics.Lighting
public bool Enabled { get; set; } = true;
public bool DrawShadows { get; set; } = true;
public bool DrawHardFov { get; set; } = true;
public bool DrawLighting { get; set; } = true;
public bool LockConsoleAccess { get; set; } = false;
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
}
}

View File

@@ -1,9 +1,32 @@
using Robust.Shared.Maths;
namespace Robust.Client.Interfaces.Graphics.Lighting
{
public interface ILightManager
{
/// <summary>
/// Enables/disables the entire light manager.
/// </summary>
bool Enabled { get; set; }
/// <summary>
/// Enables/disables shadows, but lights are still functional.
/// </summary>
bool DrawShadows { get; set; }
/// <summary>
/// Enables/disables hard FOV.
/// </summary>
bool DrawHardFov { get; set; }
/// <summary>
/// Enables/disables everything to do with the lighting buffer, without interfering with hard FOV.
/// </summary>
bool DrawLighting { get; set; }
/// <summary>
/// This is useful to prevent players messing with lighting setup when they shouldn't.
/// </summary>
bool LockConsoleAccess { get; set; }
/// <summary>
/// Ambient light. This is in linear-light, i.e. when providing a fixed colour, you must use Color.FromSrgb(Color.Black)!
/// </summary>
Color AmbientLightColor { get; set; }
}
}

View File

@@ -446,8 +446,8 @@ namespace Robust.Client.UserInterface.Controls
var (vSepActual, hSepActual) = (Vector2i) (Separations * UIScale);
var hSep = _limitDimension == Dimension.Column ? hSepActual : vSepActual;
var vSep = _limitDimension == Dimension.Column ? vSepActual : hSepActual;
var width = _limitDimension == Dimension.Column ? Width : Height;
var height = _limitDimension == Dimension.Column ? Height : Width;
var width = _limitDimension == Dimension.Column ? PixelWidth : PixelHeight;
var height = _limitDimension == Dimension.Column ? PixelHeight : PixelWidth;
var stretchMaxX = width - hSep * (cols - 1);
var stretchMaxY = height - vSep * (rows - 1);

View File

@@ -1,4 +1,3 @@
using JetBrains.Annotations;
using Robust.Client.Graphics.Drawing;
using Robust.Shared.Maths;
@@ -20,7 +19,7 @@ namespace Robust.Client.UserInterface.Controls
protected override void LayoutUpdateOverride()
{
var contentBox = _getStyleBox()?.GetContentBox(PixelSizeBox) ?? SizeBox;
var contentBox = _getStyleBox()?.GetContentBox(PixelSizeBox) ?? PixelSizeBox;
foreach (var child in Children)
{

View File

@@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface.Controls
base.Draw(handle);
var bg = _getBackground();
bg?.Draw(handle, SizeBox);
bg?.Draw(handle, PixelSizeBox);
var fg = _getForeground();
if (fg == null)
@@ -69,10 +69,10 @@ namespace Robust.Client.UserInterface.Controls
return;
}
var minSize = fg.MinimumSize;
var size = Width * GetAsRatio() - minSize.X;
var size = PixelWidth * GetAsRatio() - minSize.X;
if (size > 0)
{
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, Height));
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, PixelHeight));
}
}

View File

@@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface.Controls
{
var oldHeight = _entry.Height;
var oldWidth = _entry.Width;
_entry.Update(font, MaxWidth ?? Width, UIScale);
_entry.Update(font, (MaxWidth ?? Width) * UIScale, UIScale);
if (oldHeight != _entry.Height || MaxWidth != null && _entry.Width != oldWidth)
{
MinimumSizeChanged();

View File

@@ -1,10 +1,12 @@
using System;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.ViewVariables.Editors
{
@@ -13,48 +15,32 @@ namespace Robust.Client.ViewVariables.Editors
protected override Control MakeUI(object? value)
{
DebugTools.Assert(value!.GetType().IsEnum);
var enumVal = (Enum)value;
var enumType = value.GetType();
var enumStorageType = enumType.GetEnumUnderlyingType();
var enumList = Enum.GetValues(enumType);
var hBox = new HBoxContainer
var optionButton = new OptionButton();
foreach (var val in enumList)
{
CustomMinimumSize = new Vector2(200, 0)
};
var label = val?.ToString();
if (label == null)
continue;
optionButton.AddItem(label, Convert.ToInt32(val));
}
var lineEdit = new LineEdit
{
Text = enumVal.ToString(),
Editable = !ReadOnly,
SizeFlagsHorizontal = Control.SizeFlags.FillExpand
};
optionButton.SelectId(Convert.ToInt32(value));
optionButton.Disabled = ReadOnly;
if (!ReadOnly)
{
lineEdit.OnTextEntered += e =>
var underlyingType = Enum.GetUnderlyingType(value.GetType());
optionButton.OnItemSelected += e =>
{
var parseSig = new []{typeof(string), typeof(NumberStyles), typeof(CultureInfo), enumStorageType.MakeByRefType()};
var parseMethod = enumStorageType.GetMethod("TryParse", parseSig);
DebugTools.AssertNotNull(parseMethod);
var parameters = new object?[] {e.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, null};
var parseWorked = (bool)parseMethod!.Invoke(null, parameters)!;
if (parseWorked) // textbox was the underlying type
{
DebugTools.AssertNotNull(parameters[3]);
ValueChanged(parameters[3]);
}
else if(Enum.TryParse(enumType, e.Text, true, out var enumValue))
{
var underlyingVal = Convert.ChangeType(enumValue, enumStorageType);
ValueChanged(underlyingVal);
}
optionButton.SelectId(e.Id);
ValueChanged(Convert.ChangeType(e.Id, underlyingType));
};
}
hBox.AddChild(lineEdit);
return hBox;
return optionButton;
}
}
}

View File

@@ -238,7 +238,6 @@ namespace Robust.Server
{
netMan.Initialize(true);
netMan.StartServer();
netMan.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME);
}
catch (Exception e)
{
@@ -468,7 +467,6 @@ namespace Robust.Server
_time.TickRate = b;
Logger.InfoS("game", $"Tickrate changed to: {b} on tick {_time.CurTick}");
SendTickRateUpdateToClients(b);
});
_time.TickRate = (byte) _config.GetCVar(CVars.NetTickrate);
@@ -478,14 +476,6 @@ namespace Robust.Server
Logger.InfoS("srv", $"Max players: {MaxPlayers}");
}
private void SendTickRateUpdateToClients(byte newTickRate)
{
var msg = _network.CreateNetMessage<MsgSetTickRate>();
msg.NewTickRate = newTickRate;
_network.ServerSendToAll(msg);
}
// called right before main loop returns, do all saving/cleanup in here
private void Cleanup()
{

View File

@@ -5,6 +5,7 @@ using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Player;
using Robust.Server.Interfaces.Timing;
using Robust.Server.Maps;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
@@ -111,7 +112,7 @@ namespace Robust.Server.Console.Commands
{
public string Command => "loadbp";
public string Description => "Loads a blueprint from disk into the game.";
public string Help => "loadbp <MapID> <Path>";
public string Help => "loadbp <MapID> <Path> [storeUids]";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
@@ -141,8 +142,14 @@ namespace Robust.Server.Console.Commands
return;
}
var loadOptions = new MapLoadOptions();
if (args.Length > 2)
{
loadOptions.StoreMapUids = bool.Parse(args[2]);
}
var mapLoader = IoCManager.Resolve<IMapLoader>();
mapLoader.LoadBlueprint(mapId, args[1]);
mapLoader.LoadBlueprint(mapId, args[1], loadOptions);
}
}

View File

@@ -162,7 +162,7 @@ namespace Robust.Server.GameObjects.Components.UserInterface
_stateDirty = true;
}
/// <summary>
/// Switches between closed and open for a specific client.
/// </summary>
@@ -183,8 +183,8 @@ namespace Robust.Server.GameObjects.Components.UserInterface
}
}
/// <summary>
/// Opens this interface for a specific client.
/// </summary>
@@ -263,6 +263,7 @@ namespace Robust.Server.GameObjects.Components.UserInterface
OnClosed?.Invoke(session);
_subscribedSessions.Remove(session);
_playerStateOverrides.Remove(session);
session.PlayerStatusChanged -= OnSessionOnPlayerStatusChanged;
if (_subscribedSessions.Count == 0)
{

View File

@@ -0,0 +1,18 @@
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
/// <summary>
/// Metadata component used to keep consistent UIDs inside map files cross saving.
/// </summary>
/// <remarks>
/// This component stores the previous map UID of entities from map load.
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
/// </remarks>
public sealed class MapSaveIdComponent : Component
{
public override string Name => "MapSaveId";
public int Uid { get; set; }
}
}

View File

@@ -73,6 +73,8 @@ namespace Robust.Server.GameObjects
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif
Register<MapSaveIdComponent>();
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Server.Maps;
using Robust.Shared.Map;
using YamlDotNet.RepresentationModel;
@@ -7,9 +8,11 @@ namespace Robust.Server.Interfaces.Maps
public interface IMapLoader
{
IMapGrid? LoadBlueprint(MapId mapId, string path);
IMapGrid? LoadBlueprint(MapId mapId, string path, MapLoadOptions options);
void SaveBlueprint(GridId gridId, string yamlPath);
void LoadMap(MapId mapId, string path);
void LoadMap(MapId mapId, string path, MapLoadOptions options);
void SaveMap(MapId mapId, string yamlPath);
event Action<YamlStream, string> LoadedMapData;

View File

@@ -0,0 +1,11 @@
namespace Robust.Server.Maps
{
public sealed class MapLoadOptions
{
/// <summary>
/// If true, UID components will be created for loaded entities
/// to maintain consistency upon subsequent savings.
/// </summary>
public bool StoreMapUids { get; set; }
}
}

View File

@@ -16,6 +16,7 @@ using Robust.Shared.GameObjects;
using System.Globalization;
using Robust.Shared.Interfaces.GameObjects;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.Interfaces.Serialization;
@@ -29,6 +30,8 @@ namespace Robust.Server.Maps
/// </summary>
public class MapLoader : IMapLoader
{
private static readonly MapLoadOptions DefaultLoadOptions = new();
private const int MapFormatVersion = 2;
[Dependency] private readonly IResourceManager _resMan = default!;
@@ -46,7 +49,8 @@ namespace Robust.Server.Maps
{
var grid = _mapManager.GetGrid(gridId);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
_componentManager, _prototypeManager);
context.RegisterGrid(grid);
var root = context.Serialize();
var document = new YamlDocument(root);
@@ -68,6 +72,11 @@ namespace Robust.Server.Maps
/// <inheritdoc />
public IMapGrid? LoadBlueprint(MapId mapId, string path)
{
return LoadBlueprint(mapId, path, DefaultLoadOptions);
}
public IMapGrid? LoadBlueprint(MapId mapId, string path, MapLoadOptions options)
{
TextReader reader;
var resPath = new ResourcePath(path).ToRootedPath();
@@ -108,7 +117,8 @@ namespace Robust.Server.Maps
throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
}
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager, (YamlMappingNode)data.RootNode, mapId);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
_componentManager, _prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
context.Deserialize();
grid = context.Grids[0];
@@ -128,7 +138,8 @@ namespace Robust.Server.Maps
public void SaveMap(MapId mapId, string yamlPath)
{
Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
_componentManager, _prototypeManager);
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
context.RegisterGrid(grid);
@@ -149,11 +160,16 @@ namespace Robust.Server.Maps
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
}
}
Logger.InfoS("map", "Save completed!");
}
/// <inheritdoc />
public void LoadMap(MapId mapId, string path)
{
LoadMap(mapId, path, DefaultLoadOptions);
}
public void LoadMap(MapId mapId, string path, MapLoadOptions options)
{
TextReader reader;
var resPath = new ResourcePath(path).ToRootedPath();
@@ -188,7 +204,8 @@ namespace Robust.Server.Maps
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager, (YamlMappingNode)data.RootNode, mapId);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
_componentManager, _prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
context.Deserialize();
if (!context.MapIsPostInit && _pauseManager.IsMapInitialized(mapId))
@@ -213,6 +230,7 @@ namespace Robust.Server.Maps
private readonly IComponentManager _componentManager;
private readonly IPrototypeManager _prototypeManager;
private readonly MapLoadOptions? _loadOptions;
private readonly Dictionary<GridId, int> GridIDMap = new();
public readonly List<IMapGrid> Grids = new();
@@ -225,8 +243,6 @@ namespace Robust.Server.Maps
private bool IsBlueprintMode => GridIDMap.Count == 1;
private int uidCounter;
private readonly YamlMappingNode RootNode;
private readonly MapId TargetMap;
@@ -239,7 +255,9 @@ namespace Robust.Server.Maps
public bool MapIsPostInit { get; private set; }
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs, IServerEntityManagerInternal entities, IPauseManager pauseManager, IComponentManager componentManager, IPrototypeManager prototypeManager)
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities, IPauseManager pauseManager, IComponentManager componentManager,
IPrototypeManager prototypeManager)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
@@ -251,14 +269,17 @@ namespace Robust.Server.Maps
RootNode = new YamlMappingNode();
}
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs, IServerEntityManagerInternal entities,
IPauseManager pauseManager, IComponentManager componentManager, IPrototypeManager prototypeManager, YamlMappingNode node, MapId targetMapId)
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities,
IPauseManager pauseManager, IComponentManager componentManager, IPrototypeManager prototypeManager,
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_pauseManager = pauseManager;
_componentManager = componentManager;
_loadOptions = options;
RootNode = node;
TargetMap = targetMapId;
@@ -438,8 +459,8 @@ namespace Robust.Server.Maps
var newId = new GridId?();
YamlGridSerializer.DeserializeGrid(
_mapManager, TargetMap, ref newId,
(YamlMappingNode)grid["settings"],
(YamlSequenceNode)grid["chunks"],
(YamlMappingNode) grid["settings"],
(YamlSequenceNode) grid["chunks"],
_tileMap!,
_tileDefinitionManager
);
@@ -489,6 +510,12 @@ namespace Robust.Server.Maps
Entities.Add(entity);
UidEntityMap.Add(uid, entity.Uid);
_entitiesToDeserialize.Add((entity, entityDef));
if (_loadOptions!.StoreMapUids)
{
var comp = entity.AddComponent<MapSaveIdComponent>();
comp.Uid = uid;
}
}
}
@@ -501,7 +528,7 @@ namespace Robust.Server.Maps
{
foreach (var compData in componentList)
{
CurrentReadingEntityComponents[compData["type"].AsString()] = (YamlMappingNode)compData;
CurrentReadingEntityComponents[compData["type"].AsString()] = (YamlMappingNode) compData;
}
}
@@ -605,15 +632,55 @@ namespace Robust.Server.Maps
private void PopulateEntityList()
{
var withUid = new List<MapSaveIdComponent>();
var withoutUid = new List<IEntity>();
var takenIds = new HashSet<int>();
foreach (var entity in _serverEntityManager.GetEntities())
{
if (IsMapSavable(entity))
{
var uid = uidCounter++;
EntityUidMap.Add(entity.Uid, uid);
Entities.Add(entity);
if (entity.TryGetComponent(out MapSaveIdComponent? mapSaveId))
{
withUid.Add(mapSaveId);
}
else
{
withoutUid.Add(entity);
}
}
}
// Go over entities with a MapSaveIdComponent and assign those.
foreach (var mapIdComp in withUid)
{
var uid = mapIdComp.Uid;
if (takenIds.Contains(uid))
{
// Duplicate ID. Just pretend it doesn't have an ID and use the without path.
withoutUid.Add(mapIdComp.Owner);
}
else
{
EntityUidMap.Add(mapIdComp.Owner.Uid, uid);
takenIds.Add(uid);
}
}
var uidCounter = 0;
foreach (var entity in withoutUid)
{
while (takenIds.Contains(uidCounter))
{
// Find next available UID.
uidCounter += 1;
}
EntityUidMap.Add(entity.Uid, uidCounter);
takenIds.Add(uidCounter);
}
}
private void WriteEntitySection()
@@ -621,7 +688,7 @@ namespace Robust.Server.Maps
var entities = new YamlSequenceNode();
RootNode.Add("entities", entities);
foreach (var entity in Entities)
foreach (var entity in Entities.OrderBy(e => EntityUidMap[e.Uid]))
{
CurrentWritingEntity = entity;
var mapping = new YamlMappingNode
@@ -638,6 +705,9 @@ namespace Robust.Server.Maps
// See engine#636 for why the Distinct() call.
foreach (var component in entity.GetAllComponents())
{
if (component is MapSaveIdComponent)
continue;
var compMapping = new YamlMappingNode();
CurrentWritingComponent = component.Name;
var compSerializer = YamlObjectSerializer.NewWriter(compMapping, this);
@@ -683,6 +753,7 @@ namespace Robust.Server.Maps
return true;
}
}
if (type == typeof(EntityUid))
{
if (node.AsString() == "null")
@@ -694,7 +765,8 @@ namespace Robust.Server.Maps
var val = node.AsInt();
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.",
val);
}
else
{
@@ -702,12 +774,14 @@ namespace Robust.Server.Maps
return true;
}
}
if (typeof(IEntity).IsAssignableFrom(type))
{
var val = node.AsInt();
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.",
val);
}
else
{
@@ -715,6 +789,7 @@ namespace Robust.Server.Maps
return true;
}
}
obj = null;
return false;
}
@@ -766,6 +841,7 @@ namespace Robust.Server.Maps
return true;
}
}
node = null;
return false;
}
@@ -878,7 +954,7 @@ namespace Robust.Server.Maps
}
Stream = stream;
GridCount = ((YamlSequenceNode)RootNode["grids"]).Children.Count;
GridCount = ((YamlSequenceNode) RootNode["grids"]).Children.Count;
}
}
}

View File

@@ -249,6 +249,9 @@ namespace Robust.Shared
public static readonly CVarDef<int> DisplayLightMapDivider =
CVarDef.Create("display.lightmapdivider", 2, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<int> DisplayMaxLightsPerScene =
CVarDef.Create("display.maxlightsperscene", 128, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<bool> DisplaySoftShadows =
CVarDef.Create("display.softshadows", true, CVar.CLIENTONLY | CVar.ARCHIVE);

View File

@@ -54,6 +54,12 @@ namespace Robust.Shared.Configuration
/// </summary>
void FlushMessages();
/// <summary>
/// Clears internal flag for <see cref="ReceivedInitialNwVars"/>.
/// Must be called upon disconnect.
/// </summary>
void ClearReceivedInitialNwVars();
public event EventHandler ReceivedInitialNwVars;
}
@@ -77,7 +83,7 @@ namespace Robust.Shared.Configuration
_netManager.Connected += PeerConnected;
_netManager.Disconnect += PeerDisconnected;
}
_netManager.RegisterNetMessage<MsgConVars>(MsgConVars.NAME, HandleNetVarMessage);
}
@@ -94,10 +100,16 @@ namespace Robust.Shared.Configuration
private void HandleNetVarMessage(MsgConVars message)
{
if(!_receivedInitialNwVars)
ReceivedInitialNwVars?.Invoke(this, EventArgs.Empty);
{
_receivedInitialNwVars = true;
_receivedInitialNwVars = true;
_netVarsMessages.Add(message);
// apply the initial set immediately, so that they are available to
// for the rest of connection building
ApplyNetVarChange(message.MsgChannel, message.NetworkedVars);
ReceivedInitialNwVars?.Invoke(this, EventArgs.Empty);
}
else
_netVarsMessages.Add(message);
}
/// <inheritdoc />
@@ -135,7 +147,7 @@ namespace Robust.Shared.Configuration
_netVarsMessages.Clear();
}
private void ApplyNetVarChange(INetChannel msgChannel, List<(string name, object value)> networkedVars)
{
Logger.DebugS("cfg", "Handling replicated cvars...");
@@ -285,6 +297,11 @@ namespace Robust.Shared.Configuration
_netManager.ClientSendMessage(msg);
}
public void ClearReceivedInitialNwVars()
{
_receivedInitialNwVars = false;
}
private List<(string name, object value)> GetReplicatedVars()
{
var nwVars = new List<(string name, object value)>();
@@ -292,7 +309,7 @@ namespace Robust.Shared.Configuration
foreach (var cVar in _configVars.Values)
{
if (!cVar.Registered)
return nwVars;
continue;
if ((cVar.Flags & CVar.REPLICATED) == 0)
continue;

View File

@@ -206,8 +206,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public void RemoveComponents(EntityUid uid)
{
_entCompIndex.Remove(uid);
foreach (var comp in InSafeOrder(_entCompIndex[uid]))
{
RemoveComponentDeferred(comp, uid, false);
@@ -221,6 +219,10 @@ namespace Robust.Shared.GameObjects
{
RemoveComponentDeferred(comp, uid, true);
}
// DisposeComponents means the entity is getting deleted.
// Safe to wipe the entity out of the index.
_entCompIndex.Remove(uid);
}
private void RemoveComponentDeferred(Component component, EntityUid uid, bool removeProtected)
@@ -306,6 +308,7 @@ namespace Robust.Shared.GameObjects
var netId = component.NetID.Value;
_entNetIdDict[netId].Remove(entityUid);
_entCompIndex.Remove(entityUid, component);
// mark the owning entity as dirty for networking
component.Owner.Dirty();

View File

@@ -1,28 +0,0 @@
using Lidgren.Network;
using Robust.Shared.Interfaces.Network;
#nullable disable
namespace Robust.Shared.Network.Messages
{
public class MsgSetTickRate : NetMessage
{
#region REQUIRED
public static readonly MsgGroups GROUP = MsgGroups.Core;
public static readonly string NAME = nameof(MsgSetTickRate);
public MsgSetTickRate(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public byte NewTickRate { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
NewTickRate = buffer.ReadByte();
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(NewTickRate);
}
}
}

View File

@@ -685,7 +685,21 @@ namespace Robust.Shared.Network
_assignedUsernames.Remove(channel.UserName);
_assignedUserIds.Remove(channel.UserId);
OnDisconnected(channel, reason);
#if EXCEPTION_TOLERANCE
try
{
#endif
OnDisconnected(channel, reason);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
// A throw aborting in the middle of this method would be *really* bad
// and cause fun bugs like ghost clients sticking around.
// I say "would" as if it hasn't already happened...
Logger.ErrorS("net", "Caught exception in OnDisconnected handler:\n{0}", e);
}
#endif
_channels.Remove(connection);
peer.RemoveChannel(channel);
@@ -1004,6 +1018,7 @@ namespace Robust.Shared.Network
{
await conn(args);
}
return args;
}