Compare commits

...

15 Commits

Author SHA1 Message Date
PJB3005
3538d2797d Version: 246.0.2 2025-09-19 09:17:37 +02:00
Skye
f7cb66f832 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:37 +02:00
PJB3005
c9f6fc7182 Version: 246.0.1 2025-09-14 14:58:26 +02:00
PJB3005
bc8b37de9a Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
(cherry picked from commit 4413695c77fb705054c2f81fa18ec0a189b685dd)
2025-09-14 14:58:25 +02:00
metalgearsloth
9d1b15ab4b Version: 246.0.0 2025-02-16 19:32:42 +11:00
metalgearsloth
ea1cc5e446 Planet lighting pre-reqs (#5490)
* Add another lookup overload

* Fix RenderInRenderTarget

See the linked issue for what happens.

* Also this one

* stuff

* Fix stencilling

* fixes

* mix blend

* fix

* blur fixes

* Tile flag

* Minor tweak

* Fixes

* Render state fixes

* Fixes

* Fix stupidity

* More state render bug fixes

* MapUid on overlay draw

* Remove blur comment

* Fixes

* Fixes

* Remove

* Engine vibe
2025-02-16 19:29:32 +11:00
metalgearsloth
bcb5c2d35d Version: 245.1.0 2025-02-16 14:56:39 +11:00
metalgearsloth
c011eff80e Increase audio despawn buffer (#5665)
Apparently it can clip and the buffer is really just there so we despawn 'at some point' and rather than hunching over my debugger for potentially an hour this is easier and almost no impact.

I've also considered flagging some audio as "play the full thing" if someone misses the start of it but need to thonk on that one a bit in future.
2025-02-16 14:30:18 +11:00
Fildrance
e163c496c3 fix: fixed EntityPrototypeView not reacting on SetPrototype when EnteredTree already was called with _currentPrototype empty (#5649)
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2025-02-16 14:29:59 +11:00
Tayrtahn
fec81bc2a1 Add more info to AnchorEntity debug assert (#5668) 2025-02-16 03:16:23 +01:00
Leon Friedrich
7016facb9a Tweak UserInterfaceComponent shutdown to prevent bugs (#5678) 2025-02-14 18:16:24 +11:00
Simon
0c41a041e3 Move ParseObject method into a public class for content to use (#5674) 2025-02-14 14:21:22 +11:00
ElectroJr
55571ef5b1 Version: 245.0.0 2025-02-14 16:11:50 +13:00
Leon Friedrich
afaef645b0 Fix MappingDataNode.TryAddCopy() (#5677) 2025-02-14 14:10:13 +11:00
Milon
d442d90d60 no more (#5676) 2025-02-13 01:41:42 -05:00
43 changed files with 587 additions and 216 deletions

View File

@@ -57,7 +57,7 @@
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,6 +54,60 @@ END TEMPLATE-->
*None yet*
## 246.0.2
## 246.0.1
## 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

View File

@@ -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

View File

@@ -6,7 +6,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public static class Program
internal static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
{
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
cachePath = Path.Combine(rootDir, "cef_cache", "0");
}
var settings = new CefSettings()
{

View File

@@ -382,7 +382,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -125,7 +125,8 @@ namespace Robust.Client.Graphics.Clyde
{
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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)

View File

@@ -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 @@ namespace Robust.Client.Graphics.Clyde
{
const float arbitraryDistanceMax = 1234;
GL.Disable(EnableCap.Blend);
CheckGlError();
IsBlending = false;
GL.Enable(EnableCap.DepthTest);
CheckGlError();
@@ -329,8 +326,7 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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<MapLightComponent>(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 @@ namespace Robust.Client.Graphics.Clyde
}
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
return (state.count, expandedBounds);
}
private void BlurLights(Viewport viewport, IEye eye)
/// <inheritdoc/>
[Pure]
public Color GetClearColor(EntityUid mapUid)
{
using var _ = DebugGroup(nameof(BlurLights));
return _entityManager.GetComponentOrNull<MapLightComponent>(mapUid)?.AmbientLightColor ??
MapLightComponent.DefaultColor;
}
GL.Disable(EnableCap.Blend);
CheckGlError();
/// <inheritdoc/>
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 @@ namespace Robust.Client.Graphics.Clyde
// 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 @@ namespace Robust.Client.Graphics.Clyde
// 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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
IntPtr.Zero);
CheckGlError();
GL.Enable(EnableCap.Blend);
CheckGlError();
IsBlending = true;
}
private void ApplyFovToBuffer(Viewport viewport, IEye eye)
@@ -827,8 +847,7 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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)}");
}

View File

@@ -30,6 +30,20 @@ namespace Robust.Client.Graphics.Clyde
// 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 @@ namespace Robust.Client.Graphics.Clyde
Size = size,
TextureHandle = textureObject.TextureId,
MemoryPressure = pressure,
ColorFormat = format.ColorFormat
ColorFormat = format.ColorFormat,
SampleParameters = sampleParameters,
};
//GC.AddMemoryPressure(pressure);
@@ -251,9 +266,15 @@ namespace Robust.Client.Graphics.Clyde
}
[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 @@ namespace Robust.Client.Graphics.Clyde
// Renderbuffer handle
public GLHandle DepthStencilHandle;
public long MemoryPressure;
public TextureSampleParameters? SampleParameters;
}
private abstract class RenderTargetBase : IRenderTarget

View File

@@ -90,9 +90,61 @@ namespace Robust.Client.Graphics.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;
/// <summary>
/// Tracks enabled GL capabilities for renderer state.
/// </summary>
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<RenderCommand> _queuedRenderCommands = new RefList<RenderCommand>();
@@ -364,16 +416,17 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
}
CheckGlError();
}
else if (oldIsScissoring)
{
GL.Disable(EnableCap.ScissorTest);
CheckGlError();
}
}
// NOTE: sRGB IS IN LINEAR IF FRAMEBUFFER_SRGB IS ACTIVE.
@@ -420,17 +468,11 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
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 @@ namespace Robust.Client.Graphics.Clyde
{
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,
}
}
}

