diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props
index 17e584b6a..862edc0c6 100644
--- a/MSBuild/Robust.Engine.Version.props
+++ b/MSBuild/Robust.Engine.Version.props
@@ -1,4 +1,6 @@
-
-
- 242.0.0
-
+
+
+
+ 0.0.1
+
+
diff --git a/README.md b/README.md
index a8d9fc8c0..20410395f 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,10 @@
-
+Supermatter Engine is a game engine primarily being developed for [Space Station 14](https://github.com/Simple-Station/Einstein-Engines).
-Robust Toolbox is an engine primarily being developed for [Space Station 14](https://github.com/space-wizards/space-station-14), although we're working on making it usable for both [singleplayer](https://github.com/space-wizards/RobustToolboxTemplateSingleplayer) and [multiplayer](https://github.com/space-wizards/RobustToolboxTemplate) projects.
-
-Use the [content repo](https://github.com/space-wizards/space-station-14) for actual development, even if you're modifying the engine itself.
+Use the [content repo](https://github.com/Simple-Station/Einstein-Engines) for actual development, even if you're modifying the engine itself.
## Project Links
-[Website](https://spacestation14.io/) | [Discord](https://discord.gg/t2jac3p) | [Forum](https://forum.spacestation14.io/) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/)
+[Website](https://simplestation.org/) | [Discord](https://discord.gg/X4QEXxUrsJ) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/)
## Documentation/Wiki
@@ -14,12 +12,12 @@ The [wiki](https://docs.spacestation14.io/) has documentation on SS14s content,
## Contributing
-We are happy to accept contributions from anybody. Get in Discord or IRC if you want to help. We've got a [list of issues](https://github.com/space-wizards/RobustToolbox/issues) that need to be done and anybody can pick them up. Don't be afraid to ask for help either!
+We are happy to accept contributions from anybody. It is recommended to join our Discord if you want to help. We've got a [list of issues](https://github.com/Simple-Station/SupermatterEngine/issues) that need to be done and anybody can pick them up. Don't be afraid to ask for help either!
## Building
-This repository is the **engine** part of SS14. It's the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Think of Robust Toolbox as BYOND in the context of Space Station 13.
+This repository is the **engine** part of SS14. It's an engine SS14 servers are built on. As such, it does not start on its own: it needs the [content repo](https://github.com/Simple-Station/Einstein-Engines). Think of SME as BYOND in the context of Space Station 13.
## Legal Info
-See [legal.md](https://github.com/space-wizards/RobustToolbox/blob/master/legal.md) for licenses and copyright.
+See [legal.md](https://github.com/Simple-Station/SupermatterEngine/blob/master/legal.md) for licenses and copyright.
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 35160cd61..52ebc5914 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -43,7 +43,7 @@ END TEMPLATE-->
### Bugfixes
-* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute`
+*None yet*
### Other
@@ -54,6 +54,105 @@ END TEMPLATE-->
*None yet*
+## 246.0.0
+
+### Breaking changes
+
+* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior.
+* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage.
+
+### New features
+
+* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand.
+* Change BlurLights to BlurRenderTarget and make it public for content usage.
+* Add ContentFlag to tiles for content-flag usage.
+* Add a basic mix shader for doing canvas blends.
+* Add GetClearColorEvent for content to override the clear color behavior.
+
+### Bugfixes
+
+* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state.
+
+
+## 245.1.0
+
+### New features
+
+* Add more info to the AnchorEntity debug message.
+* Make ParseObject public where it will parse a supplied Type and string into the specified object.
+
+### Bugfixes
+
+* Fix EntityPrototypeView not always updating the entity correctly.
+* Tweak BUI shutdown to potentially avoid skipping closing.
+
+### Other
+
+* Increase Audio entity despawn buffer to avoid clipping.
+
+
+## 245.0.0
+
+### Breaking changes
+
+* `BoundUserInterface.Open()` now has the `MustCallBase` attribute
+
+### Bugfixes
+
+* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs.
+
+
+## 244.0.0
+
+### Breaking changes
+
+* Increase physics speedcap default from 35m/s to 400m/s in-line with box2d v3.
+
+### New features
+
+* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods.
+* Add CreateWindowCenteredRight for BUIs.
+
+### Bugfixes
+
+* Avoid calling UpdateState before opening a BUI.
+
+
+## 243.0.1
+
+### Bugfixes
+
+* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape.
+* Revert `BaseWindow` OnClose ordering due to prior reliance upon the ordering.
+
+
+## 243.0.0
+
+### Breaking changes
+
+* RemoveChild is called after OnClose for BaseWindow.
+
+### New features
+
+* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow` helper or via manually registering it via RegisterControl.
+
+### Other
+
+* Ensure grid fixtures get updated in client state handling even if exceptions occur.
+
+
+## 242.0.1
+
+### Bugfixes
+
+* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute`
+* Fix the pooled polygons using incorrect vertices for EntityLookup and MapManager.
+
+### Internal
+
+* Avoid normalizing angles constructed from vectors.
+
+
## 242.0.0
### Breaking changes
diff --git a/Resources/EnginePrototypes/Shaders/stockshaders.yml b/Resources/EnginePrototypes/Shaders/stockshaders.yml
index 63e2dcf68..13f0d6361 100644
--- a/Resources/EnginePrototypes/Shaders/stockshaders.yml
+++ b/Resources/EnginePrototypes/Shaders/stockshaders.yml
@@ -4,6 +4,12 @@
kind: canvas
light_mode: unshaded
+# Simple mix blend
+- type: shader
+ id: Mix
+ kind: canvas
+ blend_mode: Mix
+
- type: shader
id: shaded
kind: canvas
diff --git a/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs b/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs
new file mode 100644
index 000000000..e3afb8e79
--- /dev/null
+++ b/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs
@@ -0,0 +1,96 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Engines;
+using JetBrains.Annotations;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.UnitTesting.Server;
+
+namespace Robust.Benchmarks.EntityManager;
+
+[Virtual]
+public partial class HasComponentBenchmark
+{
+ private static readonly Consumer Consumer = new();
+
+ private ISimulation _simulation = default!;
+ private IEntityManager _entityManager = default!;
+
+ private ComponentRegistration _compReg = default!;
+
+ private A _dummyA = new();
+
+ [UsedImplicitly]
+ [Params(1, 10, 100, 1000)]
+ public int N;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ _simulation = RobustServerSimulation
+ .NewSimulation()
+ .RegisterComponents(f => f.RegisterClass())
+ .InitializeInstance();
+
+ _entityManager = _simulation.Resolve();
+ var map = _simulation.CreateMap().Uid;
+ var coords = new EntityCoordinates(map, default);
+ _compReg = _entityManager.ComponentFactory.GetRegistration(typeof(A));
+
+ for (var i = 0; i < N; i++)
+ {
+ var uid = _entityManager.SpawnEntity(null, coords);
+ _entityManager.AddComponent(uid);
+ }
+ }
+
+ [Benchmark]
+ public void HasComponentGeneric()
+ {
+ for (var i = 2; i <= N+1; i++)
+ {
+ var uid = new EntityUid(i);
+ var result = _entityManager.HasComponent(uid);
+ Consumer.Consume(result);
+ }
+ }
+
+ [Benchmark]
+ public void HasComponentCompReg()
+ {
+ for (var i = 2; i <= N+1; i++)
+ {
+ var uid = new EntityUid(i);
+ var result = _entityManager.HasComponent(uid, _compReg);
+ Consumer.Consume(result);
+ }
+ }
+
+ [Benchmark]
+ public void HasComponentType()
+ {
+ for (var i = 2; i <= N+1; i++)
+ {
+ var uid = new EntityUid(i);
+ var result = _entityManager.HasComponent(uid, typeof(A));
+ Consumer.Consume(result);
+ }
+ }
+
+ [Benchmark]
+ public void HasComponentGetType()
+ {
+ for (var i = 2; i <= N+1; i++)
+ {
+ var uid = new EntityUid(i);
+ var type = _dummyA.GetType();
+ var result = _entityManager.HasComponent(uid, type);
+ Consumer.Consume(result);
+ }
+ }
+
+ [ComponentProtoName("A")]
+ public sealed partial class A : Component
+ {
+ }
+}
diff --git a/Robust.Client/Console/Commands/LauncherAuthCommand.cs b/Robust.Client/Console/Commands/LauncherAuthCommand.cs
index 5fa5c5192..88c1fb1ed 100644
--- a/Robust.Client/Console/Commands/LauncherAuthCommand.cs
+++ b/Robust.Client/Console/Commands/LauncherAuthCommand.cs
@@ -5,6 +5,7 @@
using Robust.Client.Utility;
using Robust.Shared.Console;
using Robust.Shared.IoC;
+using Robust.Shared.Log;
using Robust.Shared.Network;
namespace Robust.Client.Console.Commands
@@ -15,31 +16,61 @@ internal sealed class LauncherAuthCommand : LocalizedCommands
[Dependency] private readonly IGameControllerInternal _gameController = default!;
public override string Command => "launchauth";
+ public override string Help => "Usage: launchauth ";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- var wantName = args.Length > 0 ? args[0] : null;
+ string? username = null;
+ string? serverId = null;
+ string? serverUrl = null;
+ if (args.Length > 0)
+ username = args[0];
+ if (args.Length > 1)
+ serverId = args[1];
+ if (args.Length > 2)
+ serverUrl = args[2];
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
-#if USE_SYSTEM_SQLITE
- SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
-#endif
+ #if USE_SYSTEM_SQLITE
+ SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
+ #endif
using var con = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
con.Open();
using var cmd = con.CreateCommand();
- cmd.CommandText = "SELECT UserId, UserName, Token FROM Login WHERE Expires > datetime('NOW')";
+ cmd.CommandText = "SELECT UserId, UserName, Token, Server, ServerUrl FROM Login WHERE Expires > datetime('NOW')";
- if (wantName != null)
+ if (username != null)
{
cmd.CommandText += " AND UserName = @userName";
- cmd.Parameters.AddWithValue("@userName", wantName);
+ cmd.Parameters.AddWithValue("@userName", username);
}
- cmd.CommandText += " LIMIT 1;";
+ if (serverId != null)
+ {
+ cmd.CommandText += " AND Server = @serverId";
+ cmd.Parameters.AddWithValue("@serverId", serverId);
+ if (serverId == IAuthManager.CustomServerId)
+ {
+ if (serverUrl == null)
+ {
+ shell.WriteLine("Custom server requires a URL");
+ return;
+ }
+ cmd.CommandText += " AND ServerUrl = @serverUrl";
+ cmd.Parameters.AddWithValue("@serverUrl", serverUrl);
+ }
+ else if (serverUrl != null)
+ {
+ shell.WriteLine("Server URL is only valid for custom servers");
+ return;
+ }
+ }
+
+ cmd.CommandText += " LIMIT 1;";
using var reader = cmd.ExecuteReader();
if (!reader.Read())
@@ -51,11 +82,13 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
var userId = Guid.Parse(reader.GetString(0));
var userName = reader.GetString(1);
var token = reader.GetString(2);
+ serverUrl = reader.GetString(4);
_auth.Token = token;
_auth.UserId = new NetUserId(userId);
+ _auth.UserServer = new("unset", new(serverUrl));
- shell.WriteLine($"Logged into account {userName}");
+ shell.WriteLine($"Logged into account {userName}@{reader.GetString(3)} ({serverUrl})");
}
}
}
diff --git a/Robust.Client/GameControllerOptions.cs b/Robust.Client/GameControllerOptions.cs
index d386b3906..fdb0b2b2d 100644
--- a/Robust.Client/GameControllerOptions.cs
+++ b/Robust.Client/GameControllerOptions.cs
@@ -1,3 +1,4 @@
+using System;
using Robust.Shared;
using Robust.Shared.Utility;
@@ -19,7 +20,7 @@ public sealed class GameControllerOptions
///
/// Name the userdata directory will have.
///
- public string UserDataDirectoryName { get; init; } = "Space Station 14";
+ public string UserDataDirectoryName { get; init; } = Environment.GetEnvironmentVariable("SS14_LAUNCHER_DATADIR") ?? "SimpleStation14";
///
/// Name of the configuration file in the user data directory.
diff --git a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs
index 7c290cb4e..4e7fb31f2 100644
--- a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs
+++ b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs
@@ -1,10 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
+ private Dictionary> _savedPositions = new();
+ private Dictionary _registeredControls = new();
+
public override void Initialize()
{
base.Initialize();
@@ -17,6 +25,59 @@ public override void Shutdown()
ProtoManager.PrototypesReloaded -= OnProtoReload;
}
+ protected override void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ base.OnUserInterfaceShutdown(ent, ref args);
+ _savedPositions.Remove(ent.Owner);
+ }
+
+ ///
+ public override void OpenUi(Entity entity, Enum key, bool predicted = false)
+ {
+ var player = Player.LocalEntity;
+
+ if (player == null)
+ return;
+
+ OpenUi(entity, key, player.Value, predicted);
+ }
+
+ protected override void SavePosition(BoundUserInterface bui)
+ {
+ if (!_registeredControls.Remove(bui, out var control))
+ return;
+
+ var keyed = _savedPositions[bui.Owner];
+ keyed[bui.UiKey] = control.Position;
+ }
+
+ ///
+ /// Registers a control so it will later have its position stored by when the BUI is closed.
+ ///
+ public void RegisterControl(BoundUserInterface bui, Control control)
+ {
+ DebugTools.Assert(!_registeredControls.ContainsKey(bui));
+ _registeredControls[bui] = control;
+ _savedPositions.GetOrNew(bui.Owner);
+ }
+
+ public override bool TryGetPosition(Entity entity, Enum key, out Vector2 position)
+ {
+ position = default;
+
+ if (!_savedPositions.TryGetValue(entity.Owner, out var keyed))
+ {
+ return false;
+ }
+
+ if (!keyed.TryGetValue(key, out position))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
var player = Player.LocalEntity;
diff --git a/Robust.Client/GameStates/PvsOverrideSystem.cs b/Robust.Client/GameStates/PvsOverrideSystem.cs
new file mode 100644
index 000000000..5b3dfc12d
--- /dev/null
+++ b/Robust.Client/GameStates/PvsOverrideSystem.cs
@@ -0,0 +1,5 @@
+using Robust.Shared.GameStates;
+
+namespace Robust.Client.GameStates;
+
+public sealed partial class PvsOverrideSystem : SharedPvsOverrideSystem;
diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs
index c70f8ef5c..8beaae893 100644
--- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs
+++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs
@@ -125,7 +125,8 @@ private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace
{
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
- var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), vp.Eye!.Position.MapId, worldBox, worldBounds);
+ var mapId = vp.Eye!.Position.MapId;
+ var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds);
if (!overlay.BeforeDraw(args))
return;
@@ -175,8 +176,9 @@ private void RenderOverlaysDirect(
var worldBounds = CalcWorldBounds(vp);
var worldAABB = worldBounds.CalcBoundingBox();
+ var mapId = vp.Eye!.Position.MapId;
- var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds);
+ var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds);
foreach (var overlay in list)
{
@@ -421,12 +423,19 @@ private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearCol
var oldTransform = _currentMatrixModel;
var oldScissor = _currentScissorState;
+ var oldMatrixProj = _currentMatrixProj;
+ var oldMatrixView = _currentMatrixView;
+ var oldBoundTarget = _currentBoundRenderTarget;
+ var oldRenderTarget = _currentRenderTarget;
+ var oldShader = _queuedShaderInstance;
+ var oldCaps = _glCaps;
+
+ // Need to get state before flushing render queue in case they modify the original state.
+ var state = PushRenderStateFull();
// Have to flush the render queue so that all commands finish rendering to the previous framebuffer.
FlushRenderQueue();
- var state = PushRenderStateFull();
-
{
BindRenderTargetFull(RtToLoaded(rt));
if (clearColor is not null)
@@ -448,8 +457,16 @@ private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearCol
PopRenderStateFull(state);
_updateUniformConstants(_currentRenderTarget.Size);
- SetScissorFull(oldScissor);
_currentMatrixModel = oldTransform;
+
+ DebugTools.Assert(oldCaps.Equals(_glCaps));
+ DebugTools.Assert(_currentMatrixModel.Equals(oldTransform));
+ DebugTools.Assert(_currentScissorState.Equals(oldScissor));
+ DebugTools.Assert(_currentMatrixProj.Equals(oldMatrixProj));
+ DebugTools.Assert(oldMatrixView.Equals(_currentMatrixView));
+ DebugTools.Assert(oldRenderTarget.Equals(_currentRenderTarget));
+ DebugTools.Assert(oldBoundTarget.Equals(_currentBoundRenderTarget));
+ DebugTools.Assert(oldShader.Equals(_queuedShaderInstance));
}
private void RenderViewport(Viewport viewport)
diff --git a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs
index 80008a627..89a324628 100644
--- a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs
+++ b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs
@@ -1,21 +1,19 @@
using System;
using System.Collections.Generic;
using System.Buffers;
+using System.Diagnostics.Contracts;
using System.Numerics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
-using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
using Robust.Shared.Physics;
-using Robust.Client.ComponentTrees;
+using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using static Robust.Shared.GameObjects.OccluderComponent;
using Robust.Shared.Utility;
@@ -279,8 +277,7 @@ private void PrepareDepthDraw(LoadedRenderTarget target)
{
const float arbitraryDistanceMax = 1234;
- GL.Disable(EnableCap.Blend);
- CheckGlError();
+ IsBlending = false;
GL.Enable(EnableCap.DepthTest);
CheckGlError();
@@ -329,8 +326,7 @@ private void FinalizeDepthDraw()
GL.Disable(EnableCap.DepthTest);
CheckGlError();
- GL.Enable(EnableCap.Blend);
- CheckGlError();
+ IsBlending = true;
}
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
@@ -394,21 +390,43 @@ private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 w
FinalizeDepthDraw();
}
- GL.Enable(EnableCap.StencilTest);
- _isStencilling = true;
+ IsStencilling = true;
var (lightW, lightH) = GetLightMapSize(viewport.Size);
GL.Viewport(0, 0, lightW, lightH);
CheckGlError();
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
+ DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId));
CheckGlError();
- GLClearColor(_entityManager.GetComponentOrNull(mapUid)?.AmbientLightColor ?? MapLightComponent.DefaultColor);
+
+ var clearEv = new GetClearColorEvent();
+ _entityManager.EventBus.RaiseEvent(EventSource.Local, ref clearEv);
+
+ var clearColor = clearEv.Color ?? GetClearColor(mapUid);
+ GLClearColor(clearColor);
GL.ClearStencil(0xFF);
GL.StencilMask(0xFF);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
CheckGlError();
+ var oldTarget = _currentRenderTarget;
+ var oldProj = _currentMatrixProj;
+ var oldShader = _queuedShaderInstance;
+ var oldModel = _currentMatrixModel;
+ var oldScissor = _currentScissorState;
+ var state = PushRenderStateFull();
+
+ RenderOverlays(viewport, OverlaySpace.BeforeLighting, worldAABB, worldBounds);
+ PopRenderStateFull(state);
+
+ DebugTools.Assert(oldScissor.Equals(_currentScissorState));
+ DebugTools.Assert(oldModel.Equals(_currentMatrixModel));
+ DebugTools.Assert(oldShader.Equals(_queuedShaderInstance));
+ DebugTools.Assert(oldProj.Equals(_currentMatrixProj));
+ DebugTools.Assert(oldTarget.Equals(_currentRenderTarget));
+ DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId));
+
ApplyLightingFovToBuffer(viewport, eye);
var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle]
@@ -509,13 +527,12 @@ private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 w
}
ResetBlendFunc();
- GL.Disable(EnableCap.StencilTest);
- _isStencilling = false;
+ IsStencilling = false;
CheckGlError();
if (_cfg.GetCVar(CVars.LightBlur))
- BlurLights(viewport, eye);
+ BlurRenderTarget(viewport, viewport.LightRenderTarget, viewport.LightBlurTarget, eye, 14f);
using (_prof.Group("BlurOntoWalls"))
{
@@ -531,9 +548,8 @@ private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 w
GL.Viewport(0, 0, viewport.Size.X, viewport.Size.Y);
CheckGlError();
- Array.Clear(_lightsToRenderList, 0, count);
-
_lightingReady = true;
+ Array.Clear(_lightsToRenderList, 0, count);
}
private static bool LightQuery(ref (
@@ -643,21 +659,33 @@ public int Compare(
return (state.count, expandedBounds);
}
- private void BlurLights(Viewport viewport, IEye eye)
+ ///
+ [Pure]
+ public Color GetClearColor(EntityUid mapUid)
{
- using var _ = DebugGroup(nameof(BlurLights));
+ return _entityManager.GetComponentOrNull(mapUid)?.AmbientLightColor ??
+ MapLightComponent.DefaultColor;
+ }
- GL.Disable(EnableCap.Blend);
- CheckGlError();
+ ///
+ public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier)
+ {
+ if (target is not RenderTexture rTexture || blurBuffer is not RenderTexture blurTexture)
+ return;
+
+ using var _ = DebugGroup(nameof(BlurRenderTarget));
+
+ var state = PushRenderStateFull();
+ IsBlending = false;
CalcScreenMatrices(viewport.Size, out var proj, out var view);
SetProjViewBuffer(proj, view);
var shader = _loadedShaders[_lightBlurShaderHandle].Program;
shader.Use();
- SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
+ SetupGlobalUniformsImmediate(shader, rTexture.Texture);
- var size = viewport.LightRenderTarget.Size;
+ var size = target.Size;
shader.SetUniformMaybe("size", (Vector2)size);
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
@@ -667,14 +695,13 @@ private void BlurLights(Viewport viewport, IEye eye)
// Initially we're pulling from the light render target.
// So we set it out of the loop so
// _wallBleedIntermediateRenderTarget2 gets bound at the end of the loop body.
- SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
+ SetTexture(TextureUnit.Texture0, rTexture.Texture);
// Have to scale the blurring radius based on viewport size and camera zoom.
- const float refCameraHeight = 14;
var facBase = _cfg.GetCVar(CVars.LightBlurFactor);
var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter;
// 7e-3f is just a magic factor that makes it look ok.
- var factor = facBase * (refCameraHeight / cameraSize);
+ var factor = facBase * (multiplier / cameraSize);
// Multi-iteration gaussian blur.
for (var i = 3; i > 0; i--)
@@ -683,35 +710,31 @@ private void BlurLights(Viewport viewport, IEye eye)
// Set factor.
shader.SetUniformMaybe("radius", scale);
- BindRenderTargetFull(viewport.LightBlurTarget);
+ BindRenderTargetImmediate(RtToLoaded(blurBuffer));
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
shader.SetUniformMaybe("direction", Vector2.UnitX);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
- SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
+ SetTexture(TextureUnit.Texture0, blurTexture.Texture);
- BindRenderTargetFull(viewport.LightRenderTarget);
+ BindRenderTargetImmediate(RtToLoaded(rTexture));
// Blur vertically to _wallBleedIntermediateRenderTarget2.
shader.SetUniformMaybe("direction", Vector2.UnitY);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
- SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
+ SetTexture(TextureUnit.Texture0, rTexture.Texture);
}
- GL.Enable(EnableCap.Blend);
- CheckGlError();
- // We didn't trample over the old _currentMatrices so just roll it back.
- SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
+ PopRenderStateFull(state);
}
private void BlurOntoWalls(Viewport viewport, IEye eye)
{
using var _ = DebugGroup(nameof(BlurOntoWalls));
- GL.Disable(EnableCap.Blend);
- CheckGlError();
+ IsBlending = false;
CalcScreenMatrices(viewport.Size, out var proj, out var view);
SetProjViewBuffer(proj, view);
@@ -761,8 +784,7 @@ private void BlurOntoWalls(Viewport viewport, IEye eye)
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture);
}
- GL.Enable(EnableCap.Blend);
- CheckGlError();
+ IsBlending = true;
// We didn't trample over the old _currentMatrices so just roll it back.
SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
}
@@ -775,8 +797,7 @@ private void MergeWallLayer(Viewport viewport)
GL.Viewport(0, 0, viewport.LightRenderTarget.Size.X, viewport.LightRenderTarget.Size.Y);
CheckGlError();
- GL.Disable(EnableCap.Blend);
- CheckGlError();
+ IsBlending = false;
var shader = _loadedShaders[_mergeWallLayerShaderHandle].Program;
shader.Use();
@@ -796,8 +817,7 @@ private void MergeWallLayer(Viewport viewport)
IntPtr.Zero);
CheckGlError();
- GL.Enable(EnableCap.Blend);
- CheckGlError();
+ IsBlending = true;
}
private void ApplyFovToBuffer(Viewport viewport, IEye eye)
@@ -827,8 +847,7 @@ private void ApplyFovToBuffer(Viewport viewport, IEye eye)
FovSetTransformAndBlit(viewport, eye.Position.Position, fovShader);
GL.StencilMask(0x00);
- GL.Disable(EnableCap.StencilTest);
- _isStencilling = false;
+ IsStencilling = false;
}
private void ApplyLightingFovToBuffer(Viewport viewport, IEye eye)
@@ -1135,22 +1154,20 @@ private void RegenLightRts(Viewport viewport)
var lightMapSize = GetLightMapSize(viewport.Size);
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
- var lightMapColorFormat = _hasGLFloatFramebuffers
- ? RenderTargetColorFormat.R11FG11FB10F
- : RenderTargetColorFormat.Rgba8;
- var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
viewport.LightRenderTarget?.Dispose();
viewport.WallMaskRenderTarget?.Dispose();
viewport.WallBleedIntermediateRenderTarget1?.Dispose();
viewport.WallBleedIntermediateRenderTarget2?.Dispose();
+ var lightMapColorFormat = _hasGLFloatFramebuffers
+ ? RenderTargetColorFormat.R11FG11FB10F
+ : RenderTargetColorFormat.Rgba8;
+ var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8,
name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}");
- viewport.LightRenderTarget = CreateRenderTarget(lightMapSize,
- new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true),
- lightMapSampleParameters,
+ viewport.LightRenderTarget = (RenderTexture) CreateLightRenderTarget(lightMapSize,
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");
viewport.LightBlurTarget = CreateRenderTarget(lightMapSize,
@@ -1158,11 +1175,13 @@ private void RegenLightRts(Viewport viewport)
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.LightBlurTarget)}");
- viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
+ viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart,
+ new RenderTargetFormatParameters(lightMapColorFormat),
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget1)}");
- viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
+ viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart,
+ new RenderTargetFormatParameters(lightMapColorFormat),
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget2)}");
}
diff --git a/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs b/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs
index a07f1cfc7..5ea57a1ae 100644
--- a/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs
+++ b/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs
@@ -30,6 +30,20 @@ private readonly ConcurrentQueue _renderTargetDisposeQueue
// It, like _mainWindowRenderTarget, is initialized in Clyde's constructor
private LoadedRenderTarget _currentBoundRenderTarget;
+
+ public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
+ {
+ var lightMapColorFormat = _hasGLFloatFramebuffers
+ ? RTCF.R11FG11FB10F
+ : RTCF.Rgba8;
+ var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
+
+ return CreateRenderTarget(size,
+ new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: depthStencil),
+ lightMapSampleParameters,
+ name: name);
+ }
+
IRenderTexture IClyde.CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
TextureSampleParameters? sampleParameters, string? name)
{
@@ -204,7 +218,8 @@ private RenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParame
Size = size,
TextureHandle = textureObject.TextureId,
MemoryPressure = pressure,
- ColorFormat = format.ColorFormat
+ ColorFormat = format.ColorFormat,
+ SampleParameters = sampleParameters,
};
//GC.AddMemoryPressure(pressure);
@@ -251,9 +266,15 @@ private void BindRenderTargetFull(RenderTargetBase rt)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private LoadedRenderTarget RtToLoaded(RenderTargetBase rt)
+ private LoadedRenderTarget RtToLoaded(IRenderTarget rt)
{
- return _renderTargets[rt.Handle];
+ switch (rt)
+ {
+ case RenderTargetBase based:
+ return _renderTargets[based.Handle];
+ default:
+ throw new NotImplementedException();
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -302,6 +323,8 @@ private sealed class LoadedRenderTarget
// Renderbuffer handle
public GLHandle DepthStencilHandle;
public long MemoryPressure;
+
+ public TextureSampleParameters? SampleParameters;
}
private abstract class RenderTargetBase : IRenderTarget
diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs
index af19a2c1a..b0f42f793 100644
--- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs
+++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs
@@ -90,9 +90,61 @@ internal partial class Clyde
// (queue) and (misc), current state of the scissor test. Null if disabled.
private UIBox2i? _currentScissorState;
- // Some simple flags that basically just tracks the current state of glEnable(GL_STENCIL/GL_SCISSOR_TEST)
- private bool _isScissoring;
- private bool _isStencilling;
+ ///
+ /// Tracks enabled GL capabilities for renderer state.
+ ///
+ private GLCaps _glCaps = GLCaps.None;
+
+ private bool IsStencilling
+ {
+ get => (_glCaps & GLCaps.Stencilling) == GLCaps.Stencilling;
+ set
+ {
+ if (value == IsStencilling)
+ return;
+
+ if (value)
+ {
+ _glCaps |= GLCaps.Stencilling;
+ GL.Enable(EnableCap.StencilTest);
+ }
+ else
+ {
+ _glCaps &= ~GLCaps.Stencilling;
+ GL.Disable(EnableCap.StencilTest);
+ }
+
+ CheckGlError();
+ }
+ }
+
+ private bool IsBlending
+ {
+ get => (_glCaps & GLCaps.Blending) == GLCaps.Blending;
+ set
+ {
+ if (value == IsBlending)
+ return;
+
+ if (value)
+ {
+ _glCaps |= GLCaps.Blending;
+ GL.Enable(EnableCap.Blend);
+ }
+ else
+ {
+ _glCaps &= ~GLCaps.Blending;
+ GL.Disable(EnableCap.Blend);
+ }
+
+ CheckGlError();
+ }
+ }
+
+ private bool IsScissoring
+ {
+ get => _currentScissorState != null;
+ }
private readonly RefList _queuedRenderCommands = new RefList();
@@ -364,16 +416,17 @@ private void SetScissorFull(UIBox2i? state)
private void SetScissorImmediate(bool enable, in UIBox2i box)
{
- var oldIsScissoring = _isScissoring;
- _isScissoring = enable;
- if (_isScissoring)
+ if (enable)
{
- if (!oldIsScissoring)
- {
- GL.Enable(EnableCap.ScissorTest);
- CheckGlError();
- }
+ GL.Enable(EnableCap.ScissorTest);
+ }
+ else
+ {
+ GL.Disable(EnableCap.ScissorTest);
+ }
+ if (enable)
+ {
// Don't forget to flip it, these coordinates have bottom left as origin.
// TODO: Broken when rendering to non-screen render targets.
@@ -387,11 +440,6 @@ private void SetScissorImmediate(bool enable, in UIBox2i box)
}
CheckGlError();
}
- else if (oldIsScissoring)
- {
- GL.Disable(EnableCap.ScissorTest);
- CheckGlError();
- }
}
// NOTE: sRGB IS IN LINEAR IF FRAMEBUFFER_SRGB IS ACTIVE.
@@ -420,17 +468,11 @@ private Color ConvertClearFromSrgb(Color color)
var program = shader.Program;
program.Use();
+ IsStencilling = instance.Stencil.Enabled;
// Handle stencil parameters.
if (instance.Stencil.Enabled)
{
- if (!_isStencilling)
- {
- GL.Enable(EnableCap.StencilTest);
- CheckGlError();
- _isStencilling = true;
- }
-
GL.StencilMask(instance.Stencil.WriteMask);
CheckGlError();
GL.StencilFunc(ToGLStencilFunc(instance.Stencil.Func), instance.Stencil.Ref, instance.Stencil.ReadMask);
@@ -438,12 +480,6 @@ private Color ConvertClearFromSrgb(Color color)
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, ToGLStencilOp(instance.Stencil.Op));
CheckGlError();
}
- else if (_isStencilling)
- {
- GL.Disable(EnableCap.StencilTest);
- CheckGlError();
- _isStencilling = false;
- }
if (instance.Parameters.Count == 0)
return (program, instance);
@@ -859,17 +895,34 @@ private void BreakBatch()
private FullStoredRendererState PushRenderStateFull()
{
- return new FullStoredRendererState(_currentMatrixProj, _currentMatrixView, _currentRenderTarget);
+ return new FullStoredRendererState(
+ _currentMatrixProj,
+ _currentMatrixView,
+ _currentBoundRenderTarget,
+ _currentRenderTarget,
+ _queuedShaderInstance,
+ _currentScissorState,
+ _glCaps);
}
private void PopRenderStateFull(in FullStoredRendererState state)
{
SetProjViewFull(state.ProjMatrix, state.ViewMatrix);
- BindRenderTargetFull(state.RenderTarget);
+ BindRenderTargetImmediate(state.BoundRenderTarget);
- var (width, height) = state.RenderTarget.Size;
+ _queuedShaderInstance = state.QueuedShaderInstance;
+ _currentRenderTarget = state.RenderTarget;
+ var (width, height) = state.BoundRenderTarget.Size;
GL.Viewport(0, 0, width, height);
- CheckGlError();
+
+ IsStencilling = (state.GLCaps & GLCaps.Stencilling) == GLCaps.Stencilling;
+ IsBlending = (state.GLCaps & GLCaps.Blending) == GLCaps.Blending;
+
+ SetScissorFull(state.ScissorState);
+
+ GL.ClearStencil(0xFF);
+ GL.StencilMask(0xFF);
+ GL.Clear(ClearBufferMask.StencilBufferBit);
}
private void SetViewportImmediate(Box2i box)
@@ -1061,15 +1114,44 @@ private readonly struct FullStoredRendererState
{
public readonly Matrix3x2 ProjMatrix;
public readonly Matrix3x2 ViewMatrix;
+ public readonly LoadedRenderTarget BoundRenderTarget;
public readonly LoadedRenderTarget RenderTarget;
+ public readonly ClydeShaderInstance QueuedShaderInstance;
- public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix,
- LoadedRenderTarget renderTarget)
+ public readonly UIBox2i? ScissorState;
+
+ public readonly GLCaps GLCaps;
+
+ public FullStoredRendererState(
+ in Matrix3x2 projMatrix,
+ in Matrix3x2 viewMatrix,
+ LoadedRenderTarget boundRenderTarget,
+ LoadedRenderTarget renderTarget,
+ ClydeShaderInstance queuedShaderInstance,
+ UIBox2i? scissorState,
+ GLCaps glcaps
+ )
{
ProjMatrix = projMatrix;
ViewMatrix = viewMatrix;
+ BoundRenderTarget = boundRenderTarget;
RenderTarget = renderTarget;
+ QueuedShaderInstance = queuedShaderInstance;
+
+ ScissorState = scissorState;
+ GLCaps = glcaps;
}
}
+
+ [Flags]
+ private enum GLCaps : ushort
+ {
+ // If you add flags here make sure to update PopRenderState!
+ None = 0,
+
+ Blending = 1 << 0,
+
+ Stencilling = 1 << 2,
+ }
}
}
diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs
index 736116e5a..4d36e6e59 100644
--- a/Robust.Client/Graphics/Clyde/Clyde.cs
+++ b/Robust.Client/Graphics/Clyde/Clyde.cs
@@ -21,6 +21,7 @@
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
@@ -245,7 +246,7 @@ private void InitOpenGL()
overrideVersion != null,
_windowing!.GetDescription());
- GL.Enable(EnableCap.Blend);
+ IsBlending = true;
if (_hasGLSrgb && !_isGLES)
{
GL.Enable(EnableCap.FramebufferSrgb);
diff --git a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs
index f8ed887b7..3c242dd00 100644
--- a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs
+++ b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs
@@ -9,6 +9,7 @@
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -189,6 +190,22 @@ public OwnedTexture CreateBlankTexture(
return new DummyTexture(size);
}
+ ///
+ public Color GetClearColor(EntityUid mapUid)
+ {
+ return Color.Transparent;
+ }
+
+ public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier)
+ {
+ // NOOP
+ }
+
+ public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
+ {
+ return CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.R8, hasDepthStencil: depthStencil), null, name: name);
+ }
+
public IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
TextureSampleParameters? sampleParameters = null, string? name = null)
{
diff --git a/Robust.Client/Graphics/IClyde.cs b/Robust.Client/Graphics/IClyde.cs
index 2c69fcbda..11c20c6f6 100644
--- a/Robust.Client/Graphics/IClyde.cs
+++ b/Robust.Client/Graphics/IClyde.cs
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.Contracts;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
+using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
+using Color = Robust.Shared.Maths.Color;
namespace Robust.Client.Graphics
{
@@ -71,6 +74,24 @@ OwnedTexture CreateBlankTexture(
in TextureLoadParameters? loadParams = null)
where T : unmanaged, IPixel;
+ ///
+ /// Gets the clear color for the specified map viewport.
+ ///
+ [Pure]
+ Color GetClearColor(EntityUid mapUid);
+
+ ///
+ /// Applies a blur to the specified render target. Requires a separate buffer with similar properties to draw intermediate steps into.
+ ///
+ /// The viewport being used for drawing.
+ /// The blur target.
+ /// The separate buffer to draw into.
+ /// The eye being drawn with.
+ /// Scale of how much blur to blur by.
+ void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier);
+
+ IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true);
+
IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
TextureSampleParameters? sampleParameters = null, string? name = null);
diff --git a/Robust.Client/Graphics/IClydeViewport.cs b/Robust.Client/Graphics/IClydeViewport.cs
index de09cfdfa..2f838ba21 100644
--- a/Robust.Client/Graphics/IClydeViewport.cs
+++ b/Robust.Client/Graphics/IClydeViewport.cs
@@ -18,6 +18,7 @@ public interface IClydeViewport : IDisposable
///
IRenderTexture RenderTarget { get; }
IRenderTexture LightRenderTarget { get; }
+
IEye? Eye { get; set; }
Vector2i Size { get; }
diff --git a/Robust.Client/Graphics/IRenderTarget.cs b/Robust.Client/Graphics/IRenderTarget.cs
index 61b277629..dc8cc53c5 100644
--- a/Robust.Client/Graphics/IRenderTarget.cs
+++ b/Robust.Client/Graphics/IRenderTarget.cs
@@ -1,4 +1,6 @@
using System;
+using System.Numerics;
+using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using SixLabors.ImageSharp.PixelFormats;
@@ -15,5 +17,42 @@ public interface IRenderTarget : IDisposable
Vector2i Size { get; }
void CopyPixelsToMemory(CopyPixelsDelegate callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel;
+
+ public Vector2 LocalToWorld(IEye eye, Vector2 point, Vector2 scale)
+ {
+ var newPoint = point;
+
+ // (inlined version of UiProjMatrix^-1)
+ newPoint -= Size / 2f;
+ newPoint *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
+
+ // view matrix
+ eye.GetViewMatrixInv(out var viewMatrixInv, scale);
+ newPoint = Vector2.Transform(newPoint, viewMatrixInv);
+
+ return newPoint;
+ }
+
+ public Vector2 WorldToLocal(Vector2 point, IEye eye, Vector2 scale)
+ {
+ var newPoint = point;
+
+ eye.GetViewMatrix(out var viewMatrix, scale);
+ newPoint = Vector2.Transform(newPoint, viewMatrix);
+
+ // (inlined version of UiProjMatrix)
+ newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
+ newPoint += Size / 2f;
+
+ return newPoint;
+ }
+
+ public Matrix3x2 GetWorldToLocalMatrix(IEye eye, Vector2 scale)
+ {
+ eye.GetViewMatrix(out var viewMatrix, scale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter));
+ viewMatrix.M31 += Size.X / 2f;
+ viewMatrix.M32 += Size.Y / 2f;
+ return viewMatrix;
+ }
}
}
diff --git a/Robust.Client/Graphics/Lighting/GetClearColorEvent.cs b/Robust.Client/Graphics/Lighting/GetClearColorEvent.cs
new file mode 100644
index 000000000..7fb2b5271
--- /dev/null
+++ b/Robust.Client/Graphics/Lighting/GetClearColorEvent.cs
@@ -0,0 +1,13 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Maths;
+
+namespace Robust.Client.Graphics;
+
+///
+/// Raised by the engine if content wishes to override the default clear color.
+///
+[ByRefEvent]
+public record struct GetClearColorEvent
+{
+ public Color? Color;
+}
diff --git a/Robust.Client/Graphics/Lighting/LightManager.cs b/Robust.Client/Graphics/Lighting/LightManager.cs
index 19bb4c498..7b4e34dcd 100644
--- a/Robust.Client/Graphics/Lighting/LightManager.cs
+++ b/Robust.Client/Graphics/Lighting/LightManager.cs
@@ -9,6 +9,5 @@ public sealed class LightManager : ILightManager
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);
}
}
diff --git a/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs b/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs
index f07c4fffa..cbdd17bcf 100644
--- a/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs
+++ b/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs
@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
+using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -39,6 +40,8 @@ public readonly ref struct OverlayDrawArgs
///
public readonly UIBox2i ViewportBounds;
+ public readonly EntityUid MapUid;
+
///
/// of the viewport's eye.
///
@@ -65,6 +68,7 @@ internal OverlayDrawArgs(
IClydeViewport viewport,
IRenderHandle renderHandle,
in UIBox2i viewportBounds,
+ in EntityUid mapUid,
in MapId mapId,
in Box2 worldAabb,
in Box2Rotated worldBounds)
@@ -78,6 +82,7 @@ internal OverlayDrawArgs(
Viewport = viewport;
RenderHandle = renderHandle;
ViewportBounds = viewportBounds;
+ MapUid = mapUid;
MapId = mapId;
WorldAABB = worldAabb;
WorldBounds = worldBounds;
diff --git a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs
index eb59385a3..9cea88e74 100644
--- a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs
+++ b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs
@@ -1,4 +1,5 @@
using System;
+using Robust.Client.GameObjects;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
@@ -6,14 +7,63 @@ namespace Robust.Client.UserInterface;
public static class BoundUserInterfaceExt
{
+ private static T GetWindow(BoundUserInterface bui) where T : BaseWindow, new()
+ {
+ var window = bui.CreateDisposableControl();
+ window.OnClose += bui.Close;
+ var system = bui.EntMan.System();
+ system.RegisterControl(bui, window);
+ return window;
+ }
+
///
/// Helper method to create a window and also handle closing the BUI when it's closed.
///
public static T CreateWindow(this BoundUserInterface bui) where T : BaseWindow, new()
{
- var window = bui.CreateDisposableControl();
- window.OpenCentered();
- window.OnClose += bui.Close;
+ var window = GetWindow(bui);
+
+ if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position))
+ {
+ window.Open(position);
+ }
+ else
+ {
+ window.OpenCentered();
+ }
+
+ return window;
+ }
+
+ public static T CreateWindowCenteredLeft(this BoundUserInterface bui) where T : BaseWindow, new()
+ {
+ var window = GetWindow(bui);
+
+ if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position))
+ {
+ window.Open(position);
+ }
+ else
+ {
+ window.OpenCenteredLeft();
+ }
+
+ return window;
+ }
+
+ public static T CreateWindowCenteredRight(this BoundUserInterface bui) where T : BaseWindow, new()
+ {
+ var window = GetWindow(bui);
+
+ if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position))
+ {
+ window.Open(position);
+ }
+ else
+ {
+ window.OpenCenteredRight();
+ }
+
return window;
}
diff --git a/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs b/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs
index 5173a3da4..bd2e7c1c0 100644
--- a/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs
+++ b/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs
@@ -9,6 +9,7 @@ public class EntityPrototypeView : SpriteView
{
private string? _currentPrototype;
private EntityUid? _ourEntity;
+ private bool _isShowing;
public EntityPrototypeView()
{
@@ -31,7 +32,7 @@ public void SetPrototype(EntProtoId? entProto)
_currentPrototype = entProto;
- if (_ourEntity != null)
+ if (_ourEntity != null || _isShowing)
{
UpdateEntity();
}
@@ -45,6 +46,8 @@ protected override void EnteredTree()
{
UpdateEntity();
}
+
+ _isShowing = true;
}
protected override void ExitedTree()
@@ -52,6 +55,8 @@ protected override void ExitedTree()
base.ExitedTree();
EntMan.TryQueueDeleteEntity(_ourEntity);
_ourEntity = null;
+
+ _isShowing = false;
}
private void UpdateEntity()
diff --git a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs
index 2e416f871..42184c87d 100644
--- a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs
+++ b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs
@@ -5,6 +5,7 @@
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
+using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
@@ -102,6 +103,8 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
var cursor = CursorShape.Arrow;
var previewDragMode = GetDragModeFor(args.RelativePosition);
+ previewDragMode &= ~DragMode.Move;
+
switch (previewDragMode)
{
case DragMode.Top:
@@ -116,9 +119,6 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args)
case DragMode.Bottom | DragMode.Left:
case DragMode.Top | DragMode.Right:
- cursor = CursorShape.Crosshair;
- break;
-
case DragMode.Bottom | DragMode.Right:
case DragMode.Top | DragMode.Left:
cursor = CursorShape.Crosshair;
@@ -160,15 +160,6 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args)
var rect = new UIBox2(left, top, right, bottom);
LayoutContainer.SetPosition(this, rect.TopLeft);
SetSize = rect.Size;
-
- /*
- var timing = IoCManager.Resolve();
-
- var l = GetValue(LayoutContainer.MarginLeftProperty);
- var t = GetValue(LayoutContainer.MarginTopProperty);
-
- Logger.Debug($"{timing.CurFrame}: {rect.TopLeft}/({l}, {t}), {rect.Size}/{SetSize}");
- */
}
}
@@ -229,6 +220,16 @@ public void Open()
OnOpen?.Invoke();
}
+ ///
+ /// Opens the window and places it at the specified position.
+ ///
+ public void Open(Vector2 position)
+ {
+ Measure(Vector2Helpers.Infinity);
+ Open();
+ LayoutContainer.SetPosition(this, position);
+ }
+
public void OpenCentered() => OpenCenteredAt(new Vector2(0.5f, 0.5f));
public void OpenToLeft() => OpenCenteredAt(new Vector2(0, 0.5f));
diff --git a/Robust.Server/GameStates/PvsOverrideSystem.cs b/Robust.Server/GameStates/PvsOverrideSystem.cs
index bef471af0..187e264c2 100644
--- a/Robust.Server/GameStates/PvsOverrideSystem.cs
+++ b/Robust.Server/GameStates/PvsOverrideSystem.cs
@@ -5,13 +5,14 @@
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Server.GameStates;
-public sealed class PvsOverrideSystem : EntitySystem
+public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IConsoleHost _console = default!;
@@ -134,8 +135,10 @@ private void Clear(EntityUid uid)
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
/// causing them to always be sent to all clients.
///
- public void AddGlobalOverride(EntityUid uid)
+ public override void AddGlobalOverride(EntityUid uid)
{
+ base.AddGlobalOverride(uid);
+
if (GlobalOverride.Add(uid))
_hasOverride.Add(uid);
}
@@ -143,8 +146,10 @@ public void AddGlobalOverride(EntityUid uid)
///
/// Removes an entity from the global overrides.
///
- public void RemoveGlobalOverride(EntityUid uid)
+ public override void RemoveGlobalOverride(EntityUid uid)
{
+ base.RemoveGlobalOverride(uid);
+
GlobalOverride.Remove(uid);
// Not bothering to clear _hasOverride, as we'd have to check all the other collections, and at that point we
// might as well just do that when the entity gets deleted anyways.
@@ -203,8 +208,10 @@ public void RemoveForceSend(EntityUid uid, ICommonSession session)
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a
/// specific session.
///
- public void AddSessionOverride(EntityUid uid, ICommonSession session)
+ public override void AddSessionOverride(EntityUid uid, ICommonSession session)
{
+ base.AddSessionOverride(uid, session);
+
if (SessionOverrides.GetOrNew(session).Add(uid))
_hasOverride.Add(uid);
}
@@ -212,8 +219,10 @@ public void AddSessionOverride(EntityUid uid, ICommonSession session)
///
/// Removes an entity from a session's overrides.
///
- public void RemoveSessionOverride(EntityUid uid, ICommonSession session)
+ public override void RemoveSessionOverride(EntityUid uid, ICommonSession session)
{
+ base.RemoveSessionOverride(uid, session);
+
if (!SessionOverrides.TryGetValue(session, out var overrides))
return;
@@ -228,8 +237,10 @@ public void RemoveSessionOverride(EntityUid uid, ICommonSession session)
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
/// causing them to always be sent to all clients.
///
- public void AddSessionOverrides(EntityUid uid, Filter filter)
+ public override void AddSessionOverrides(EntityUid uid, Filter filter)
{
+ base.AddSessionOverrides(uid, filter);
+
foreach (var session in filter.Recipients)
{
AddSessionOverride(uid, session);
diff --git a/Robust.Server/Maps/MapChunkSerializer.cs b/Robust.Server/Maps/MapChunkSerializer.cs
index cabf4435c..5b81b2f79 100644
--- a/Robust.Server/Maps/MapChunkSerializer.cs
+++ b/Robust.Server/Maps/MapChunkSerializer.cs
@@ -73,7 +73,7 @@ public MapChunk Read(ISerializationManager serializationManager, MappingDataNode
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
- var flags = (TileRenderFlag)reader.ReadByte();
+ var flags = reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileMap[id];
diff --git a/Robust.Server/Upload/GamePrototypeLoadManager.cs b/Robust.Server/Upload/GamePrototypeLoadManager.cs
index 3d6500af5..8912624bc 100644
--- a/Robust.Server/Upload/GamePrototypeLoadManager.cs
+++ b/Robust.Server/Upload/GamePrototypeLoadManager.cs
@@ -50,6 +50,9 @@ protected override void LoadPrototypeData(GamePrototypeLoadMessage message)
internal void SendToNewUser(INetChannel channel)
{
+ if (LoadedPrototypes.Count == 0)
+ return;
+
// Just dump all the prototypes on connect, before them missing could be an issue.
var msg = new GamePrototypeLoadMessage
{
diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs
index 37bb59ff9..42d003ddd 100644
--- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs
+++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs
@@ -37,6 +37,8 @@ public abstract partial class SharedAudioSystem : EntitySystem
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
+ private const float AudioDespawnBuffer = 1f;
+
///
/// Default max range at which the sound can be heard.
///
@@ -234,7 +236,7 @@ public void SetState(EntityUid? entity, AudioState state, bool force = false, Au
{
var timed = EnsureComp(entity.Value);
var audioLength = GetAudioLength(component.FileName);
- timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f;
+ timed.Lifetime = (float) audioLength.TotalSeconds + AudioDespawnBuffer;
}
break;
}
@@ -322,7 +324,7 @@ protected Entity SetupAudio(string? fileName, AudioParams? audio
var despawn = AddComp(uid);
// Don't want to clip audio too short due to imprecision.
- despawn.Lifetime = (float) length.Value.TotalSeconds + 0.01f;
+ despawn.Lifetime = (float) length.Value.TotalSeconds + AudioDespawnBuffer;
}
if (comp.Params.Variation != null && comp.Params.Variation.Value != 0f)
diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs
index 0a0f0d4fc..27df1fd75 100644
--- a/Robust.Shared/CVars.cs
+++ b/Robust.Shared/CVars.cs
@@ -942,12 +942,12 @@ protected CVars()
public static readonly CVarDef AuthAllowLocal =
CVarDef.Create("auth.allowlocal", true, CVar.SERVERONLY);
- // Only respected on server, client goes through IAuthManager for security.
///
- /// Authentication server address.
+ /// List of comma separated URLs to use as whitelisted authentication servers
///
- public static readonly CVarDef AuthServer =
- CVarDef.Create("auth.server", AuthManager.DefaultAuthServer, CVar.SERVERONLY);
+ /// "Space-Wizards:https://auth.spacestation14.com/,SimpleStation:https://auth.simplestation.org/"
+ public static readonly CVarDef AuthServers =
+ CVarDef.Create("auth.servers", AuthServer.ToStringList(AuthManager.DefaultAuthServers), CVar.SERVERONLY);
/*
* RENDERING
@@ -1349,10 +1349,10 @@ protected CVars()
/// MaxLinVelocity is compared to the dot product of linearVelocity * frameTime.
///
///
- /// Default is 35 m/s. Around half a tile per tick at 60 ticks per second.
+ /// Default is 400 m/s in-line with Box2c. Box2d used 120m/s.
///
public static readonly CVarDef MaxLinVelocity =
- CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED);
+ CVarDef.Create("physics.maxlinvelocity", 400f, CVar.SERVER | CVar.REPLICATED);
///
/// Maximum angular velocity in full rotations per second.
@@ -1364,7 +1364,6 @@ protected CVars()
public static readonly CVarDef MaxAngVelocity =
CVarDef.Create("physics.maxangvelocity", 15f);
-
/*
* User interface
*/
diff --git a/Robust.Shared/Configuration/ConfigurationCommands.cs b/Robust.Shared/Configuration/ConfigurationCommands.cs
index 24f62883d..0b4022773 100644
--- a/Robust.Shared/Configuration/ConfigurationCommands.cs
+++ b/Robust.Shared/Configuration/ConfigurationCommands.cs
@@ -8,6 +8,58 @@
namespace Robust.Shared.Configuration
{
+ public static class CVarCommandUtil
+ {
+ ///
+ /// Parses a string into an object of the given type.
+ ///
+ /// Thrown if the string could not be parsed into the given type.
+ /// Thrown if the type is not supported.
+ public static object ParseObject(Type type, string input)
+ {
+ if (type == typeof(bool))
+ {
+ if (bool.TryParse(input, out var val))
+ return val;
+
+ if (Parse.TryInt32(input, out var intVal))
+ {
+ if (intVal == 0) return false;
+ if (intVal == 1) return true;
+ }
+
+ throw new FormatException($"Could not parse bool value: {input}");
+ }
+
+ if (type == typeof(string))
+ {
+ return input;
+ }
+
+ if (type == typeof(int))
+ {
+ return Parse.Int32(input);
+ }
+
+ if (type == typeof(float))
+ {
+ return Parse.Float(input);
+ }
+
+ if (type == typeof(long))
+ {
+ return long.Parse(input);
+ }
+
+ if (type == typeof(ushort))
+ {
+ return ushort.Parse(input);
+ }
+
+ throw new NotSupportedException();
+ }
+ }
+
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal sealed class CVarCommand : LocalizedCommands
{
@@ -51,7 +103,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
var type = _cfg.GetCVarType(name);
try
{
- var parsed = ParseObject(type, value);
+ var parsed = CVarCommandUtil.ParseObject(type, value);
_cfg.SetCVar(name, parsed);
}
catch (FormatException)
@@ -95,50 +147,6 @@ private static string GetCVarValueHint(IConfigurationManager cfg, string cVar)
return value;
}
-
- private static object ParseObject(Type type, string input)
- {
- if (type == typeof(bool))
- {
- if (bool.TryParse(input, out var val))
- return val;
-
- if (Parse.TryInt32(input, out var intVal))
- {
- if (intVal == 0) return false;
- if (intVal == 1) return true;
- }
-
- throw new FormatException($"Could not parse bool value: {input}");
- }
-
- if (type == typeof(string))
- {
- return input;
- }
-
- if (type == typeof(int))
- {
- return Parse.Int32(input);
- }
-
- if (type == typeof(float))
- {
- return Parse.Float(input);
- }
-
- if (type == typeof(long))
- {
- return long.Parse(input);
- }
-
- if (type == typeof(ushort))
- {
- return ushort.Parse(input);
- }
-
- throw new NotSupportedException();
- }
}
internal sealed class CVarSubsCommand : LocalizedCommands
diff --git a/Robust.Shared/Enums/OverlaySpaces.cs b/Robust.Shared/Enums/OverlaySpaces.cs
index deefdc64c..28be50a76 100644
--- a/Robust.Shared/Enums/OverlaySpaces.cs
+++ b/Robust.Shared/Enums/OverlaySpaces.cs
@@ -54,6 +54,11 @@ public enum OverlaySpace : ushort
///
/// Overlay will be rendered below grids, entities, and everything else. In world space.
///
- WorldSpaceBelowWorld = 1 << 8
+ WorldSpaceBelowWorld = 1 << 8,
+
+ ///
+ /// Called after GLClear but before FOV applied to the lighting buffer.
+ ///
+ BeforeLighting = 1 << 9,
}
}
diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs
index 9bdc7b208..e55c2aa05 100644
--- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs
+++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs
@@ -11,10 +11,12 @@ namespace Robust.Shared.GameObjects
///
public abstract class BoundUserInterface : IDisposable
{
- [Dependency] protected readonly IEntityManager EntMan = default!;
+ [Dependency] protected internal readonly IEntityManager EntMan = default!;
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
protected readonly SharedUserInterfaceSystem UiSystem;
+ public bool IsOpened { get; protected set; }
+
public readonly Enum UiKey;
public EntityUid Owner { get; }
@@ -41,8 +43,13 @@ protected BoundUserInterface(EntityUid owner, Enum uiKey)
/// Invoked when the UI is opened.
/// Do all creation and opening of things like windows in here.
///
+ [MustCallBase]
protected internal virtual void Open()
{
+ if (IsOpened)
+ return;
+
+ IsOpened = true;
}
///
@@ -93,6 +100,10 @@ protected internal virtual void ReceiveMessage(BoundUserInterfaceMessage message
///
public void Close()
{
+ if (!IsOpened)
+ return;
+
+ IsOpened = false;
UiSystem.CloseUi(Owner, UiKey, PlayerManager.LocalEntity, predicted: true);
}
diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs
index 95889c291..ec99bd4da 100644
--- a/Robust.Shared/GameObjects/EntityManager.Components.cs
+++ b/Robust.Shared/GameObjects/EntityManager.Components.cs
@@ -197,17 +197,23 @@ public void AddComponents(EntityUid target, ComponentRegistry registry, bool rem
{
var reg = _componentFactory.GetRegistration(name);
- if (HasComponent(target, reg.Type))
+ if (removeExisting)
{
- if (!removeExisting)
+ var comp = _componentFactory.GetComponent(reg);
+ _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
+ AddComponentInternal(target, comp, reg, overwrite: true, metadata: metadata);
+ }
+ else
+ {
+ if (HasComponent(target, reg))
+ {
continue;
+ }
- RemoveComponent(target, reg.Type, metadata);
+ var comp = _componentFactory.GetComponent(reg);
+ _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
+ AddComponentInternal(target, comp, reg, overwrite: false, metadata: metadata);
}
-
- var comp = _componentFactory.GetComponent(reg);
- _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
- AddComponent(target, comp, metadata: metadata);
}
}
@@ -315,6 +321,22 @@ public void AddComponent(EntityUid uid, T component, bool overwrite = false,
AddComponentInternal(uid, component, overwrite, false, metadata);
}
+ private void AddComponentInternal(
+ EntityUid uid,
+ T component,
+ ComponentRegistration compReg,
+ bool overwrite = false,
+ MetaDataComponent? metadata = null) where T : IComponent
+ {
+ if (!MetaQuery.Resolve(uid, ref metadata, false))
+ throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
+
+ DebugTools.Assert(component.Owner == default);
+ component.Owner = uid;
+
+ AddComponentInternal(uid, component, compReg, overwrite, skipInit: false, metadata);
+ }
+
private void AddComponentInternal(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent
{
if (!MetaQuery.ResolveInternal(uid, ref metadata, false))
@@ -731,6 +753,14 @@ public bool HasComponent([NotNullWhen(true)] EntityUid? uid) where T : ICompo
return uid.HasValue && HasComponent(uid.Value);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [Pure]
+ public bool HasComponent(EntityUid uid, ComponentRegistration reg)
+ {
+ var dict = _entTraitArray[reg.Idx.Value];
+ return dict.TryGetValue(uid, out var comp) && !comp.Deleted;
+ }
+
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
@@ -943,6 +973,23 @@ public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(
return false;
}
+ ///
+ public bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component)
+ {
+ var dict = _entTraitArray[reg.Idx.Value];
+ if (dict.TryGetValue(uid, out var comp))
+ {
+ if (!comp.Deleted)
+ {
+ component = comp;
+ return true;
+ }
+ }
+
+ component = null;
+ return false;
+ }
+
///
public bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component)
{
diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs
index 84b47c53f..11937cde0 100644
--- a/Robust.Shared/GameObjects/IEntityManager.Components.cs
+++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs
@@ -176,6 +176,14 @@ public partial interface IEntityManager
/// True if the entity has the component type, otherwise false.
bool HasComponent([NotNullWhen(true)] EntityUid? uid) where T : IComponent;
+ ///
+ /// Checks if the entity has a component type.
+ ///
+ /// Entity UID to check.
+ /// The component registration to check for.
+ /// True if the entity has the component type, otherwise false.
+ bool HasComponent(EntityUid uid, ComponentRegistration reg);
+
///
/// Checks if the entity has a component type.
///
@@ -294,6 +302,15 @@ public partial interface IEntityManager
/// If the component existed in the entity.
bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
+ ///
+ /// Returns the component of a specific type.
+ ///
+ /// Entity UID to check.
+ /// The component registration to check for.
+ /// Component of the specified type (if exists).
+ /// If the component existed in the entity.
+ bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component);
+
///
/// Returns the component of a specific type.
///
diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
index 53cd5af40..7af2d9cb4 100644
--- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
@@ -312,7 +312,7 @@ private void ApplyChunkData(
if (data.IsDeleted())
{
- if (!component.Chunks.TryGetValue(index, out var deletedChunk))
+ if (!component.Chunks.Remove(index, out var deletedChunk))
return;
// Deleted chunks still need to raise tile-changed events.
@@ -330,11 +330,13 @@ private void ApplyChunkData(
}
}
- component.Chunks.Remove(index);
return;
}
var chunk = GetOrAddChunk(uid, component, index);
+ chunk.Fixtures.Clear();
+ chunk.Fixtures.UnionWith(data.Fixtures);
+
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
@@ -355,12 +357,6 @@ private void ApplyChunkData(
// These should never refer to the same object
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
- if (!chunk.Fixtures.SetEquals(data.Fixtures))
- {
- chunk.Fixtures.Clear();
- chunk.Fixtures.UnionWith(data.Fixtures);
- }
-
chunk.CachedBounds = data.CachedBounds!.Value;
chunk.SuppressCollisionRegeneration = false;
}
diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs
index a5e514305..ed03a1c65 100644
--- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs
@@ -119,7 +119,8 @@ public bool AnchorEntity(EntityUid uid, TransformComponent xform)
public bool AnchorEntity(Entity entity, Entity? grid = null)
{
- DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid);
+ DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid,
+ $"Tried to anchor entity {Name(entity)} to a grid ({grid!.Value.Owner}) different from its GridUid ({entity.Comp.GridUid})");
if (grid == null)
{
diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs
index 540867900..bbeb508bb 100644
--- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Numerics;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Shared.Collections;
@@ -40,11 +41,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
///
private readonly List<(BoundUserInterface Bui, bool value)> _queuedBuis = new();
- ///
- /// Temporary storage for BUI keys
- ///
- private ValueList _keys = new();
-
public override void Initialize()
{
base.Initialize();
@@ -86,6 +82,11 @@ public override void Initialize()
SubscribeLocalEvent(OnActorShutdown);
}
+ private void AddQueued(BoundUserInterface bui, bool value)
+ {
+ _queuedBuis.Add((bui, value));
+ }
+
///
/// Validates the received message, and then pass it onto systems/components
///
@@ -232,7 +233,7 @@ private void CloseUiInternal(Entity ent, Enum key, Enti
if (ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
{
- _queuedBuis.Add((cBui, false));
+ AddQueued(cBui, false);
}
if (ent.Comp.Actors.Count == 0)
@@ -274,18 +275,18 @@ private void OnUserInterfaceStartup(Entity ent, ref Comp
// PlayerAttachedEvent will catch some of these.
foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces)
{
- _queuedBuis.Add((bui, true));
+ AddQueued(bui, true);
}
}
- private void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args)
+ protected virtual void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args)
{
- var actors = new List();
+ var ents = new ValueList();
foreach (var (key, acts) in ent.Comp.Actors)
{
- actors.Clear();
- actors.AddRange(acts);
- foreach (var actor in actors)
+ ents.Clear();
+ ents.AddRange(acts);
+ foreach (var actor in ents)
{
CloseUiInternal(ent!, key, actor);
DebugTools.Assert(!acts.Contains(actor));
@@ -456,7 +457,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref
}
var bui = ent.Comp.ClientOpenInterfaces[key];
- _queuedBuis.Add((bui, false));
+ AddQueued(bui, false);
}
}
@@ -480,7 +481,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref
ent.Comp.States[key] = buiState;
- if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
+ if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui) || !cBui.IsOpened)
continue;
cBui.State = buiState;
@@ -537,7 +538,7 @@ private void EnsureClientBui(Entity entity, Enum key, In
if (!open)
return;
- _queuedBuis.Add((boundUserInterface, true));
+ AddQueued(boundUserInterface, true);
}
///
@@ -653,6 +654,14 @@ public bool TryOpenUi(Entity entity, Enum key, EntityUi
return true;
}
+ ///
+ /// Opens the UI for the local client. Does nothing on server.
+ ///
+ public virtual void OpenUi(Entity entity, Enum key, bool predicted = false)
+ {
+
+ }
+
public void OpenUi(Entity entity, Enum key, EntityUid? actor, bool predicted = false)
{
if (actor == null || !UIQuery.Resolve(entity.Owner, ref entity.Comp, false))
@@ -690,6 +699,23 @@ public void OpenUi(Entity entity, Enum key, ICommonSess
OpenUi(entity, key, actorEnt.Value, predicted);
}
+ ///
+ /// Tries to return the saved position of a user interface.
+ ///
+ public virtual bool TryGetPosition(Entity entity, Enum key, out Vector2 position)
+ {
+ position = Vector2.Zero;
+ return false;
+ }
+
+ ///
+ /// Saves a position for the BUI.
+ ///
+ protected virtual void SavePosition(BoundUserInterface bui)
+ {
+
+ }
+
///
/// Sets a BUI state and networks it to all clients.
///
@@ -723,8 +749,11 @@ public void SetUiState(Entity entity, Enum key, BoundUs
// Predict the change on client
if (state != null && _netManager.IsClient && entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var bui))
{
- bui.UpdateState(state);
- bui.Update();
+ if (bui.State?.Equals(state) != true)
+ {
+ bui.UpdateState(state);
+ bui.Update();
+ }
}
DirtyField(entity, nameof(UserInterfaceComponent.States));
@@ -862,12 +891,13 @@ public void CloseUserUis(Entity actor) where T:
if (actor.Comp.OpenInterfaces.Count == 0)
return;
+ var keys = new ValueList();
foreach (var (uid, enums) in actor.Comp.OpenInterfaces)
{
- _keys.Clear();
- _keys.AddRange(enums);
+ keys.Clear();
+ keys.AddRange(enums);
- foreach (var weh in _keys)
+ foreach (var weh in keys)
{
if (weh is not T)
continue;
@@ -888,12 +918,14 @@ public void CloseUserUis(Entity actor)
if (actor.Comp.OpenInterfaces.Count == 0)
return;
+ var keys = new ValueList();
+
foreach (var (uid, enums) in actor.Comp.OpenInterfaces)
{
- _keys.Clear();
- _keys.AddRange(enums);
+ keys.Clear();
+ keys.AddRange(enums);
- foreach (var key in _keys)
+ foreach (var key in keys)
{
CloseUiInternal(uid, key, actor.Owner);
}
@@ -1056,6 +1088,7 @@ public override void Update(float frameTime)
}
#endif
}
+ // Close BUI
else
{
if (UIQuery.TryComp(bui.Owner, out var uiComp))
@@ -1063,6 +1096,7 @@ public override void Update(float frameTime)
uiComp.ClientOpenInterfaces.Remove(bui.UiKey);
}
+ SavePosition(bui);
bui.Dispose();
}
}
diff --git a/Robust.Shared/GameStates/SharedPvsOverrideSystem.cs b/Robust.Shared/GameStates/SharedPvsOverrideSystem.cs
new file mode 100644
index 000000000..93d76dde7
--- /dev/null
+++ b/Robust.Shared/GameStates/SharedPvsOverrideSystem.cs
@@ -0,0 +1,50 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Player;
+
+namespace Robust.Shared.GameStates;
+
+public abstract class SharedPvsOverrideSystem : EntitySystem
+{
+ ///
+ /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
+ /// causing them to always be sent to all clients.
+ ///
+ public virtual void AddGlobalOverride(EntityUid uid)
+ {
+
+ }
+
+ ///
+ /// Removes an entity from the global overrides.
+ ///
+ public virtual void RemoveGlobalOverride(EntityUid uid)
+ {
+
+ }
+
+ ///
+ /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a
+ /// specific session.
+ ///
+ public virtual void AddSessionOverride(EntityUid uid, ICommonSession session)
+ {
+
+ }
+
+ ///
+ /// Removes an entity from a session's overrides.
+ ///
+ public virtual void RemoveSessionOverride(EntityUid uid, ICommonSession session)
+ {
+
+ }
+
+ ///
+ /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
+ /// causing them to always be sent to all clients.
+ ///
+ public virtual void AddSessionOverrides(EntityUid uid, Filter filter)
+ {
+
+ }
+}
diff --git a/Robust.Shared/Map/Components/MapLightComponent.cs b/Robust.Shared/Map/Components/MapLightComponent.cs
index a1afe9a28..57126fc53 100644
--- a/Robust.Shared/Map/Components/MapLightComponent.cs
+++ b/Robust.Shared/Map/Components/MapLightComponent.cs
@@ -17,7 +17,6 @@ public sealed partial class MapLightComponent : Component
///
/// Ambient light. This is in linear-light, i.e. when providing a fixed colour, you must use Color.FromSrgb(Color.Black)!
///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("ambientLightColor")]
- public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
+ [DataField]
+ public Color AmbientLightColor = Color.FromSrgb(Color.Black);
}
diff --git a/Robust.Shared/Map/Tile.cs b/Robust.Shared/Map/Tile.cs
index 23c6ad9f3..a08470a6d 100644
--- a/Robust.Shared/Map/Tile.cs
+++ b/Robust.Shared/Map/Tile.cs
@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
+using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Map;
@@ -16,9 +17,9 @@ namespace Robust.Shared.Map;
public readonly int TypeId;
///
- /// Rendering flags.
+ /// Custom flags for additional tile-data.
///
- public readonly TileRenderFlag Flags;
+ public readonly byte Flags;
///
/// Variant of this tile to render.
@@ -39,9 +40,9 @@ namespace Robust.Shared.Map;
/// Creates a new instance of a grid tile.
///
/// Internal type ID.
- /// Flags used by toolbox's rendering.
+ /// Custom tile flags for usage.
/// The visual variant this tile is using.
- public Tile(int typeId, TileRenderFlag flags = 0, byte variant = 0)
+ public Tile(int typeId, byte flags = 0, byte variant = 0)
{
TypeId = typeId;
Flags = flags;
@@ -112,9 +113,19 @@ public override int GetHashCode()
return (TypeId.GetHashCode() * 397) ^ Flags.GetHashCode() ^ Variant.GetHashCode();
}
}
-}
-public enum TileRenderFlag : byte
-{
+ [Pure]
+ public Tile WithVariant(byte variant)
+ {
+ return new Tile(TypeId, Flags, variant);
+ }
+ [Pure]
+ public Tile WithFlag(byte flag)
+ {
+ return new Tile(TypeId, flags: flag, variant: Variant);
+ }
}
+
+public sealed class TileFlagLayer {}
+
diff --git a/Robust.Shared/Network/AuthManager.cs b/Robust.Shared/Network/AuthManager.cs
index ea2250e89..e81c38d80 100644
--- a/Robust.Shared/Network/AuthManager.cs
+++ b/Robust.Shared/Network/AuthManager.cs
@@ -1,5 +1,9 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Robust.Shared.Serialization;
+
namespace Robust.Shared.Network
{
@@ -10,8 +14,11 @@ namespace Robust.Shared.Network
///
internal interface IAuthManager
{
+ public const string CustomServerId = "Custom";
+
NetUserId? UserId { get; set; }
- string? Server { get; set; }
+ AuthServer UserServer { get; set; }
+ HashSet Servers { get; set; }
string? Token { get; set; }
string? PubKey { get; set; }
@@ -23,42 +30,67 @@ internal interface IAuthManager
void LoadFromEnv();
}
+ public sealed class AuthServer(string id, Uri authUrl)
+ {
+ public string Id { get; } = id;
+ public Uri AuthUrl { get; } = authUrl;
+
+ /// Returns a string representation of the auth server
+ /// "Space-Wizards:https://auth.spacestation14.com/"
+ public override string ToString() => $"{Id}:{AuthUrl}";
+
+ /// Returns a string representation of a list of auth servers
+ /// "Space-Wizards:https://auth.spacestation14.com/,SimpleStation:https://auth.simplestation.org/"
+ public static string ToStringList(HashSet servers) => string.Join(',', servers.Select(s => s.ToString()));
+
+ /// Takes a representation of an auth server and returns an AuthServer object
+ /// "Space-Wizards:https://auth.spacestation14.com/"
+ public static AuthServer FromString(string str)
+ {
+ var parts = str.Split(':');
+ if (parts.Length != 2)
+ throw new ArgumentException($"Invalid auth server string: {str}");
+
+ return new(parts[0], new(parts[1]));
+ }
+
+ /// Takes a list of auth server representations and returns a HashSet of AuthServer objects
+ /// "Space-Wizards:https://auth.spacestation14.com/,SimpleStation:https://auth.simplestation.org/"
+ public static HashSet FromStringList(string str) => new(str.Split(',').Select(FromString));
+ }
+
internal sealed class AuthManager : IAuthManager
{
- public const string DefaultAuthServer = "https://auth.spacestation14.com/";
+ public static readonly AuthServer FallbackAuthServer = new("Space-Wizards", new("https://auth.spacestation14.com/"));
+ public static readonly HashSet DefaultAuthServers = new()
+ {
+ FallbackAuthServer,
+ new("SimpleStation", new("https://auth.simplestation.org/")),
+ };
public NetUserId? UserId { get; set; }
- public string? Server { get; set; } = DefaultAuthServer;
+ public AuthServer UserServer { get; set; } = FallbackAuthServer;
+ public HashSet Servers { get; set; } = DefaultAuthServers;
public string? Token { get; set; }
public string? PubKey { get; set; }
public bool AllowHwid { get; set; } = true;
public void LoadFromEnv()
{
- if (TryGetVar("ROBUST_AUTH_SERVER", out var server))
- {
- Server = server;
- }
+ if (TryGetVar("ROBUST_AUTH_SERVERS", out var servers))
+ Servers = AuthServer.FromStringList(servers);
if (TryGetVar("ROBUST_AUTH_USERID", out var userId))
- {
UserId = new NetUserId(Guid.Parse(userId));
- }
if (TryGetVar("ROBUST_AUTH_PUBKEY", out var pubKey))
- {
PubKey = pubKey;
- }
if (TryGetVar("ROBUST_AUTH_TOKEN", out var token))
- {
Token = token;
- }
if (TryGetVar("ROBUST_AUTH_ALLOW_HWID", out var allowHwid))
- {
AllowHwid = allowHwid.Trim() == "1";
- }
static bool TryGetVar(string var, [NotNullWhen(true)] out string? val)
{
diff --git a/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs b/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs
index ca8d39755..a29bd2047 100644
--- a/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs
+++ b/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs
@@ -16,6 +16,7 @@ internal sealed class MsgLoginStart : NetMessage
public override MsgGroups MsgGroup => MsgGroups.Core;
public string UserName;
+ public string AuthServer;
public bool CanAuth;
public bool NeedPubKey;
public bool Encrypt;
@@ -23,6 +24,7 @@ internal sealed class MsgLoginStart : NetMessage
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
UserName = buffer.ReadString();
+ AuthServer = buffer.ReadString();
CanAuth = buffer.ReadBoolean();
NeedPubKey = buffer.ReadBoolean();
Encrypt = buffer.ReadBoolean();
@@ -31,6 +33,7 @@ public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(UserName);
+ buffer.Write(AuthServer);
buffer.Write(CanAuth);
buffer.Write(NeedPubKey);
buffer.Write(Encrypt);
diff --git a/Robust.Shared/Network/NetManager.ClientConnect.cs b/Robust.Shared/Network/NetManager.ClientConnect.cs
index 7e8a4d3c9..221343c58 100644
--- a/Robust.Shared/Network/NetManager.ClientConnect.cs
+++ b/Robust.Shared/Network/NetManager.ClientConnect.cs
@@ -126,7 +126,7 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str
var encrypt = _config.GetCVar(CVars.NetEncrypt);
var authToken = _authManager.Token;
var pubKey = _authManager.PubKey;
- var authServer = _authManager.Server;
+ var authServer = _authManager.UserServer;
var userId = _authManager.UserId;
var hasPubKey = !string.IsNullOrEmpty(pubKey);
@@ -137,9 +137,10 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str
var msgLogin = new MsgLoginStart
{
UserName = userNameRequest,
+ AuthServer = authServer.AuthUrl.ToString(),
CanAuth = authenticate,
NeedPubKey = !hasPubKey,
- Encrypt = encrypt
+ Encrypt = encrypt,
};
var outLoginMsg = peer.Peer.CreateMessage();
@@ -199,7 +200,7 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str
}
var joinReq = new JoinRequest(authHash, Base64Helpers.ToBase64Nullable(modernHwid));
- var request = new HttpRequestMessage(HttpMethod.Post, authServer + "api/session/join");
+ var request = new HttpRequestMessage(HttpMethod.Post, authServer.AuthUrl + "api/session/join");
request.Content = JsonContent.Create(joinReq);
request.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", authToken);
var joinResp = await _http.Client.SendAsync(request, cancel);
diff --git a/Robust.Shared/Network/NetManager.ServerAuth.cs b/Robust.Shared/Network/NetManager.ServerAuth.cs
index a8eda20dc..14c8d1db4 100644
--- a/Robust.Shared/Network/NetManager.ServerAuth.cs
+++ b/Robust.Shared/Network/NetManager.ServerAuth.cs
@@ -50,11 +50,11 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection)
var isLocal = IPAddress.IsLoopback(ip) && _config.GetCVar(CVars.AuthAllowLocal);
var canAuth = msgLogin.CanAuth;
var needPk = msgLogin.NeedPubKey;
- var authServer = _config.GetCVar(CVars.AuthServer);
+ var authServer = msgLogin.AuthServer;
_logger.Verbose(
$"{connection.RemoteEndPoint}: Received MsgLoginStart. " +
- $"canAuth: {canAuth}, needPk: {needPk}, username: {msgLogin.UserName}, encrypt: {msgLogin.Encrypt}");
+ $"canAuth: {canAuth}, needPk: {needPk}, username: {msgLogin.UserName}, auth server: {msgLogin.AuthServer}, encrypt: {msgLogin.Encrypt}");
_logger.Verbose(
$"{connection.RemoteEndPoint}: Connection is specialized local? {isLocal} ");
@@ -78,6 +78,13 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection)
_logger.Verbose(
$"{connection.RemoteEndPoint}: Initiating authentication");
+ var servers = _authManager.Servers.Select(s => s.AuthUrl.ToString()).ToArray();
+ if (!servers.Contains(authServer))
+ {
+ connection.Disconnect($"Invalid or disallowed auth server {authServer}, needs to be one of {string.Join(", ", servers)}");
+ return;
+ }
+
var verifyToken = new byte[4];
RandomNumberGenerator.Fill(verifyToken);
var wantHwid = _config.GetCVar(CVars.NetHWId);
diff --git a/Robust.Shared/Physics/Collision/DistanceProxy.cs b/Robust.Shared/Physics/Collision/DistanceProxy.cs
index 2438c50fe..60603a31c 100644
--- a/Robust.Shared/Physics/Collision/DistanceProxy.cs
+++ b/Robust.Shared/Physics/Collision/DistanceProxy.cs
@@ -71,13 +71,13 @@ internal void Set(T shape, int index) where T : IPhysShape
case ShapeType.Polygon:
if (shape is Polygon poly)
{
- Vertices = poly.Vertices;
+ Vertices = poly.Vertices.AsSpan()[..poly.VertexCount];
Radius = poly.Radius;
}
else
{
var polyShape = Unsafe.As(shape);
- Vertices = polyShape.Vertices;
+ Vertices = polyShape.Vertices.AsSpan()[..polyShape.VertexCount];
Radius = polyShape.Radius;
}
diff --git a/Robust.Shared/Physics/Shapes/Polygon.cs b/Robust.Shared/Physics/Shapes/Polygon.cs
index b9a42a143..c96585b62 100644
--- a/Robust.Shared/Physics/Shapes/Polygon.cs
+++ b/Robust.Shared/Physics/Shapes/Polygon.cs
@@ -16,7 +16,7 @@ internal record struct Polygon : IPhysShape
[DataField]
public Vector2[] Vertices;
- public int VertexCount => Vertices.Length;
+ public byte VertexCount;
public Vector2[] Normals;
@@ -42,17 +42,19 @@ public Polygon(PolygonShape polyShape)
Array.Copy(polyShape.Vertices, Vertices, Vertices.Length);
Array.Copy(polyShape.Normals, Normals, Vertices.Length);
+ VertexCount = (byte) Vertices.Length;
}
///
/// Manually constructed polygon for internal use to take advantage of pooling.
///
- internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid)
+ internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid, byte count)
{
Unsafe.SkipInit(out this);
Vertices = vertices;
Normals = normals;
Centroid = centroid;
+ VertexCount = count;
}
public Polygon(Box2 aabb)
@@ -72,6 +74,7 @@ public Polygon(Box2 aabb)
Normals[2] = new Vector2(0.0f, 1.0f);
Normals[3] = new Vector2(-1.0f, 0.0f);
+ VertexCount = 4;
Centroid = aabb.Center;
}
@@ -89,6 +92,7 @@ public Polygon(Box2Rotated bounds)
CalculateNormals(Normals, 4);
+ VertexCount = 4;
Centroid = bounds.Center;
}
@@ -99,11 +103,13 @@ public Polygon(Vector2[] vertices)
if (hull.Count < 3)
{
+ VertexCount = 0;
Vertices = [];
Normals = [];
return;
}
+ VertexCount = (byte) vertices.Length;
Vertices = vertices;
Normals = new Vector2[vertices.Length];
Set(hull);
diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs
index e8107174e..ef946d3b8 100644
--- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs
+++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Numerics;
+using Microsoft.Extensions.ObjectPool;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Shapes;
@@ -7,15 +8,13 @@ namespace Robust.Shared.Physics.Systems;
public abstract partial class SharedPhysicsSystem
{
- private ArrayPool _vectorPool = ArrayPool.Create(4, 128);
-
///
/// Gets a polygon with pooled arrays backing it.
///
internal Polygon GetPooled(Box2 box)
{
- var vertices = _vectorPool.Rent(4);
- var normals = _vectorPool.Rent(4);
+ var vertices = ArrayPool.Shared.Rent(4);
+ var normals = ArrayPool.Shared.Rent(4);
var centroid = box.Center;
vertices[0] = box.BottomLeft;
@@ -28,13 +27,13 @@ internal Polygon GetPooled(Box2 box)
normals[2] = new Vector2(0.0f, 1.0f);
normals[3] = new Vector2(-1.0f, 0.0f);
- return new Polygon(vertices, normals, centroid);
+ return new Polygon(vertices, normals, centroid, 4);
}
internal Polygon GetPooled(Box2Rotated box)
{
- var vertices = _vectorPool.Rent(4);
- var normals = _vectorPool.Rent(4);
+ var vertices = ArrayPool.Shared.Rent(4);
+ var normals = ArrayPool.Shared.Rent(4);
var centroid = box.Center;
vertices[0] = box.BottomLeft;
@@ -42,7 +41,7 @@ internal Polygon GetPooled(Box2Rotated box)
vertices[2] = box.TopRight;
vertices[3] = box.TopLeft;
- var polygon = new Polygon(vertices, normals, centroid);
+ var polygon = new Polygon(vertices, normals, centroid, 4);
polygon.CalculateNormals(normals, 4);
return polygon;
@@ -50,7 +49,7 @@ internal Polygon GetPooled(Box2Rotated box)
internal void ReturnPooled(Polygon polygon)
{
- _vectorPool.Return(polygon.Vertices);
- _vectorPool.Return(polygon.Normals);
+ ArrayPool.Shared.Return(polygon.Vertices);
+ ArrayPool.Shared.Return(polygon.Normals);
}
}
diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs
index 397cf084c..b0b4c459f 100644
--- a/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs
+++ b/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs
@@ -26,14 +26,23 @@ private delegate DataNode PushCompositionDelegate(
public DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null)
{
+ // TODO SERIALIZATION
+ // Add variant that doesn't require a parent array.
+
+ // TODO SERIALIZATION
+ // Change inheritance pushing so that it modifies the passed in child. This avoids re-creating the child
+ // multiple times when there are multiple children.
+ //
+ // I.e., change the PushCompositionDelegate signature to not have a return value, and also add an override
+ // of this method that modified the given child.
+
if (parents.Length == 0)
return child.Copy();
DebugTools.Assert(parents.All(x => x.GetType() == child.GetType()));
- // TODO SERIALIZATION
- // Change inheritance pushing so that it modifies the passed in child.
- // I.e., move the child.Clone() statement to the beginning here, then make the delegate modify the clone.
+
+ // the child.Clone() statement to the beginning here, then make the delegate modify the clone.
// Currently pusing more than one parent requires multiple unnecessary clones.
var pusher = GetOrCreatePushCompositionDelegate(type, child);
diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs b/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs
index a89792651..68391843d 100644
--- a/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs
+++ b/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs
@@ -57,13 +57,11 @@ private void InitializeFlagsAndConstants(IEnumerable flags, IEnumerable(true))
{
- if (_flagsMapping.ContainsKey(flagType.Tag))
+ if (!_flagsMapping.TryAdd(flagType.Tag, bitflagType))
{
throw new NotSupportedException($"Multiple bitflag enums declared for the tag {flagType.Tag}.");
}
- _flagsMapping.Add(flagType.Tag, bitflagType);
-
var highestBit = bitflagType
.GetEnumValues()
.Cast()
diff --git a/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs b/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs
index bd4a35be6..fe605f6e3 100644
--- a/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs
+++ b/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs
@@ -400,7 +400,11 @@ public bool Remove(KeyValuePair item)
public bool TryAdd(DataNode key, DataNode value)
{
- return _children.TryAdd(key, value);
+ if (!_children.TryAdd(key, value))
+ return false;
+
+ _list.Add(new(key, value));
+ return true;
}
public bool TryAddCopy(DataNode key, DataNode value)
@@ -410,6 +414,7 @@ public bool TryAddCopy(DataNode key, DataNode value)
return false;
entry = value.Copy();
+ _list.Add(new(key, entry));
return true;
}
}
diff --git a/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs b/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs
index 8a6a3eeb0..1ba57f582 100644
--- a/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs
+++ b/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs
@@ -37,12 +37,12 @@ public void GetTileRefCoords()
gridOptions.ChunkSize = 8;
var grid = mapMan.CreateGridEntity(mapId, gridOptions);
- mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1));
+ mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(typeId: 1, flags: 1, variant: 1));
var result = mapSystem.GetTileRef(grid.Owner, grid.Comp, new Vector2i(-9, -1));
Assert.That(grid.Comp.ChunkCount, Is.EqualTo(1));
Assert.That(mapSystem.GetMapChunks(grid.Owner, grid.Comp).Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
- Assert.That(result, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9,-1), new Tile(1, (TileRenderFlag)1, 1))));
+ Assert.That(result, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9,-1), new Tile(typeId: 1, flags: 1, variant: 1))));
}
///
@@ -169,14 +169,14 @@ public void TryGetTileRefTileExists()
gridOptions.ChunkSize = 8;
var grid = mapMan.CreateGridEntity(mapId, gridOptions);
- mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1));
+ mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(typeId: 1, flags: 1, variant: 1));
var foundTile = mapSystem.TryGetTileRef(grid.Owner, grid.Comp, new Vector2i(-9, -1), out var tileRef);
Assert.That(foundTile, Is.True);
Assert.That(grid.Comp.ChunkCount, Is.EqualTo(1));
Assert.That(mapSystem.GetMapChunks(grid.Owner, grid.Comp).Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
- Assert.That(tileRef, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1))));
+ Assert.That(tileRef, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9, -1), new Tile(typeId: 1, flags: 1, variant: 1))));
}
[Test]
diff --git a/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs b/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs
index 0e0db47d1..e38164aaf 100644
--- a/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs
+++ b/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs
@@ -78,7 +78,7 @@ await server.WaitPost(() =>
sMap = sys.CreateMap(out var mapId);
var comp = mapMan.CreateGridEntity(mapId);
grid = (comp.Owner, comp);
- sys.SetTile(grid, grid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag)1, 1));
+ sys.SetTile(grid, grid, new Vector2i(0, 0), new Tile(typeId: 1, flags: 1, variant: 1));
var coords = new EntityCoordinates(grid, 0.5f, 0.5f);
sPlayer = sEntMan.SpawnEntity(null, coords);