mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd01ca924b | ||
|
|
38ad8ce132 | ||
|
|
ee440c2df9 | ||
|
|
32f3c863fb | ||
|
|
b00e0bef5a | ||
|
|
0a09b27918 | ||
|
|
c9f6a4e32a | ||
|
|
b205a14f69 | ||
|
|
d5f3292e0a | ||
|
|
561e4b330e | ||
|
|
36a5d102ff | ||
|
|
b9c39e0953 | ||
|
|
ad4c8be132 | ||
|
|
988cbf9a87 | ||
|
|
e26512001a | ||
|
|
8e97982f1e | ||
|
|
3ca686298e | ||
|
|
5e914cb13a | ||
|
|
a1bdfca8ba | ||
|
|
79deaca409 | ||
|
|
2eeb21431b | ||
|
|
c4062bcae9 | ||
|
|
cd3a85ea04 | ||
|
|
d15b5c7f22 | ||
|
|
18bbe2271d | ||
|
|
ee2b7a3a66 | ||
|
|
ca36671131 | ||
|
|
604a1a6960 | ||
|
|
2898f5396f | ||
|
|
39541639c5 |
20
.github/workflows/codeql-analysis.yml
vendored
20
.github/workflows/codeql-analysis.yml
vendored
@@ -11,14 +11,14 @@
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '30 18 * * 6'
|
||||
#on:
|
||||
# push:
|
||||
# branches: [ master ]
|
||||
# pull_request:
|
||||
# # The branches below must be a subset of the branches above
|
||||
# branches: [ master ]
|
||||
# schedule:
|
||||
# - cron: '30 18 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -38,12 +38,12 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
// On net472, we rely on Mono's DllMap for this. See the .dll.config file.
|
||||
NativeLibrary.SetDllImportResolver(typeof(GLFWNative).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
// Please keep in sync with what Robust.Shared/DllMapHelper.cs does.
|
||||
if (name != "glfw3.dll")
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Interfaces;
|
||||
using Robust.Client.Interfaces.Debugging;
|
||||
@@ -7,15 +7,14 @@ using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Utility;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
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
|
||||
@@ -25,7 +24,7 @@ namespace Robust.Client
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
@@ -50,18 +49,28 @@ namespace Robust.Client
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgServerInfo>(MsgServerInfo.NAME, HandleServerInfo);
|
||||
_net.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME, HandleSetTickRate);
|
||||
_net.RegisterNetMessage<MsgServerInfoReq>(MsgServerInfoReq.NAME);
|
||||
_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)
|
||||
{
|
||||
@@ -98,9 +107,39 @@ namespace Robust.Client
|
||||
|
||||
private void OnConnected(object? sender, NetChannelArgs args)
|
||||
{
|
||||
// request base info about the server
|
||||
var msgInfo = _net.CreateNetMessage<MsgServerInfoReq>();
|
||||
_net.ClientSendMessage(msgInfo);
|
||||
_configManager.SyncWithServer();
|
||||
_configManager.ReceivedInitialNwVars += OnReceivedClientData;
|
||||
}
|
||||
|
||||
private void OnReceivedClientData(object? sender, EventArgs e)
|
||||
{
|
||||
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
|
||||
|
||||
var info = GameInfo;
|
||||
|
||||
var serverName = _configManager.GetCVar<string>("game.hostname");
|
||||
if (info == null)
|
||||
{
|
||||
GameInfo = info = new ServerInfo(serverName);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ServerName = serverName;
|
||||
}
|
||||
|
||||
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
|
||||
info.ServerMaxPlayers = maxPlayers;
|
||||
|
||||
var userName = _net.ServerChannel!.UserName;
|
||||
var userId = _net.ServerChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,6 +174,7 @@ namespace Robust.Client
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_configManager.ClearReceivedInitialNwVars();
|
||||
OnRunLevelChanged(ClientRunLevel.Initialize);
|
||||
}
|
||||
|
||||
@@ -152,6 +192,7 @@ namespace Robust.Client
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
@@ -160,42 +201,6 @@ namespace Robust.Client
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void HandleServerInfo(MsgServerInfo msg)
|
||||
{
|
||||
var info = GameInfo;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
GameInfo = info = new ServerInfo(msg.ServerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ServerName = msg.ServerName;
|
||||
}
|
||||
|
||||
info.ServerMaxPlayers = msg.ServerMaxPlayers;
|
||||
info.TickRate = msg.TickRate;
|
||||
_timing.TickRate = msg.TickRate;
|
||||
Logger.InfoS("client", $"Tickrate changed to: {msg.TickRate}");
|
||||
|
||||
var userName = msg.MsgChannel.UserName;
|
||||
var userId = msg.MsgChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@@ -164,6 +164,7 @@ namespace Robust.Client
|
||||
|
||||
_userInterfaceManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Client.Interfaces;
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
@@ -41,7 +42,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
@@ -57,7 +58,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
public bool Predicting { get; private set; }
|
||||
|
||||
public int PredictSize { get; private set; }
|
||||
public int PredictTickBias { get; private set; }
|
||||
public float PredictLagBias { get; private set; }
|
||||
|
||||
public int StateBufferMergeThreshold { get; private set; }
|
||||
|
||||
@@ -82,14 +84,16 @@ namespace Robust.Client.GameStates
|
||||
_config.OnValueChanged(CVars.NetInterpRatio, i => _processor.InterpRatio = i, true);
|
||||
_config.OnValueChanged(CVars.NetLogging, b => _processor.Logging = b, true);
|
||||
_config.OnValueChanged(CVars.NetPredict, b => Predicting = b, true);
|
||||
_config.OnValueChanged(CVars.NetPredictSize, i => PredictSize = i, true);
|
||||
_config.OnValueChanged(CVars.NetPredictTickBias, i => PredictTickBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetPredictLagBias, i => PredictLagBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetStateBufMergeThreshold, i => StateBufferMergeThreshold = i, true);
|
||||
|
||||
_processor.Interpolation = _config.GetCVar(CVars.NetInterp);
|
||||
_processor.InterpRatio = _config.GetCVar(CVars.NetInterpRatio);
|
||||
_processor.Logging = _config.GetCVar(CVars.NetLogging);
|
||||
Predicting = _config.GetCVar(CVars.NetPredict);
|
||||
PredictSize = _config.GetCVar(CVars.NetPredictSize);
|
||||
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
|
||||
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -174,7 +178,7 @@ namespace Robust.Client.GameStates
|
||||
var i = 0;
|
||||
for (; i < applyCount; i++)
|
||||
{
|
||||
_timing.CurTick = _lastProcessedTick + 1;
|
||||
_timing.LastRealTick = _timing.CurTick = _lastProcessedTick + 1;
|
||||
|
||||
// TODO: We could theoretically communicate with the GameStateProcessor better here.
|
||||
// Since game states are sliding windows, it is possible that we need less than applyCount applies here.
|
||||
@@ -256,9 +260,9 @@ namespace Robust.Client.GameStates
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f; // seconds.
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictSize;
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
@@ -377,6 +381,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
|
||||
// xy: A, zw: B
|
||||
varying highp vec4 fragPos;
|
||||
// x: actual angle, y: horizontal (1) / vertical (-1)
|
||||
varying highp vec2 fragAngle;
|
||||
// x: Angle being queried, y: Angle of closest point of line (is of 90-degree angle to line angle), z: Distance at y
|
||||
varying highp vec3 fragControl;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Stuff that needs to be inferred to avoid interpolation issues.
|
||||
highp vec2 rayNormal = vec2(cos(fragAngle.x), -sin(fragAngle.x));
|
||||
|
||||
// Depth calculation accounting for interpolation.
|
||||
highp float dist;
|
||||
|
||||
if (fragAngle.y > 0.0) {
|
||||
// Line is horizontal
|
||||
dist = abs(fragPos.y / rayNormal.y);
|
||||
} else {
|
||||
// Line is vertical
|
||||
dist = abs(fragPos.x / rayNormal.x);
|
||||
}
|
||||
// Thanks to Radrark for finding this for me. There's also a useful diagram, but this is text, so:
|
||||
// r = p / cos(theta - phi)
|
||||
// r: Distance to line *given angle theta*
|
||||
// p: Distance to closest point of line
|
||||
// theta: Angle being queried
|
||||
// phi: Angle of closest point of line - inherently on 90-degree angle to line angle
|
||||
highp float dist = abs(fragControl.z / cos(fragControl.x - fragControl.y));
|
||||
|
||||
// Main body.
|
||||
#ifdef HAS_DFDX
|
||||
|
||||
@@ -11,10 +11,8 @@ attribute vec4 aPos;
|
||||
// x: deflection(0=A/1=B) y: height
|
||||
attribute vec2 subVertex;
|
||||
|
||||
// xy: A, zw: B
|
||||
varying vec4 fragPos;
|
||||
// x: actual angle, y: horizontal (1) / vertical (-1)
|
||||
varying vec2 fragAngle;
|
||||
// x: actual angle, y: line angle + 90 degrees, z: Distance at y
|
||||
varying vec3 fragControl;
|
||||
|
||||
// Note: This is *not* the standard projectionMatrix!
|
||||
uniform vec2 shadowLightCentre;
|
||||
@@ -70,8 +68,19 @@ void main()
|
||||
}
|
||||
}
|
||||
|
||||
fragPos = vec4(pA, pB);
|
||||
fragAngle = vec2(mix(xA, xB, subVertex.x), abs(pA.x - pB.x) - abs(pA.y - pB.y));
|
||||
float targetAngle = mix(xA, xB, subVertex.x);
|
||||
|
||||
// Calculate the necessary control data for the fragment shader.
|
||||
vec2 lineNormal = pB - pA; // hypothetical: <- would have negative X, zero Y
|
||||
lineNormal /= length(lineNormal);
|
||||
fragControl = vec3(
|
||||
// Angle
|
||||
targetAngle,
|
||||
// Angle Out
|
||||
atan(lineNormal.x, lineNormal.y),
|
||||
// Distance @ Angle Out
|
||||
dot(vec2(lineNormal.y, -lineNormal.x), pA)
|
||||
);
|
||||
|
||||
// Depth divide MUST be implemented here no matter what,
|
||||
// because GLES SL 1.00 doesn't have gl_FragDepth.
|
||||
@@ -79,5 +88,5 @@ void main()
|
||||
// and we don't really need to have correction
|
||||
float zbufferDepth = 1.0 - (1.0 / (length(mix(pA, pB, subVertex.x)) + DEPTH_ZBUFFER_PREDIV_BIAS));
|
||||
|
||||
gl_Position = vec4(mix(xA, xB, subVertex.x) / PI, mix(1.0, -1.0, subVertex.y), zbufferDepth, 1.0);
|
||||
gl_Position = vec4(targetAngle / PI, mix(1.0, -1.0, subVertex.y), zbufferDepth, 1.0);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,22 @@ namespace Robust.Client.Interfaces.UserInterface
|
||||
/// </summary>
|
||||
Stylesheet? Stylesheet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A control can have "keyboard focus" separate from ControlFocused, obtained when calling
|
||||
/// Control.GrabKeyboardFocus. Corresponding events in Control are KeyboardFocusEntered/Exited
|
||||
/// </summary>
|
||||
Control? KeyboardFocused { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A control gets "ControlFocused" when a mouse button (or any KeyBinding which has CanFocus = true) is
|
||||
/// pressed down on the control. While it is focused, it will receive mouse hover events and the corresponding
|
||||
/// keyup event if it still has focus when that occurs (it will NOT receive the keyup if focus has
|
||||
/// been taken by another control). Focus is removed when a different control takes focus
|
||||
/// (such as by pressing a different mouse button down over a different control) or when the keyup event
|
||||
/// happens. When focus is lost on a control, it always fires Control.ControlFocusExited.
|
||||
/// </summary>
|
||||
Control? ControlFocused { get; }
|
||||
|
||||
LayoutContainer StateRoot { get; }
|
||||
|
||||
LayoutContainer WindowRoot { get; }
|
||||
|
||||
@@ -757,14 +757,32 @@ namespace Robust.Client.UserInterface
|
||||
/// <summary>
|
||||
/// Called when this control receives keyboard focus.
|
||||
/// </summary>
|
||||
protected internal virtual void FocusEntered()
|
||||
protected internal virtual void KeyboardFocusEntered()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this control loses keyboard focus.
|
||||
/// Called when this control loses keyboard focus (corresponds to UserInterfaceManager.KeyboardFocused).
|
||||
/// </summary>
|
||||
protected internal virtual void FocusExited()
|
||||
protected internal virtual void KeyboardFocusExited()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a control loses control focus for any reason. See <see cref="IUserInterfaceManager.ControlFocused"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Controls which have some sort of drag / drop behavior should usually implement this method (typically by cancelling the drag drop).
|
||||
/// Otherwise, if a user clicks down LMB over one control to initiate a drag, then clicks RMB down
|
||||
/// over a different control while still holding down LMB, the control being dragged will now lose focus
|
||||
/// and will no longer receive the keyup for the LMB, thus won't cancel the drag.
|
||||
/// This should also be considered for controls which have any special KeyBindUp behavior - consider
|
||||
/// what would happen if the control lost focus and never received the KeyBindUp.
|
||||
///
|
||||
/// There is no corresponding ControlFocusEntered - if a control wants to handle that situation they should simply
|
||||
/// handle KeyBindDown as that's the only way a control would gain focus.
|
||||
/// </remarks>
|
||||
protected internal virtual void ControlFocusExited()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -589,18 +589,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return index;
|
||||
}
|
||||
|
||||
protected internal override void FocusEntered()
|
||||
protected internal override void KeyboardFocusEntered()
|
||||
{
|
||||
base.FocusEntered();
|
||||
base.KeyboardFocusEntered();
|
||||
|
||||
// Reset this so the cursor is always visible immediately after gaining focus..
|
||||
_resetCursorBlink();
|
||||
OnFocusEnter?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
protected internal override void FocusExited()
|
||||
protected internal override void KeyboardFocusExited()
|
||||
{
|
||||
base.FocusExited();
|
||||
base.KeyboardFocusExited();
|
||||
OnFocusExit?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
264
Robust.Client/UserInterface/Controls/RadioOptions.cs
Normal file
264
Robust.Client/UserInterface/Controls/RadioOptions.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public enum RadioOptionsLayout { Horizontal, Vertical }
|
||||
|
||||
public class RadioOptions<T> : Control
|
||||
{
|
||||
private int internalIdCount = 0;
|
||||
|
||||
private readonly List<RadioOptionButtonData<T>> _buttonDataList = new();
|
||||
//private readonly Dictionary<int, int> _idMap = new();
|
||||
private ButtonGroup _buttonGroup = new();
|
||||
private Container _container;
|
||||
|
||||
public string ButtonStyle = string.Empty;
|
||||
public string FirstButtonStyle = string.Empty;
|
||||
public string LastButtonStyle = string.Empty;
|
||||
|
||||
public int ItemCount => _buttonDataList.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever you select a button.
|
||||
///
|
||||
/// Note: You should add optionButtons.Select(args.Id); if you want to actually select the button.
|
||||
/// </summary>
|
||||
public event Action<RadioOptionItemSelectedEventArgs<T>>? OnItemSelected;
|
||||
|
||||
public RadioOptions(RadioOptionsLayout layout)
|
||||
{
|
||||
switch (layout)
|
||||
{
|
||||
case RadioOptionsLayout.Vertical:
|
||||
_container = new VBoxContainer();
|
||||
break;
|
||||
case RadioOptionsLayout.Horizontal:
|
||||
default:
|
||||
_container = new HBoxContainer();
|
||||
break;
|
||||
}
|
||||
|
||||
this.AddChild(_container);
|
||||
}
|
||||
|
||||
public int AddItem(string label, T value, Action<RadioOptionItemSelectedEventArgs<T>>? itemSelectedAction = null)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Text = label,
|
||||
Group = _buttonGroup
|
||||
};
|
||||
|
||||
button.OnPressed += ButtonOnPressed;
|
||||
|
||||
var data = new RadioOptionButtonData<T>(label, value, button)
|
||||
{
|
||||
Id = internalIdCount++
|
||||
};
|
||||
|
||||
if (itemSelectedAction != null)
|
||||
{
|
||||
data.OnItemSelected += itemSelectedAction;
|
||||
}
|
||||
|
||||
_buttonDataList.Add(data);
|
||||
_container.AddChild(button);
|
||||
UpdateFirstAndLastButtonStyle();
|
||||
|
||||
if (_buttonDataList.Count == 1)
|
||||
{
|
||||
Select(data.Id);
|
||||
}
|
||||
return data.Id;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is triggered when the button is pressed via the UI
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
private void ButtonOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Button == obj.Button);
|
||||
if (buttonData != null)
|
||||
{
|
||||
InvokeItemSelected(new RadioOptionItemSelectedEventArgs<T>(buttonData.Id, this));
|
||||
return;
|
||||
}
|
||||
// Not reachable.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var buttonDatum in _buttonDataList)
|
||||
{
|
||||
buttonDatum.Button.OnPressed -= ButtonOnPressed;
|
||||
}
|
||||
_buttonDataList.Clear();
|
||||
_container.Children.Clear();
|
||||
SelectedId = 0;
|
||||
}
|
||||
|
||||
public object? GetItemMetadata(int idx)
|
||||
{
|
||||
return _buttonDataList.FirstOrDefault(bd => bd.Id == idx)?.Metadata;
|
||||
}
|
||||
|
||||
public int SelectedId { get; private set; }
|
||||
public RadioOptionButtonData<T> SelectedButtonData => _buttonDataList.First(bd => bd.Id == SelectedId);
|
||||
public Button SelectedButton => SelectedButtonData.Button;
|
||||
public string SelectedText => SelectedButtonData.Text;
|
||||
public T SelectedValue => SelectedButtonData.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Always will return true if itemId is not found.
|
||||
/// </summary>
|
||||
/// <param name="idx"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsItemDisabled(int idx)
|
||||
{
|
||||
return _buttonDataList.FirstOrDefault(bd => bd.Id == idx)?.Disabled ?? true;
|
||||
}
|
||||
|
||||
public void RemoveItem(int idx)
|
||||
{
|
||||
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data!= null)
|
||||
{
|
||||
data.Button.OnPressed -= ButtonOnPressed;
|
||||
_container.RemoveChild(data.Button);
|
||||
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (buttonData != null)
|
||||
_buttonDataList.Remove(buttonData);
|
||||
|
||||
UpdateFirstAndLastButtonStyle();
|
||||
}
|
||||
}
|
||||
|
||||
public void Select(int idx)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
SelectedId = data.Id;
|
||||
data.Button.Pressed = true;
|
||||
return;
|
||||
}
|
||||
// Not found.
|
||||
}
|
||||
|
||||
public void SelectByValue(T value)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => EqualityComparer<T>.Default.Equals(bd.Value, value));
|
||||
if (data != null)
|
||||
{
|
||||
Select(data.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void InvokeItemSelected(RadioOptionItemSelectedEventArgs<T> args)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == args.Id);
|
||||
if (buttonData == null) return;
|
||||
|
||||
if (buttonData.HasOnItemSelectedEvent)
|
||||
buttonData.InvokeItemSelected(args);
|
||||
else
|
||||
OnItemSelected?.Invoke(args);
|
||||
}
|
||||
|
||||
public void UpdateFirstAndLastButtonStyle()
|
||||
{
|
||||
for (int i = 0; i < _buttonDataList.Count; i++)
|
||||
{
|
||||
var buttonData = _buttonDataList[i];
|
||||
if (buttonData.Button == null) continue;
|
||||
|
||||
buttonData.Button.StyleClasses.Remove(ButtonStyle);
|
||||
buttonData.Button.StyleClasses.Remove(LastButtonStyle);
|
||||
buttonData.Button.StyleClasses.Remove(FirstButtonStyle);
|
||||
|
||||
if (i == 0)
|
||||
buttonData.Button.StyleClasses.Add(FirstButtonStyle);
|
||||
else if (i == _buttonDataList.Count - 1)
|
||||
buttonData.Button.StyleClasses.Add(LastButtonStyle);
|
||||
else
|
||||
buttonData.Button.StyleClasses.Add(ButtonStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItemDisabled(int idx, bool disabled)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
data.Disabled = disabled;
|
||||
data.Button.Disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItemMetadata(int idx, object metadata)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (buttonData != null)
|
||||
buttonData.Metadata = metadata;
|
||||
}
|
||||
|
||||
public void SetItemText(int idx, string text)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
data.Text = text;
|
||||
data.Button.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class RadioOptionItemSelectedEventArgs<T> : EventArgs
|
||||
{
|
||||
public RadioOptions<T> Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the item that has been selected.
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
public RadioOptionItemSelectedEventArgs(int id, RadioOptions<T> button)
|
||||
{
|
||||
Id = id;
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RadioOptionButtonData<T>
|
||||
{
|
||||
public int Id;
|
||||
public string Text;
|
||||
public T Value;
|
||||
public bool Disabled;
|
||||
public object? Metadata;
|
||||
|
||||
public Button Button;
|
||||
|
||||
public RadioOptionButtonData(string text, T value, Button button)
|
||||
{
|
||||
Text = text;
|
||||
this.Button = button;
|
||||
Value = value;
|
||||
}
|
||||
public event Action<RadioOptionItemSelectedEventArgs<T>>? OnItemSelected;
|
||||
public bool HasOnItemSelectedEvent => OnItemSelected != null;
|
||||
public void InvokeItemSelected(RadioOptionItemSelectedEventArgs<T> args)
|
||||
{
|
||||
OnItemSelected?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -179,7 +179,17 @@ namespace Robust.Client.UserInterface
|
||||
return tcs.Task;
|
||||
#else
|
||||
// Luckily, GTK Linux and COM Windows are both happily threaded. Yay!
|
||||
return Task.Run(action);
|
||||
// * Actual attempts to have multiple file dialogs up at the same time, and the resulting crashes,
|
||||
// have shown that at least for GTK+ (Linux), just because it can handle being on any thread doesn't mean it handle being on two at the same time.
|
||||
// Testing system was Ubuntu 20.04.
|
||||
// COM on Windows might handle this, but honestly, who exactly wants to risk it?
|
||||
// In particular this could very well be an swnfd issue.
|
||||
return Task.Run(() =>
|
||||
{
|
||||
lock (this) {
|
||||
return action();
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public Control? KeyboardFocused { get; private set; }
|
||||
|
||||
// When a control receives a mouse down it must also receive a mouse up and mouse moves, always.
|
||||
// So we keep track of which control is "focused" by the mouse.
|
||||
private Control? _controlFocused;
|
||||
public Control? ControlFocused { get; private set; }
|
||||
|
||||
public LayoutContainer StateRoot { get; private set; } = default!;
|
||||
public PopupContainer ModalRoot { get; private set; } = default!;
|
||||
@@ -244,7 +242,8 @@ namespace Robust.Client.UserInterface
|
||||
RemoveModal(top);
|
||||
else
|
||||
{
|
||||
_controlFocused = top;
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = top;
|
||||
return false; // prevent anything besides the top modal control from receiving input
|
||||
}
|
||||
}
|
||||
@@ -260,12 +259,12 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = control;
|
||||
|
||||
_controlFocused = control;
|
||||
|
||||
if (_controlFocused.CanKeyboardFocus && _controlFocused.KeyboardFocusOnClick)
|
||||
if (ControlFocused.CanKeyboardFocus && ControlFocused.KeyboardFocusOnClick)
|
||||
{
|
||||
_controlFocused.GrabKeyboardFocus();
|
||||
ControlFocused.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -273,7 +272,8 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public void HandleCanFocusUp()
|
||||
{
|
||||
_controlFocused = null;
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = null;
|
||||
}
|
||||
|
||||
public void KeyBindDown(BoundKeyEventArgs args)
|
||||
@@ -290,7 +290,7 @@ namespace Robust.Client.UserInterface
|
||||
return;
|
||||
}
|
||||
|
||||
var control = _controlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
var control = ControlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
|
||||
if (control == null)
|
||||
{
|
||||
@@ -311,7 +311,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public void KeyBindUp(BoundKeyEventArgs args)
|
||||
{
|
||||
var control = _controlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
var control = ControlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
if (control == null)
|
||||
{
|
||||
return;
|
||||
@@ -352,7 +352,7 @@ namespace Robust.Client.UserInterface
|
||||
_needUpdateActiveCursor = true;
|
||||
}
|
||||
|
||||
var target = _controlFocused ?? newHovered;
|
||||
var target = ControlFocused ?? newHovered;
|
||||
if (target != null)
|
||||
{
|
||||
var guiArgs = new GUIMouseMoveEventArgs(mouseMoveEventArgs.Relative / UIScale,
|
||||
@@ -368,7 +368,7 @@ namespace Robust.Client.UserInterface
|
||||
private void UpdateActiveCursor()
|
||||
{
|
||||
// Consider mouse input focus first so that dragging windows don't act up etc.
|
||||
var cursorTarget = _controlFocused ?? CurrentlyHovered;
|
||||
var cursorTarget = ControlFocused ?? CurrentlyHovered;
|
||||
|
||||
if (cursorTarget == null)
|
||||
{
|
||||
@@ -478,13 +478,13 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
KeyboardFocused = control;
|
||||
|
||||
KeyboardFocused.FocusEntered();
|
||||
KeyboardFocused.KeyboardFocusEntered();
|
||||
}
|
||||
|
||||
public void ReleaseKeyboardFocus()
|
||||
{
|
||||
var oldFocused = KeyboardFocused;
|
||||
oldFocused?.FocusExited();
|
||||
oldFocused?.KeyboardFocusExited();
|
||||
KeyboardFocused = null;
|
||||
}
|
||||
|
||||
@@ -528,10 +528,9 @@ namespace Robust.Client.UserInterface
|
||||
_clearTooltip();
|
||||
}
|
||||
|
||||
if (control == _controlFocused)
|
||||
{
|
||||
_controlFocused = null;
|
||||
}
|
||||
if (control != ControlFocused) return;
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = null;
|
||||
}
|
||||
|
||||
public void PushModal(Control modal)
|
||||
@@ -569,7 +568,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public void CursorChanged(Control control)
|
||||
{
|
||||
if (control == _controlFocused || control == CurrentlyHovered)
|
||||
if (control == ControlFocused || control == CurrentlyHovered)
|
||||
{
|
||||
_needUpdateActiveCursor = true;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Traits;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -51,7 +52,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
private bool _serverLoaded;
|
||||
|
||||
public ViewVariablesInstanceEntity(IViewVariablesManagerInternal vvm, IResourceCache resCache, IEntityManager entityManager) : base(vvm, resCache)
|
||||
public ViewVariablesInstanceEntity(IViewVariablesManagerInternal vvm, IEntityManager entityManager, IRobustSerializer robustSerializer) : base(vvm, robustSerializer)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
@@ -117,7 +118,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
_tabs.SetTabTitle(TabClientVars, "Client Variables");
|
||||
|
||||
var first = true;
|
||||
foreach (var group in LocalPropertyList(obj, ViewVariablesManager, _resourceCache))
|
||||
foreach (var group in LocalPropertyList(obj, ViewVariablesManager, _robustSerializer))
|
||||
{
|
||||
ViewVariablesTraitMembers.CreateMemberGroupHeader(
|
||||
ref first,
|
||||
@@ -202,7 +203,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
button.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
button.Visible = true;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +232,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
button.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!button.Text.Contains(searchStr, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
button.Visible = false;
|
||||
@@ -314,7 +315,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
foreach (var propertyData in groupMembers)
|
||||
{
|
||||
var propertyEdit = new ViewVariablesPropertyControl(ViewVariablesManager, _resourceCache);
|
||||
var propertyEdit = new ViewVariablesPropertyControl(ViewVariablesManager, _robustSerializer);
|
||||
propertyEdit.SetStyle(otherStyle = !otherStyle);
|
||||
var editor = propertyEdit.SetProperty(propertyData);
|
||||
var selectorChain = new object[] {new ViewVariablesMemberSelector(propertyData.PropertyIndex)};
|
||||
@@ -328,7 +329,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var componentsBlob = await ViewVariablesManager.RequestData<ViewVariablesBlobEntityComponents>(_entitySession, new ViewVariablesRequestEntityComponents());
|
||||
|
||||
_serverComponents.DisposeAllChildren();
|
||||
|
||||
|
||||
_serverComponents.AddChild(_serverComponentsSearchBar = new LineEdit
|
||||
{
|
||||
PlaceHolder = Loc.GetString("Search"),
|
||||
@@ -336,7 +337,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
});
|
||||
|
||||
_serverComponentsSearchBar.OnTextChanged += OnServerComponentsSearchBarChanged;
|
||||
|
||||
|
||||
componentsBlob.ComponentTypes.Sort();
|
||||
|
||||
var componentTypes = componentsBlob.ComponentTypes.AsEnumerable();
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Traits;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -19,8 +20,8 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
public ViewVariablesRemoteSession? Session { get; private set; }
|
||||
public object? Object { get; private set; }
|
||||
|
||||
public ViewVariablesInstanceObject(IViewVariablesManagerInternal vvm, IResourceCache resCache)
|
||||
: base(vvm, resCache) { }
|
||||
public ViewVariablesInstanceObject(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
: base(vvm, robustSerializer) { }
|
||||
|
||||
public override void Initialize(SS14Window window, object obj)
|
||||
{
|
||||
@@ -113,7 +114,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var list = new List<ViewVariablesTrait>(traitData.Count);
|
||||
if (traitData.Contains(ViewVariablesTraits.Members))
|
||||
{
|
||||
list.Add(new ViewVariablesTraitMembers(ViewVariablesManager, _resourceCache));
|
||||
list.Add(new ViewVariablesTraitMembers(ViewVariablesManager, _robustSerializer));
|
||||
}
|
||||
|
||||
if (traitData.Contains(ViewVariablesTraits.Enumerable))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.ViewVariables.Instances;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -11,7 +12,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
internal class ViewVariablesTraitMembers : ViewVariablesTrait
|
||||
{
|
||||
private readonly IViewVariablesManagerInternal _vvm;
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
private VBoxContainer _memberList = default!;
|
||||
|
||||
@@ -22,9 +23,9 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
instance.AddTab("Members", _memberList);
|
||||
}
|
||||
|
||||
public ViewVariablesTraitMembers(IViewVariablesManagerInternal vvm, IResourceCache resourceCache)
|
||||
public ViewVariablesTraitMembers(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
{
|
||||
_resourceCache = resourceCache;
|
||||
_robustSerializer = robustSerializer;
|
||||
_vvm = vvm;
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
{
|
||||
var first = true;
|
||||
foreach (var group in ViewVariablesInstance.LocalPropertyList(Instance.Object,
|
||||
Instance.ViewVariablesManager, _resourceCache))
|
||||
Instance.ViewVariablesManager, _robustSerializer))
|
||||
{
|
||||
CreateMemberGroupHeader(
|
||||
ref first,
|
||||
@@ -64,7 +65,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
|
||||
foreach (var propertyData in groupMembers)
|
||||
{
|
||||
var propertyEdit = new ViewVariablesPropertyControl(_vvm, _resourceCache);
|
||||
var propertyEdit = new ViewVariablesPropertyControl(_vvm, _robustSerializer);
|
||||
propertyEdit.SetStyle(otherStyle = !otherStyle);
|
||||
var editor = propertyEdit.SetProperty(propertyData);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Traits;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -19,12 +20,12 @@ namespace Robust.Client.ViewVariables
|
||||
internal abstract class ViewVariablesInstance
|
||||
{
|
||||
public readonly IViewVariablesManagerInternal ViewVariablesManager;
|
||||
protected readonly IResourceCache _resourceCache;
|
||||
protected readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
protected ViewVariablesInstance(IViewVariablesManagerInternal vvm, IResourceCache resCache)
|
||||
protected ViewVariablesInstance(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
{
|
||||
ViewVariablesManager = vvm;
|
||||
_resourceCache = resCache;
|
||||
_robustSerializer = robustSerializer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +55,7 @@ namespace Robust.Client.ViewVariables
|
||||
}
|
||||
|
||||
protected internal static IEnumerable<IGrouping<Type, Control>> LocalPropertyList(object obj, IViewVariablesManagerInternal vvm,
|
||||
IResourceCache resCache)
|
||||
IRobustSerializer robustSerializer)
|
||||
{
|
||||
var styleOther = false;
|
||||
var type = obj.GetType();
|
||||
@@ -104,7 +105,7 @@ namespace Robust.Client.ViewVariables
|
||||
Value = value
|
||||
};
|
||||
|
||||
var propertyEdit = new ViewVariablesPropertyControl(vvm, resCache);
|
||||
var propertyEdit = new ViewVariablesPropertyControl(vvm, robustSerializer);
|
||||
propertyEdit.SetStyle(styleOther = !styleOther);
|
||||
var editor = propertyEdit.SetProperty(data);
|
||||
editor.OnValueChanged += onValueChanged;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Robust.Client.ViewVariables
|
||||
internal class ViewVariablesManager : ViewVariablesManagerShared, IViewVariablesManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private uint _nextReqId = 1;
|
||||
@@ -214,11 +214,11 @@ namespace Robust.Client.ViewVariables
|
||||
ViewVariablesInstance instance;
|
||||
if (obj is IEntity entity && !entity.Deleted)
|
||||
{
|
||||
instance = new ViewVariablesInstanceEntity(this, _resourceCache, _entityManager);
|
||||
instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = new ViewVariablesInstanceObject(this, _resourceCache);
|
||||
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
|
||||
}
|
||||
|
||||
var window = new SS14Window {Title = "View Variables"};
|
||||
@@ -255,11 +255,11 @@ namespace Robust.Client.ViewVariables
|
||||
ViewVariablesInstance instance;
|
||||
if (type != null && typeof(IEntity).IsAssignableFrom(type))
|
||||
{
|
||||
instance = new ViewVariablesInstanceEntity(this, _resourceCache, _entityManager);
|
||||
instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = new ViewVariablesInstanceObject(this, _resourceCache);
|
||||
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
|
||||
}
|
||||
|
||||
loadingLabel.Dispose();
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.ViewVariables.Editors;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -23,14 +24,14 @@ namespace Robust.Client.ViewVariables
|
||||
private readonly Label _bottomLabel;
|
||||
|
||||
private readonly IViewVariablesManagerInternal _viewVariablesManager;
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
public ViewVariablesPropertyControl(IViewVariablesManagerInternal viewVars, IResourceCache resourceCache)
|
||||
public ViewVariablesPropertyControl(IViewVariablesManagerInternal viewVars, IRobustSerializer robustSerializer)
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
_viewVariablesManager = viewVars;
|
||||
_resourceCache = resourceCache;
|
||||
_robustSerializer = robustSerializer;
|
||||
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
ToolTip = "Click to expand";
|
||||
@@ -68,11 +69,11 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
_bottomLabel.Text = $"Type: {member.TypePretty}";
|
||||
VVPropEditor editor;
|
||||
if (type == null)
|
||||
if (type == null || !_robustSerializer.CanSerialize(type))
|
||||
{
|
||||
// Type is server-side only.
|
||||
// Info whether it's reference or value type can be figured out from the sent value.
|
||||
if (member.Value is ViewVariablesBlobMembers.ServerValueTypeToken)
|
||||
if (type?.IsValueType == true || member.Value is ViewVariablesBlobMembers.ServerValueTypeToken)
|
||||
{
|
||||
// Value type, just display it stringified read-only.
|
||||
editor = new VVPropEditorDummy();
|
||||
@@ -80,7 +81,7 @@ namespace Robust.Client.ViewVariables
|
||||
else
|
||||
{
|
||||
// Has to be a reference type at this point.
|
||||
DebugTools.Assert(member.Value is ViewVariablesBlobMembers.ReferenceToken || member.Value == null);
|
||||
DebugTools.Assert(member.Value is ViewVariablesBlobMembers.ReferenceToken || member.Value == null || type?.IsClass == true || type?.IsInterface == true);
|
||||
editor = _viewVariablesManager.PropertyFor(typeof(object));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Prometheus;
|
||||
using Robust.Server.Console;
|
||||
@@ -37,6 +38,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Server.DataMetrics;
|
||||
using Robust.Server.Log;
|
||||
using Robust.Server.Utility;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Serilog.Debugging;
|
||||
@@ -236,7 +238,6 @@ namespace Robust.Server
|
||||
{
|
||||
netMan.Initialize(true);
|
||||
netMan.StartServer();
|
||||
netMan.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -286,6 +287,8 @@ namespace Robust.Server
|
||||
|
||||
// Initialize Tier 2 services
|
||||
IoCManager.Resolve<IGameTiming>().InSimulation = true;
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
|
||||
_stateManager.Initialize();
|
||||
IoCManager.Resolve<IPlayerManager>().Initialize(MaxPlayers);
|
||||
@@ -320,6 +323,11 @@ namespace Robust.Server
|
||||
|
||||
_stringSerializer.LockStrings();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -459,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);
|
||||
@@ -469,17 +476,11 @@ 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()
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
|
||||
// shut down networking, kicking all players.
|
||||
_network.Shutdown($"Server shutting down: {_shutdownReason}");
|
||||
|
||||
@@ -500,6 +501,11 @@ namespace Robust.Server
|
||||
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
|
||||
|
||||
//TODO: This should prob shutdown all managers in a loop.
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeEndPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
}
|
||||
}
|
||||
|
||||
private string UpdateBps()
|
||||
@@ -528,6 +534,9 @@ namespace Robust.Server
|
||||
ServerCurTick.Set(_time.CurTick.Value);
|
||||
ServerCurTime.Set(_time.CurTime.TotalSeconds);
|
||||
|
||||
// These are always the same on the server, there is no prediction.
|
||||
_time.LastRealTick = _time.CurTick;
|
||||
|
||||
UpdateTitle();
|
||||
|
||||
using (TickUsage.WithLabels("PreEngine").NewTimer())
|
||||
@@ -535,6 +544,11 @@ namespace Robust.Server
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
using (TickUsage.WithLabels("NetworkedCVar").NewTimer())
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().TickProcessMessages();
|
||||
}
|
||||
|
||||
using (TickUsage.WithLabels("Timers").NewTimer())
|
||||
{
|
||||
timerManager.UpdateTimers(frameEventArgs);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,11 @@ namespace Robust.Server.GameObjects
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
Dirty();
|
||||
if (_enabled != value)
|
||||
{
|
||||
_enabled = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
18
Robust.Server/GameObjects/MapSaveIdComponent.cs
Normal file
18
Robust.Server/GameObjects/MapSaveIdComponent.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,8 @@ namespace Robust.Server.GameObjects
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
Register<MapSaveIdComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
Robust.Server/Maps/MapLoadOptions.cs
Normal file
11
Robust.Server/Maps/MapLoadOptions.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Robust.Server.Interfaces;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input;
|
||||
@@ -91,8 +92,6 @@ namespace Robust.Server.Player
|
||||
|
||||
MaxPlayers = maxPlayers;
|
||||
|
||||
_network.RegisterNetMessage<MsgServerInfoReq>(MsgServerInfoReq.NAME, HandleWelcomeMessageReq);
|
||||
_network.RegisterNetMessage<MsgServerInfo>(MsgServerInfo.NAME);
|
||||
_network.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME, HandlePlayerListReq);
|
||||
_network.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME);
|
||||
|
||||
@@ -378,6 +377,8 @@ namespace Robust.Server.Player
|
||||
}
|
||||
|
||||
PlayerCountMetric.Set(PlayerCount);
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().SyncConnectingClient(args.Channel);
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(IPlayerSession session, SessionStatus oldStatus, SessionStatus newStatus)
|
||||
@@ -414,18 +415,6 @@ namespace Robust.Server.Player
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void HandleWelcomeMessageReq(MsgServerInfoReq message)
|
||||
{
|
||||
var channel = message.MsgChannel;
|
||||
var netMsg = channel.CreateNetMessage<MsgServerInfo>();
|
||||
|
||||
netMsg.ServerName = _baseServer.ServerName;
|
||||
netMsg.ServerMaxPlayers = _baseServer.MaxPlayers;
|
||||
netMsg.TickRate = _timing.TickRate;
|
||||
|
||||
channel.SendMessage(netMsg);
|
||||
}
|
||||
|
||||
private void HandlePlayerListReq(MsgPlayerListReq message)
|
||||
{
|
||||
var channel = message.MsgChannel;
|
||||
|
||||
@@ -189,15 +189,17 @@ namespace Robust.Server.ServerStatus
|
||||
_listener!.Stop();
|
||||
}
|
||||
|
||||
#pragma warning disable CS0649
|
||||
[JsonObject(ItemRequired = Required.DisallowNull)]
|
||||
private sealed class BuildInfo
|
||||
{
|
||||
[JsonProperty("engine_version")] public string EngineVersion = default!;
|
||||
[JsonProperty("hash")] public string? Hash;
|
||||
[JsonProperty("download")] public string? Download;
|
||||
[JsonProperty("download")] public string? Download = default;
|
||||
[JsonProperty("fork_id")] public string ForkId = default!;
|
||||
[JsonProperty("version")] public string Version = default!;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
|
||||
private sealed class ContextImpl : IStatusHandlerContext
|
||||
{
|
||||
|
||||
38
Robust.Server/Utility/WindowsTickPeriod.cs
Normal file
38
Robust.Server/Utility/WindowsTickPeriod.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Robust.Server.Utility
|
||||
{
|
||||
internal static class WindowsTickPeriod
|
||||
{
|
||||
private const uint TIMERR_NOERROR = 0;
|
||||
// This is an actual error code my god.
|
||||
private const uint TIMERR_NOCANDO = 97;
|
||||
|
||||
public static void TimeBeginPeriod(uint period)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeBeginPeriod(period);
|
||||
if (ret != TIMERR_NOERROR)
|
||||
throw new InvalidOperationException($"timeBeginPeriod returned error: {ret}");
|
||||
}
|
||||
|
||||
public static void TimeEndPeriod(uint period)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeEndPeriod(period);
|
||||
if (ret != TIMERR_NOERROR)
|
||||
throw new InvalidOperationException($"timeEndPeriod returned error: {ret}");
|
||||
}
|
||||
|
||||
[DllImport("Winmm.dll")]
|
||||
private static extern uint timeBeginPeriod(uint uPeriod);
|
||||
|
||||
[DllImport("Winmm.dll")]
|
||||
private static extern uint timeEndPeriod(uint uPeriod);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Robust.Shared.Scripting
|
||||
{
|
||||
return new EntityCoordinates(EntityUid.Invalid, ((float) x, (float) y));
|
||||
}
|
||||
|
||||
|
||||
return new EntityCoordinates(grid.GridEntityId, ((float) x, (float) y));
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace Robust.Shared.Scripting
|
||||
return getent(eid(i));
|
||||
}
|
||||
|
||||
public T gcm<T>(int i)
|
||||
{
|
||||
return getent(i).GetComponent<T>();
|
||||
}
|
||||
|
||||
public IEntity getent(EntityUid uid)
|
||||
{
|
||||
return ent.GetEntity(uid);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
@@ -59,8 +60,19 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> NetPredict =
|
||||
CVarDef.Create("net.predict", true, CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> NetPredictSize =
|
||||
CVarDef.Create("net.predict_size", 1, CVar.ARCHIVE);
|
||||
public static readonly CVarDef<int> NetPredictTickBias =
|
||||
CVarDef.Create("net.predict_tick_bias", 1, CVar.ARCHIVE);
|
||||
|
||||
// On Windows we default this to 16ms lag bias, to account for time period lag in the Lidgren thread.
|
||||
// Basically due to how time periods work on Windows, messages are (at worst) time period-delayed when sending.
|
||||
// BUT! Lidgren's latency calculation *never* measures this due to how it works.
|
||||
// This broke some prediction calculations quite badly so we bias them to mask it.
|
||||
// This is not necessary on Linux because Linux, for better or worse,
|
||||
// just has the Lidgren thread go absolute brr polling.
|
||||
public static readonly CVarDef<float> NetPredictLagBias = CVarDef.Create(
|
||||
"net.predict_lag_bias",
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0.016f : 0,
|
||||
CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> NetStateBufMergeThreshold =
|
||||
CVarDef.Create("net.state_buf_merge_threshold", 5, CVar.ARCHIVE);
|
||||
@@ -77,6 +89,8 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<int> NetTickrate =
|
||||
CVarDef.Create("net.tickrate", 60, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
public static readonly CVarDef<int> SysWinTickPeriod =
|
||||
CVarDef.Create("sys.win_tick_period", 3, CVar.SERVERONLY);
|
||||
|
||||
#if DEBUG
|
||||
public static readonly CVarDef<float> NetFakeLoss = CVarDef.Create("net.fakeloss", 0f, CVar.CHEAT);
|
||||
@@ -151,10 +165,10 @@ namespace Robust.Shared
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<int> GameMaxPlayers =
|
||||
CVarDef.Create("game.maxplayers", 32, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
CVarDef.Create("game.maxplayers", 32, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
public static readonly CVarDef<string> GameHostName =
|
||||
CVarDef.Create("game.hostname", "MyServer", CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
CVarDef.Create("game.hostname", "MyServer", CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/*
|
||||
* LOG
|
||||
@@ -235,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);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Nett;
|
||||
using Nett;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
using System;
|
||||
@@ -13,12 +13,12 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// Stores and manages global configuration variables.
|
||||
/// </summary>
|
||||
internal sealed class ConfigurationManager : IConfigurationManagerInternal
|
||||
internal class ConfigurationManager : IConfigurationManagerInternal
|
||||
{
|
||||
private const char TABLE_DELIMITER = '.';
|
||||
private readonly Dictionary<string, ConfigVar> _configVars = new();
|
||||
protected readonly Dictionary<string, ConfigVar> _configVars = new();
|
||||
private string? _configFile;
|
||||
private bool _isServer;
|
||||
protected bool _isServer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ConfigurationManager.
|
||||
@@ -79,6 +79,7 @@ namespace Robust.Shared.Configuration
|
||||
else // this is a key, add CVar
|
||||
{
|
||||
// if the CVar has already been registered
|
||||
var tomlValue = TypeConvert(obj);
|
||||
if (_configVars.TryGetValue(tablePath, out var cfgVar))
|
||||
{
|
||||
if ((cfgVar.Flags & CVar.SECURE) != 0)
|
||||
@@ -90,13 +91,14 @@ namespace Robust.Shared.Configuration
|
||||
return;
|
||||
}
|
||||
// overwrite the value with the saved one
|
||||
cfgVar.Value = TypeConvert(obj);
|
||||
cfgVar.Value = tomlValue;
|
||||
cfgVar.ValueChanged?.Invoke(cfgVar.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
//or add another unregistered CVar
|
||||
cfgVar = new ConfigVar(tablePath, null, CVar.NONE) { Value = TypeConvert(obj) };
|
||||
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
|
||||
cfgVar = new ConfigVar(tablePath, 0, CVar.NONE) { Value = tomlValue };
|
||||
_configVars.Add(tablePath, cfgVar);
|
||||
}
|
||||
|
||||
@@ -131,6 +133,8 @@ namespace Robust.Shared.Configuration
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't write if Archive flag is not set.
|
||||
// Don't write if the cVar is the default value.
|
||||
if (!cVar.ConfigModified &&
|
||||
(cVar.Flags & CVar.ARCHIVE) == 0 || value.Equals(cVar.DefaultValue))
|
||||
{
|
||||
@@ -192,6 +196,7 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
|
||||
public void RegisterCVar<T>(string name, T defaultValue, CVar flags = CVar.NONE, Action<T>? onValueChanged = null)
|
||||
where T : notnull
|
||||
{
|
||||
Action<object>? valueChangedDelegate = null;
|
||||
if (onValueChanged != null)
|
||||
@@ -202,7 +207,7 @@ namespace Robust.Shared.Configuration
|
||||
RegisterCVar(name, typeof(T), defaultValue, flags, valueChangedDelegate);
|
||||
}
|
||||
|
||||
private void RegisterCVar(string name, Type type, object? defaultValue, CVar flags, Action<object>? onValueChanged)
|
||||
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags, Action<object>? onValueChanged)
|
||||
{
|
||||
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
|
||||
$"{name}: Enum cvars must have int as underlying type.");
|
||||
@@ -305,7 +310,7 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCVar(string name, object value)
|
||||
public virtual void SetCVar(string name, object value)
|
||||
{
|
||||
SetCVarInternal(name, value);
|
||||
}
|
||||
@@ -394,13 +399,14 @@ namespace Robust.Shared.Configuration
|
||||
else
|
||||
{
|
||||
//or add another unregistered CVar
|
||||
var cVar = new ConfigVar(key, null, CVar.NONE) { OverrideValue = value };
|
||||
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
|
||||
var cVar = new ConfigVar(key, 0, CVar.NONE) { OverrideValue = value };
|
||||
_configVars.Add(key, cVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object ParseOverrideValue(string value, Type? type)
|
||||
private static object ParseOverrideValue(string value, Type? type)
|
||||
{
|
||||
if (type == typeof(int))
|
||||
{
|
||||
@@ -451,7 +457,7 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// Holds the data for a single configuration variable.
|
||||
/// </summary>
|
||||
private class ConfigVar
|
||||
protected class ConfigVar
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a CVar.
|
||||
@@ -461,7 +467,7 @@ namespace Robust.Shared.Configuration
|
||||
/// everything after is the CVar name in the TOML document.</param>
|
||||
/// <param name="defaultValue">The default value of this CVar.</param>
|
||||
/// <param name="flags">Optional flags to modify the behavior of this CVar.</param>
|
||||
public ConfigVar(string name, object? defaultValue, CVar flags)
|
||||
public ConfigVar(string name, object defaultValue, CVar flags)
|
||||
{
|
||||
Name = name;
|
||||
DefaultValue = defaultValue;
|
||||
@@ -476,7 +482,7 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// The default value of this CVar.
|
||||
/// </summary>
|
||||
public object? DefaultValue { get; set; }
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional flags to modify the behavior of this CVar.
|
||||
|
||||
328
Robust.Shared/Configuration/NetConfigurationManager.cs
Normal file
328
Robust.Shared/Configuration/NetConfigurationManager.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
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.Shared.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A networked configuration manager that controls the replication of
|
||||
/// console variables between client and server.
|
||||
/// </summary>
|
||||
public interface INetConfigurationManager : IConfigurationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up the networking for the config manager.
|
||||
/// </summary>
|
||||
void SetupNetworking();
|
||||
|
||||
/// <summary>
|
||||
/// Get a replicated client CVar for a specific client.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">CVar type.</typeparam>
|
||||
/// <param name="channel">channel of the connected client.</param>
|
||||
/// <param name="name">Name of the CVar.</param>
|
||||
/// <returns>Replicated CVar of the client.</returns>
|
||||
T GetClientCVar<T>(INetChannel channel, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the CVars marked with <see cref="CVar.REPLICATED"/> with the client.
|
||||
/// This needs to be called once during the client connection.
|
||||
/// </summary>
|
||||
/// <param name="client">Client's NetChannel to sync replicated CVars with.</param>
|
||||
void SyncConnectingClient(INetChannel client);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the CVars marked with <see cref="CVar.REPLICATED"/> with the server.
|
||||
/// This needs to be called once when connecting.
|
||||
/// </summary>
|
||||
void SyncWithServer();
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick to process any incoming network messages.
|
||||
/// </summary>
|
||||
void TickProcessMessages();
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any NwCVar messages in the receive buffer.
|
||||
/// </summary>
|
||||
void FlushMessages();
|
||||
|
||||
/// <summary>
|
||||
/// Clears internal flag for <see cref="ReceivedInitialNwVars"/>.
|
||||
/// Must be called upon disconnect.
|
||||
/// </summary>
|
||||
void ClearReceivedInitialNwVars();
|
||||
|
||||
public event EventHandler ReceivedInitialNwVars;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="INetConfigurationManager"/>
|
||||
internal class NetConfigurationManager : ConfigurationManager, INetConfigurationManager
|
||||
{
|
||||
[Dependency] private readonly INetManager _netManager = null!;
|
||||
[Dependency] private readonly IGameTiming _timing = null!;
|
||||
|
||||
private readonly Dictionary<INetChannel, Dictionary<string, object>> _replicatedCVars = new();
|
||||
private readonly List<MsgConVars> _netVarsMessages = new();
|
||||
|
||||
public event EventHandler? ReceivedInitialNwVars;
|
||||
private bool _receivedInitialNwVars;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
{
|
||||
if(_isServer)
|
||||
{
|
||||
_netManager.Connected += PeerConnected;
|
||||
_netManager.Disconnect += PeerDisconnected;
|
||||
}
|
||||
|
||||
_netManager.RegisterNetMessage<MsgConVars>(MsgConVars.NAME, HandleNetVarMessage);
|
||||
}
|
||||
|
||||
private void PeerConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
_replicatedCVars.Add(e.Channel, new Dictionary<string, object>());
|
||||
}
|
||||
|
||||
private void PeerDisconnected(object? sender, NetDisconnectedArgs e)
|
||||
{
|
||||
_replicatedCVars.Remove(e.Channel);
|
||||
}
|
||||
|
||||
private void HandleNetVarMessage(MsgConVars message)
|
||||
{
|
||||
if(!_receivedInitialNwVars)
|
||||
{
|
||||
_receivedInitialNwVars = true;
|
||||
|
||||
// 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 />
|
||||
public void TickProcessMessages()
|
||||
{
|
||||
if(!_timing.InSimulation || _timing.InPrediction)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _netVarsMessages.Count; i++)
|
||||
{
|
||||
var msg = _netVarsMessages[i];
|
||||
|
||||
if (msg.Tick > _timing.LastRealTick)
|
||||
continue;
|
||||
|
||||
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars);
|
||||
|
||||
if(msg.Tick < _timing.LastRealTick)
|
||||
Logger.WarningS("cfg", $"{msg.MsgChannel}: Received late nwVar message ({msg.Tick} < {_timing.LastRealTick} ).");
|
||||
|
||||
_netVarsMessages.RemoveSwap(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FlushMessages()
|
||||
{
|
||||
_netVarsMessages.Sort(((a, b) => a.Tick.Value.CompareTo(b.Tick.Value)));
|
||||
|
||||
foreach (var msg in _netVarsMessages)
|
||||
{
|
||||
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars);
|
||||
}
|
||||
|
||||
_netVarsMessages.Clear();
|
||||
}
|
||||
|
||||
private void ApplyNetVarChange(INetChannel msgChannel, List<(string name, object value)> networkedVars)
|
||||
{
|
||||
Logger.DebugS("cfg", "Handling replicated cvars...");
|
||||
|
||||
foreach (var (name, value) in networkedVars)
|
||||
{
|
||||
if (_netManager.IsClient) // Server sent us a CVar update.
|
||||
{
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value);
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else // Client sent us a CVar update
|
||||
{
|
||||
if (!_configVars.TryGetValue(name, out var cVar))
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cVar.Registered)
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if((cVar.Flags & CVar.REPLICATED) != 0)
|
||||
{
|
||||
var clientCVars = _replicatedCVars[msgChannel];
|
||||
|
||||
if (clientCVars.ContainsKey(name))
|
||||
clientCVars[name] = value;
|
||||
else
|
||||
clientCVars.Add(name, value);
|
||||
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetClientCVar<T>(INetChannel channel, string name)
|
||||
{
|
||||
if (!_configVars.TryGetValue(name, out var cVar) || !cVar.Registered)
|
||||
throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'");
|
||||
|
||||
if (_replicatedCVars.TryGetValue(channel, out var clientCVars) && clientCVars.TryGetValue(name, out var value))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
return (T)(cVar.DefaultValue!);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetCVar(string name, object value)
|
||||
{
|
||||
if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered)
|
||||
{
|
||||
if (_netManager.IsClient)
|
||||
{
|
||||
if (_netManager.IsConnected)
|
||||
{
|
||||
if ((cVar.Flags & CVar.NOT_CONNECTED) != 0)
|
||||
{
|
||||
Logger.WarningS("cfg", $"'{name}' can only be changed when not connected to a server.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((cVar.Flags & CVar.SERVER) != 0)
|
||||
{
|
||||
Logger.WarningS("cfg", $"Only the server can change '{name}'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidConfigurationException($"Trying to set unregistered variable '{name}'");
|
||||
}
|
||||
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value);
|
||||
|
||||
var cvar = _configVars[name];
|
||||
|
||||
// replicate if needed
|
||||
if (_netManager.IsClient)
|
||||
{
|
||||
if ((cvar.Flags & CVar.REPLICATED) == 0)
|
||||
return;
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = new List<(string name, object value)>
|
||||
{
|
||||
(name, value)
|
||||
};
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
else // Server
|
||||
{
|
||||
if ((cvar.Flags & CVar.REPLICATED) == 0)
|
||||
return;
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = new List<(string name, object value)>
|
||||
{
|
||||
(name, value)
|
||||
};
|
||||
_netManager.ServerSendToAll(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SyncConnectingClient(INetChannel client)
|
||||
{
|
||||
DebugTools.Assert(_netManager.IsConnected);
|
||||
DebugTools.Assert(_netManager.IsServer);
|
||||
|
||||
Logger.InfoS("cfg", $"{client}: Sending server info...");
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = GetReplicatedVars();
|
||||
_netManager.ServerSendMessage(msg, client);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SyncWithServer()
|
||||
{
|
||||
DebugTools.Assert(_netManager.IsConnected);
|
||||
DebugTools.Assert(_netManager.IsClient);
|
||||
|
||||
Logger.InfoS("cfg", "Sending client info...");
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = GetReplicatedVars();
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
public void ClearReceivedInitialNwVars()
|
||||
{
|
||||
_receivedInitialNwVars = false;
|
||||
}
|
||||
|
||||
private List<(string name, object value)> GetReplicatedVars()
|
||||
{
|
||||
var nwVars = new List<(string name, object value)>();
|
||||
|
||||
foreach (var cVar in _configVars.Values)
|
||||
{
|
||||
if (!cVar.Registered)
|
||||
continue;
|
||||
|
||||
if ((cVar.Flags & CVar.REPLICATED) == 0)
|
||||
continue;
|
||||
|
||||
if (_netManager.IsClient && (cVar.Flags & CVar.SERVER) != 0)
|
||||
continue;
|
||||
|
||||
nwVars.Add((cVar.Name, cVar.Value ?? cVar.DefaultValue));
|
||||
|
||||
Logger.DebugS("cfg", $"name={cVar.Name}, val={(cVar.Value ?? cVar.DefaultValue)}");
|
||||
}
|
||||
|
||||
return nwVars;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,45 +14,7 @@ namespace Robust.Shared
|
||||
// On .NET Framework this doesn't need to run because:
|
||||
// On Windows, the DLL names should check out correctly to just work.
|
||||
// On Linux/macOS, Mono's DllMap handles it for us.
|
||||
#if NETCOREAPP
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// DLL names should line up on Windows by default.
|
||||
// So a hook won't do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.SetDllImportResolver(assembly, (name, _, __) =>
|
||||
{
|
||||
if (name == $"{baseName}.dll")
|
||||
{
|
||||
var assemblyDir = Path.GetDirectoryName(assembly.Location);
|
||||
|
||||
if (assemblyDir == null)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
string libName;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
libName = $"lib{baseName}.so";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
libName = $"lib{baseName}.dylib";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return NativeLibrary.Load(Path.Combine(assemblyDir, libName));
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
});
|
||||
#endif
|
||||
RegisterExplicitMap(assembly, $"{baseName}.dll", $"lib{baseName}.so", $"lib{baseName}.dylib");
|
||||
}
|
||||
|
||||
[Conditional("NETCOREAPP")]
|
||||
@@ -62,32 +24,24 @@ namespace Robust.Shared
|
||||
// On Windows, the DLL names should check out correctly to just work.
|
||||
// On Linux/macOS, Mono's DllMap handles it for us.
|
||||
#if NETCOREAPP
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
NativeLibrary.SetDllImportResolver(assembly, (name, assembly, path) =>
|
||||
{
|
||||
// DLL names should line up on Windows by default.
|
||||
// So a hook won't do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.SetDllImportResolver(assembly, (name, _, __) =>
|
||||
{
|
||||
if (name == baseName)
|
||||
// Please keep in sync with what GLFWNative does.
|
||||
// This particular API is only really used by the MIDI instruments stuff in SS14 right now,
|
||||
// which means when it breaks people don't notice or report.
|
||||
if (name != baseName)
|
||||
{
|
||||
string libName;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
libName = linuxName;
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
libName = macName;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
return NativeLibrary.Load(libName);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return NativeLibrary.Load(linuxName, assembly, path);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return NativeLibrary.Load(macName, assembly, path);
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Robust.Shared.Interfaces.Configuration
|
||||
@@ -24,7 +23,8 @@ namespace Robust.Shared.Interfaces.Configuration
|
||||
/// <param name="defaultValue">The default Value of the CVar.</param>
|
||||
/// <param name="flags">Optional flags to change behavior of the CVar.</param>
|
||||
/// <param name="onValueChanged">Invoked whenever the CVar value changes.</param>
|
||||
void RegisterCVar<T>(string name, T defaultValue, CVar flags = CVar.NONE, Action<T>? onValueChanged = null);
|
||||
void RegisterCVar<T>(string name, T defaultValue, CVar flags = CVar.NONE, Action<T>? onValueChanged = null)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Is the named CVar already registered?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -120,8 +120,21 @@ namespace Robust.Shared.Interfaces.Timing
|
||||
/// </summary>
|
||||
void ResetRealTime();
|
||||
|
||||
/// <summary>
|
||||
/// Is this the first time CurTick has been predicted?
|
||||
/// </summary>
|
||||
bool IsFirstTimePredicted { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Is CurTick ahead of LastRealTick, meaning we are inside predicted ticks?
|
||||
/// </summary>
|
||||
bool InPrediction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The last real non-predicted tick that was processed.
|
||||
/// </summary>
|
||||
GameTick LastRealTick { get; set; }
|
||||
|
||||
void StartPastPrediction();
|
||||
void EndPastPrediction();
|
||||
|
||||
|
||||
143
Robust.Shared/Network/Messages/MsgConVars.cs
Normal file
143
Robust.Shared/Network/Messages/MsgConVars.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
internal class MsgConVars : NetMessage
|
||||
{
|
||||
// Max buffer could potentially be 255 * 128 * 1024 = ~33MB, so if MaxMessageSize starts being a problem it can be increased.
|
||||
private const int MaxMessageSize = 0x4000; // Arbitrarily chosen as a 'sane' value as the maximum size of the entire message.
|
||||
private const int MaxNameSize = 4 * 32; // UTF8 Max char size is 4 bytes, 32 chars.
|
||||
private const int MaxStringValSize = 4 * 256; // UTF8 Max char size is 4 bytes, 256 chars.
|
||||
|
||||
#region REQUIRED
|
||||
public static readonly MsgGroups GROUP = MsgGroups.Command;
|
||||
public static readonly string NAME = nameof(MsgConVars);
|
||||
public MsgConVars(INetChannel channel) : base(NAME, GROUP) { }
|
||||
#endregion
|
||||
|
||||
public GameTick Tick;
|
||||
public List<(string name, object value)> NetworkedVars = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
if(buffer.LengthBytes > MaxMessageSize)
|
||||
Logger.WarningS("net", $"{MsgChannel}: received a large {nameof(MsgConVars)}, {buffer.LengthBytes}B > {MaxMessageSize}B");
|
||||
|
||||
Tick = new GameTick(buffer.ReadVariableUInt32());
|
||||
var nVars = buffer.ReadByte();
|
||||
|
||||
NetworkedVars = new List<(string name, object value)>(nVars);
|
||||
|
||||
for (int i = 0; i < nVars; i++)
|
||||
{
|
||||
// give the string a smaller bounds than int.MaxValue
|
||||
var nameSize = buffer.PeekStringSize();
|
||||
if (0 >= nameSize || nameSize > MaxNameSize)
|
||||
throw new InvalidOperationException($"Cvar name size '{nameSize}' is out of bounds (1-{MaxNameSize} bytes).");
|
||||
|
||||
var name = buffer.ReadString();
|
||||
var valType = (CvarType)buffer.ReadByte();
|
||||
|
||||
object value;
|
||||
switch (valType)
|
||||
{
|
||||
case CvarType.Int:
|
||||
value = buffer.ReadInt32();
|
||||
break;
|
||||
case CvarType.Long:
|
||||
value = buffer.ReadInt64();
|
||||
break;
|
||||
case CvarType.Bool:
|
||||
value = buffer.ReadBoolean();
|
||||
break;
|
||||
case CvarType.String:
|
||||
|
||||
// give the string a smaller bounds than int.MaxValue
|
||||
var strSize = buffer.PeekStringSize();
|
||||
if (0 > strSize || strSize > MaxStringValSize)
|
||||
throw new InvalidOperationException($"Cvar string value size '{nameSize}' for cvar '{name}' is out of bounds (0-{MaxStringValSize} bytes).");
|
||||
|
||||
value = buffer.ReadString();
|
||||
break;
|
||||
case CvarType.Float:
|
||||
value = buffer.ReadFloat();
|
||||
break;
|
||||
case CvarType.Double:
|
||||
value = buffer.ReadDouble();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
NetworkedVars.Add((name, value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
if(NetworkedVars == null)
|
||||
throw new InvalidOperationException($"{nameof(NetworkedVars)} collection is null.");
|
||||
|
||||
if(NetworkedVars.Count > byte.MaxValue)
|
||||
throw new InvalidOperationException($"{nameof(NetworkedVars)} collection count is greater than {short.MaxValue}.");
|
||||
|
||||
buffer.WriteVariableUInt32(Tick.Value);
|
||||
buffer.Write((byte)NetworkedVars.Count);
|
||||
|
||||
foreach (var (name, value) in NetworkedVars)
|
||||
{
|
||||
buffer.Write(name);
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case int val:
|
||||
buffer.Write((byte)CvarType.Int);
|
||||
buffer.Write(val);
|
||||
break;
|
||||
case long val:
|
||||
buffer.Write((byte)CvarType.Long);
|
||||
buffer.Write(val);
|
||||
break;
|
||||
case bool val:
|
||||
buffer.Write((byte)CvarType.Bool);
|
||||
buffer.Write(val);
|
||||
break;
|
||||
case string val:
|
||||
buffer.Write((byte)CvarType.String);
|
||||
buffer.Write(val);
|
||||
break;
|
||||
case float val:
|
||||
buffer.Write((byte)CvarType.Float);
|
||||
buffer.Write(val);
|
||||
break;
|
||||
case double val:
|
||||
buffer.Write((byte)CvarType.Double);
|
||||
buffer.Write(val);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum CvarType : byte
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
None,
|
||||
|
||||
Int,
|
||||
Long,
|
||||
Bool,
|
||||
String,
|
||||
Float,
|
||||
Double
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
public class MsgServerInfo : NetMessage
|
||||
{
|
||||
#region REQUIRED
|
||||
public static readonly MsgGroups GROUP = MsgGroups.Core;
|
||||
public static readonly string NAME = nameof(MsgServerInfo);
|
||||
public MsgServerInfo(INetChannel channel) : base(NAME, GROUP) { }
|
||||
#endregion
|
||||
|
||||
public string ServerName { get; set; }
|
||||
public int ServerMaxPlayers { get; set; }
|
||||
public byte TickRate { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
ServerName = buffer.ReadString();
|
||||
ServerMaxPlayers = buffer.ReadInt32();
|
||||
TickRate = buffer.ReadByte();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
buffer.Write(ServerName);
|
||||
buffer.Write(ServerMaxPlayers);
|
||||
buffer.Write(TickRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
public class MsgServerInfoReq : NetMessage
|
||||
{
|
||||
#region REQUIRED
|
||||
public static readonly MsgGroups GROUP = MsgGroups.Core;
|
||||
public static readonly string NAME = nameof(MsgServerInfoReq);
|
||||
public MsgServerInfoReq(INetChannel channel) : base(NAME, GROUP) { }
|
||||
#endregion
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,23 @@ namespace Robust.Shared.Network
|
||||
message.Write(span);
|
||||
}
|
||||
|
||||
public static Color ReadColor(this NetIncomingMessage message)
|
||||
{
|
||||
var rByte = message.ReadByte();
|
||||
var gByte = message.ReadByte();
|
||||
var bByte = message.ReadByte();
|
||||
var aByte = message.ReadByte();
|
||||
return new Color(rByte, gByte, bByte, aByte);
|
||||
}
|
||||
|
||||
public static void Write(this NetOutgoingMessage message, Color color)
|
||||
{
|
||||
message.Write(color.RByte);
|
||||
message.Write(color.GByte);
|
||||
message.Write(color.BByte);
|
||||
message.Write(color.AByte);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads byte-aligned data as a memory stream.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -35,8 +35,9 @@ namespace Robust.Shared
|
||||
public static void RegisterIoC()
|
||||
{
|
||||
IoCManager.Register<IComponentManager, ComponentManager>();
|
||||
IoCManager.Register<IConfigurationManager, ConfigurationManager>();
|
||||
IoCManager.Register<IConfigurationManagerInternal, ConfigurationManager>();
|
||||
IoCManager.Register<IConfigurationManager, NetConfigurationManager>();
|
||||
IoCManager.Register<INetConfigurationManager, NetConfigurationManager>();
|
||||
IoCManager.Register<IConfigurationManagerInternal, NetConfigurationManager>();
|
||||
IoCManager.Register<IDynamicTypeFactory, DynamicTypeFactory>();
|
||||
IoCManager.Register<IDynamicTypeFactoryInternal, DynamicTypeFactory>();
|
||||
IoCManager.Register<IEntitySystemManager, EntitySystemManager>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -238,6 +238,12 @@ namespace Robust.Shared.Timing
|
||||
|
||||
public bool IsFirstTimePredicted { get; private set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool InPrediction => CurTick > LastRealTick;
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameTick LastRealTick { get; set; }
|
||||
|
||||
public void StartPastPrediction()
|
||||
{
|
||||
// Don't allow recursive predictions.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.UnitTesting.Client.UserInterface.Controls
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(RadioOptions<int>))]
|
||||
public class RadioOptionsTest : RobustUnitTest
|
||||
{
|
||||
public override UnitTestProject Project => UnitTestProject.Client;
|
||||
|
||||
[Test]
|
||||
public void TestDefaultInvoke()
|
||||
{
|
||||
//Arrange
|
||||
RadioOptions<int> _optionButton = new RadioOptions<int>(RadioOptionsLayout.Horizontal);
|
||||
|
||||
int itemId = _optionButton.AddItem("High", 1);
|
||||
|
||||
int countSelected = 0;
|
||||
_optionButton.OnItemSelected += args =>
|
||||
{
|
||||
countSelected++;
|
||||
};
|
||||
|
||||
//Act
|
||||
_optionButton.InvokeItemSelected(new RadioOptionItemSelectedEventArgs<int>(itemId, _optionButton));
|
||||
|
||||
//Assert
|
||||
Assert.That(countSelected, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOverrideInvoke()
|
||||
{
|
||||
//Arrange
|
||||
RadioOptions<int> _optionButton = new RadioOptions<int>(RadioOptionsLayout.Horizontal);
|
||||
|
||||
int countSelected = 0;
|
||||
|
||||
int itemId = _optionButton.AddItem("High", 1, args => { countSelected--; });
|
||||
int itemId2 = _optionButton.AddItem("High", 2);
|
||||
|
||||
_optionButton.OnItemSelected += args =>
|
||||
{
|
||||
countSelected++;
|
||||
};
|
||||
|
||||
//Act
|
||||
_optionButton.InvokeItemSelected(new RadioOptionItemSelectedEventArgs<int>(itemId, _optionButton));
|
||||
|
||||
//Assert
|
||||
Assert.That(countSelected, Is.EqualTo(-1));
|
||||
|
||||
//Act
|
||||
_optionButton.InvokeItemSelected(new RadioOptionItemSelectedEventArgs<int>(itemId2, _optionButton));
|
||||
_optionButton.InvokeItemSelected(new RadioOptionItemSelectedEventArgs<int>(itemId2, _optionButton));
|
||||
|
||||
//Assert
|
||||
Assert.That(countSelected, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Robust.Server.Console;
|
||||
using Robust.Server.Interfaces;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.ServerStatus;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
@@ -357,7 +358,7 @@ namespace Robust.UnitTesting
|
||||
}
|
||||
}
|
||||
|
||||
cfg.OverrideConVars(new []{("log.runtimelog", "false")});
|
||||
cfg.OverrideConVars(new []{("log.runtimelog", "false"), (CVars.SysWinTickPeriod.Name, "-1")});
|
||||
|
||||
if (server.Start(() => new TestLogHandler("SERVER")))
|
||||
{
|
||||
@@ -435,11 +436,12 @@ namespace Robust.UnitTesting
|
||||
|
||||
client.LoadConfigAndUserData = false;
|
||||
|
||||
var cfg = IoCManager.Resolve<IConfigurationManagerInternal>();
|
||||
|
||||
if (_options != null)
|
||||
{
|
||||
_options.BeforeStart?.Invoke();
|
||||
IoCManager.Resolve<IConfigurationManagerInternal>()
|
||||
.OverrideConVars(_options.CVarOverrides.Select(p => (p.Key, p.Value)));
|
||||
cfg.OverrideConVars(_options.CVarOverrides.Select(p => (p.Key, p.Value)));
|
||||
|
||||
if (_options.ExtraPrototypes != null)
|
||||
{
|
||||
@@ -448,6 +450,8 @@ namespace Robust.UnitTesting
|
||||
}
|
||||
}
|
||||
|
||||
cfg.OverrideConVars(new []{(CVars.NetPredictLagBias.Name, "0")});
|
||||
|
||||
client.Startup(() => new TestLogHandler("CLIENT"));
|
||||
|
||||
var gameLoop = new IntegrationGameLoop(DependencyCollection.Resolve<IGameTiming>(),
|
||||
|
||||
Reference in New Issue
Block a user