View File

@@ -21,6 +21,7 @@ using Robust.Shared.Log;
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 @@ namespace Robust.Client.Graphics.Clyde
overrideVersion != null,
_windowing!.GetDescription());
GL.Enable(EnableCap.Blend);
IsBlending = true;
if (_hasGLSrgb && !_isGLES)
{
GL.Enable(EnableCap.FramebufferSrgb);

View File

@@ -9,6 +9,7 @@ using Robust.Client.Input;
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 @@ namespace Robust.Client.Graphics.Clyde
return new DummyTexture(size);
}
/// <inheritdoc />
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)
{

View File

@@ -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 @@ namespace Robust.Client.Graphics
in TextureLoadParameters? loadParams = null)
where T : unmanaged, IPixel<T>;
/// <summary>
/// Gets the clear color for the specified map viewport.
/// </summary>
[Pure]
Color GetClearColor(EntityUid mapUid);
/// <summary>
/// Applies a blur to the specified render target. Requires a separate buffer with similar properties to draw intermediate steps into.
/// </summary>
/// <param name="viewport">The viewport being used for drawing.</param>
/// <param name="target">The blur target.</param>
/// <param name="blurBuffer">The separate buffer to draw into.</param>
/// <param name="eye">The eye being drawn with.</param>
/// <param name="multiplier">Scale of how much blur to blur by.</param>
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);

View File

@@ -18,6 +18,7 @@ namespace Robust.Client.Graphics
/// </summary>
IRenderTexture RenderTarget { get; }
IRenderTexture LightRenderTarget { get; }
IEye? Eye { get; set; }
Vector2i Size { get; }

View File

@@ -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 @@ namespace Robust.Client.Graphics
Vector2i Size { get; }
void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel<T>;
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;
}
}
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics;
/// <summary>
/// Raised by the engine if content wishes to override the default clear color.
/// </summary>
[ByRefEvent]
public record struct GetClearColorEvent
{
public Color? Color;
}

View File

@@ -9,6 +9,5 @@ namespace Robust.Client.Graphics
public bool DrawHardFov { get; set; } = true;
public bool DrawLighting { get; set; } = true;
public bool LockConsoleAccess { get; set; } = false;
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
}
}

View File

@@ -1,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 @@ namespace Robust.Client.Graphics
/// </summary>
public readonly UIBox2i ViewportBounds;
public readonly EntityUid MapUid;
/// <summary>
/// <see cref="MapId"/> of the viewport's eye.
/// </summary>
@@ -65,6 +68,7 @@ namespace Robust.Client.Graphics
IClydeViewport viewport,
IRenderHandle renderHandle,
in UIBox2i viewportBounds,
in EntityUid mapUid,
in MapId mapId,
in Box2 worldAabb,
in Box2Rotated worldBounds)
@@ -78,6 +82,7 @@ namespace Robust.Client.Graphics
Viewport = viewport;
RenderHandle = renderHandle;
ViewportBounds = viewportBounds;
MapUid = mapUid;
MapId = mapId;
WorldAABB = worldAabb;
WorldBounds = worldBounds;

View File

