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 @@ -![Robust Toolbox](https://raw.githubusercontent.com/space-wizards/asset-dump/3dd3078e49e3a7e06709a6e0fc6e3223d8d44ca2/robust.png) +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);