@@ -9,6 +9,7 @@ public class EntityPrototypeView : SpriteView
{
private string? _currentPrototype;
private EntityUid? _ourEntity;
private bool _isShowing;
public EntityPrototypeView()
{
@@ -31,7 +32,7 @@ public class EntityPrototypeView : SpriteView
_currentPrototype = entProto;
if (_ourEntity != null)
if (_ourEntity != null || _isShowing)
{
UpdateEntity();
}
@@ -45,6 +46,8 @@ public class EntityPrototypeView : SpriteView
{
UpdateEntity();
}
_isShowing = true;
}
protected override void ExitedTree()
@@ -52,6 +55,8 @@ public class EntityPrototypeView : SpriteView
base.ExitedTree();
EntMan.TryQueueDeleteEntity(_ourEntity);
_ourEntity = null;
_isShowing = false;
}
private void UpdateEntity()

View File

@@ -297,7 +297,7 @@ namespace Robust.Server
: null;
// Set up the VFS
_resources.Initialize(dataDir);
_resources.Initialize(dataDir, hideUserDataDir: false);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;

View File

@@ -73,7 +73,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
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];

View File

@@ -50,6 +50,9 @@ public sealed class GamePrototypeLoadManager : SharedPrototypeLoadManager
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
{

View File

@@ -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;
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
@@ -234,7 +236,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
{
var timed = EnsureComp<TimedDespawnComponent>(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 @@ public abstract partial class SharedAudioSystem : EntitySystem
var despawn = AddComp<TimedDespawnComponent>(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)

View File

@@ -8,6 +8,58 @@ using Robust.Shared.Utility;
namespace Robust.Shared.Configuration
{
public static class CVarCommandUtil
{
/// <summary>
/// Parses a string into an object of the given type.
/// </summary>
/// <exception cref="FormatException">Thrown if the string could not be parsed into the given type.</exception>
/// <exception cref="NotSupportedException">Thrown if the type is not supported.</exception>
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 @@ namespace Robust.Shared.Configuration
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 @@ namespace Robust.Shared.Configuration
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

View File

@@ -60,7 +60,7 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
}
/// <inheritdoc />

View File

@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
/// The directory to use for user data.
/// If null, a virtual temporary file system is used instead.
/// </param>
void Initialize(string? userData);
/// <param name="hideUserDataDir">
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
/// <see cref="IResourceManager.UserData"/>.
/// </param>
void Initialize(string? userData, bool hideUserDataDir);
/// <summary>
/// Mounts a single stream as a content file. Useful for unit testing.

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
{
/// <summary>
/// The root path of this provider.
/// Can be null if it's a virtual provider.
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
/// </summary>
string? RootDir { get; }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
!OperatingSystem.IsWindows()
&& !OperatingSystem.IsMacOS();
internal static string SafeGetResourcePath(string baseDir, ResPath path)
{
var relSysPath = path.ToRelativeSystemPath();
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
// better safe than sorry check
if (!retPath.StartsWith(baseDir))
{
// Allow path to match if it's just missing the directory separator at the end.
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return retPath;
}
}
}

View File

@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
public IWritableDirProvider UserData { get; private set; } = default!;
/// <inheritdoc />
public virtual void Initialize(string? userData)
public virtual void Initialize(string? userData, bool hideRootDir)
{
Sawmill = _logManager.GetSawmill("res");
if (userData != null)
{
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
}
else
{
@@ -379,7 +379,13 @@ namespace Robust.Shared.ContentPack
{
if (root is DirLoader loader)
{
yield return new ResPath(loader.GetPath(new ResPath(@"/")));
var rootDir = loader.GetPath(new ResPath(@"/"));
// TODO: GET RID OF THIS.
// This code shouldn't be passing OS disk paths through ResPath.
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
yield return new ResPath(rootDir);
}
}
}

View File

@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
/// <inheritdoc />
internal sealed class WritableDirProvider : IWritableDirProvider
{
/// <inheritdoc />
private readonly bool _hideRootDir;
public string RootDir { get; }
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
/// <summary>
/// Constructs an instance of <see cref="WritableDirProvider"/>.
/// </summary>
/// <param name="rootDir">Root file system directory to allow writing.</param>
public WritableDirProvider(DirectoryInfo rootDir)
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
{
// FullName does not have a trailing separator, and we MUST have a separator.
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
_hideRootDir = hideRootDir;
}
#region File Access
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
throw new FileNotFoundException();
var dirInfo = new DirectoryInfo(GetFullPath(path));
return new WritableDirProvider(dirInfo);
return new WritableDirProvider(dirInfo, _hideRootDir);
}
/// <inheritdoc />
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
path = path.Clean();
return GetFullPath(RootDir, path);
}
private static string GetFullPath(string root, ResPath path)
{
var relPath = path.ToRelativeSystemPath();
if (relPath.Contains("\\..") || relPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return Path.GetFullPath(Path.Combine(root, relPath));
return PathHelpers.SafeGetResourcePath(RootDir, path);
}
}
}

View File

@@ -54,6 +54,11 @@ namespace Robust.Shared.Enums
/// <summary>
/// Overlay will be rendered below grids, entities, and everything else. In world space.
/// </summary>
WorldSpaceBelowWorld = 1 << 8
WorldSpaceBelowWorld = 1 << 8,
/// <summary>
/// Called after GLClear but before FOV applied to the lighting buffer.
/// </summary>
BeforeLighting = 1 << 9,
}
}

View File

@@ -43,6 +43,7 @@ namespace Robust.Shared.GameObjects
/// Invoked when the UI is opened.
/// Do all creation and opening of things like windows in here.
/// </summary>
[MustCallBase]
protected internal virtual void Open()
{
if (IsOpened)

View File

@@ -119,7 +119,8 @@ public abstract partial class SharedTransformSystem
public bool AnchorEntity(Entity<TransformComponent> entity, Entity<MapGridComponent>? 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)
{

View File

@@ -41,13 +41,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
/// </summary>
private readonly List<(BoundUserInterface Bui, bool value)> _queuedBuis = new();
/// <summary>
/// Temporary storage for BUI keys
/// </summary>
private ValueList<Enum> _keys = new();
private ValueList<EntityUid> _entList = new();
public override void Initialize()
{
base.Initialize();
@@ -288,11 +281,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
protected virtual void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
{
var ents = new ValueList<EntityUid>();
foreach (var (key, acts) in ent.Comp.Actors)
{
_entList.Clear();
_entList.AddRange(acts);
foreach (var actor in _entList)
ents.Clear();
ents.AddRange(acts);
foreach (var actor in ents)
{
CloseUiInternal(ent!, key, actor);
DebugTools.Assert(!acts.Contains(actor));
@@ -897,12 +891,13 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (actor.Comp.OpenInterfaces.Count == 0)
return;
var keys = new ValueList<Enum>();
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;
@@ -923,12 +918,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (actor.Comp.OpenInterfaces.Count == 0)
return;
var keys = new ValueList<Enum>();
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);
}

View File

@@ -17,7 +17,6 @@ public sealed partial class MapLightComponent : Component
/// <summary>
/// Ambient light. This is in linear-light, i.e. when providing a fixed colour, you must use Color.FromSrgb(Color.Black)!
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("ambientLightColor")]
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
[DataField]
public Color AmbientLightColor = Color.FromSrgb(Color.Black);
}

View File

@@ -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 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
public readonly int TypeId;
/// <summary>
/// Rendering flags.
/// Custom flags for additional tile-data.
/// </summary>
public readonly TileRenderFlag Flags;
public readonly byte Flags;
/// <summary>
/// Variant of this tile to render.
@@ -39,9 +40,9 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
/// Creates a new instance of a grid tile.
/// </summary>
/// <param name="typeId">Internal type ID.</param>
/// <param name="flags">Flags used by toolbox's rendering.</param>
/// <param name="flags">Custom tile flags for usage.</param>
/// <param name="variant">The visual variant this tile is using.</param>
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 readonly struct Tile : IEquatable<Tile>, ISpanFormattable
return (TypeId.GetHashCode() * 397) ^ Flags.GetHashCode() ^ Variant.GetHashCode();
}
}
[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 enum TileRenderFlag : byte
{
public sealed class TileFlagLayer {}
}

View File

@@ -26,14 +26,23 @@ public partial class SerializationManager
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);

View File

@@ -57,13 +57,11 @@ namespace Robust.Shared.Serialization.Manager
foreach (var flagType in bitflagType.GetCustomAttributes<FlagsForAttribute>(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<int>()

View File

@@ -400,7 +400,11 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
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 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return false;
entry = value.Copy();
_list.Add(new(key, entry));
return true;
}
}

View File

@@ -37,12 +37,12 @@ namespace Robust.UnitTesting.Shared.Map
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))));
}
/// <summary>
@@ -169,14 +169,14 @@ namespace Robust.UnitTesting.Shared.Map
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]

View File

@@ -78,7 +78,7 @@ public sealed class GridDeleteSingleTileRemoveTestTest : RobustIntegrationTest
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);

View File

@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Resources
_testDir = Directory.CreateDirectory(_testDirPath);
var subDir = Path.Combine(_testDirPath, "writable");
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir));
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir), hideRootDir: false);
}
[OneTimeTearDown]