mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee330d0ae9 |
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -38,4 +38,4 @@ jobs:
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
run: dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -13,9 +13,3 @@
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
[submodule "Linguini"]
|
||||
path = Linguini
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
[submodule "cefglue"]
|
||||
path = cefglue
|
||||
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 4b36b36f39...5fc11c2b2b
1
Linguini
1
Linguini
Submodule Linguini deleted from 26c2608f9b
Submodule NetSerializer updated: ab1b651292...2d4f8b5611
@@ -129,7 +129,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
/// for controlling sRGB rendering and a created OpenGL ES context will always have sRGB rendering enabled.
|
||||
/// </summary>
|
||||
SrgbCapable = 0x0002100E,
|
||||
|
||||
ScaleToMonitor = 0x0002200C,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5551,10 +5551,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
{
|
||||
return glfwGetX11Window(window);
|
||||
}
|
||||
|
||||
public static unsafe IntPtr GetWin32Window(Window* window)
|
||||
{
|
||||
return glfwGetWin32Window(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.so.3", assembly, path);
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
|
||||
}
|
||||
@@ -406,8 +406,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern uint glfwGetX11Window(Window* window);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern IntPtr glfwGetWin32Window(Window* window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,48 +23,6 @@
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- name: Box2D
|
||||
license:
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Erin Catto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- name: Bullet Physics SDK
|
||||
license:
|
||||
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
http://bulletphysics.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
- name: Castle Core
|
||||
license: |
|
||||
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
|
||||
@@ -359,43 +317,6 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
- name: Farseer Physics Engine
|
||||
license:
|
||||
Microsoft Permissive License (Ms-PL)
|
||||
|
||||
This license governs use of the accompanying software.
|
||||
If you use the software, you accept this license.
|
||||
If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
- name: Mono.Cecil
|
||||
license: |
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
|
||||
1
Resources/Locale/en-US/console.ftl
Normal file
1
Resources/Locale/en-US/console.ftl
Normal file
@@ -0,0 +1 @@
|
||||
console-line-edit-placeholder = Command Here
|
||||
@@ -1,11 +0,0 @@
|
||||
## EntitySpawnWindow
|
||||
|
||||
entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
@@ -1 +0,0 @@
|
||||
tab-container-not-tab-title-provided = No title
|
||||
@@ -1,11 +0,0 @@
|
||||
## ViewVariablesInstanceEntity
|
||||
|
||||
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
|
||||
view-variable-instance-entity-client-variables-tab-title = Client Variables
|
||||
view-variable-instance-entity-client-components-tab-title = Client Components
|
||||
view-variable-instance-entity-server-variables-tab-title = Server Variables
|
||||
view-variable-instance-entity-server-components-tab-title = Server Components
|
||||
view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
@@ -1,107 +0,0 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class FriendAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";
|
||||
|
||||
public const string DiagnosticId = "RA0002";
|
||||
|
||||
private const string Title = "Tried to access friend-only member";
|
||||
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
|
||||
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
|
||||
private const string Category = "Usage";
|
||||
|
||||
[SuppressMessage("ReSharper", "RS2008")]
|
||||
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(CheckFriendship, SyntaxKind.SimpleMemberAccessExpression);
|
||||
}
|
||||
|
||||
private void CheckFriendship(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not MemberAccessExpressionSyntax memberAccess)
|
||||
return;
|
||||
|
||||
// We only do something if our parent is one of a few types.
|
||||
switch (context.Node.Parent)
|
||||
{
|
||||
// If we're being assigned...
|
||||
case AssignmentExpressionSyntax assignParent:
|
||||
{
|
||||
if (assignParent.Left != memberAccess)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're being invoked...
|
||||
case InvocationExpressionSyntax:
|
||||
break;
|
||||
|
||||
// Otherwise, do nothing.
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the friend attribute
|
||||
var friendAttr = context.Compilation.GetTypeByMetadataName(FriendAttribute);
|
||||
|
||||
// Get the type that is containing this expression, or, the class where this is happening.
|
||||
if (context.ContainingSymbol?.ContainingType is not { } containingType)
|
||||
return;
|
||||
|
||||
// We check all of our children and get only the identifiers.
|
||||
foreach (var identifier in memberAccess.ChildNodes().Select(node => node as IdentifierNameSyntax))
|
||||
{
|
||||
if (identifier == null) continue;
|
||||
|
||||
// Get the type info of the identifier, so we can check the attributes...
|
||||
if (context.SemanticModel.GetTypeInfo(identifier).ConvertedType is not { } type)
|
||||
continue;
|
||||
|
||||
// Same-type access is always fine.
|
||||
if (SymbolEqualityComparer.Default.Equals(type, containingType))
|
||||
continue;
|
||||
|
||||
// Finally, get all attributes of the type, to check if we have any friend classes.
|
||||
foreach (var attribute in type.GetAttributes())
|
||||
{
|
||||
// If the attribute isn't the friend attribute, continue.
|
||||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, friendAttr))
|
||||
continue;
|
||||
|
||||
// Check all types allowed in the friend attribute. (We assume there's only one constructor arg.)
|
||||
foreach (var constant in attribute.ConstructorArguments[0].Values)
|
||||
{
|
||||
// Check if the value is a type...
|
||||
if (constant.Value is not INamedTypeSymbol t)
|
||||
continue;
|
||||
|
||||
// If we find that the containing class is specified in the attribute, return! All is good.
|
||||
if (SymbolEqualityComparer.Default.Equals(containingType, t))
|
||||
return;
|
||||
}
|
||||
|
||||
// Not in a friend class! Report an error.
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, context.Node.GetLocation(),
|
||||
$"{context.Node.ToString().Split('.').LastOrDefault()}", $"{type.Name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers
|
||||
{
|
||||
public class AddBenchmark
|
||||
{
|
||||
[Params(32, 128)]
|
||||
public int N { get; set; }
|
||||
|
||||
private float[] _inputA = default!;
|
||||
private float[] _inputB = default!;
|
||||
private float[] _output = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_inputA = new float[N];
|
||||
_inputB = new float[N];
|
||||
_output = new float[N];
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Bench()
|
||||
{
|
||||
Shared.Maths.NumericsHelpers.Add(_inputA, _inputB, _output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Benchmarks
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -36,10 +35,6 @@ namespace Robust.Benchmarks.Serialization.Copy
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
|
||||
|
||||
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
|
||||
|
||||
[Benchmark]
|
||||
public string? CreateCopyString()
|
||||
{
|
||||
@@ -116,25 +111,5 @@ namespace Robust.Benchmarks.Serialization.Copy
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public object? CreateCopyFlagZero()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
(int) FlagZero,
|
||||
(int) FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public object? CreateCopyFlagThirtyOne()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
(int) FlagThirtyOne,
|
||||
(int) FlagThirtyOne);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
public class BenchmarkFlags
|
||||
{
|
||||
public const int Zero = 1 << 0;
|
||||
public const int ThirtyOne = 1 << 31;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[FlagsFor(typeof(BenchmarkFlags))]
|
||||
public enum BenchmarkFlagsEnum
|
||||
{
|
||||
Zero = BenchmarkFlags.Zero,
|
||||
ThirtyOne = BenchmarkFlags.ThirtyOne
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Read
|
||||
@@ -34,10 +32,6 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
|
||||
private MappingDataNode SeedNode { get; }
|
||||
|
||||
private ValueDataNode FlagZero { get; } = new("Zero");
|
||||
|
||||
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
|
||||
|
||||
[Benchmark]
|
||||
public string? ReadString()
|
||||
{
|
||||
@@ -61,25 +55,5 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
{
|
||||
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadFlagZero()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadThirtyOne()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagThirtyOne);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Write
|
||||
@@ -36,10 +35,6 @@ namespace Robust.Benchmarks.Serialization.Write
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
|
||||
|
||||
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteString()
|
||||
{
|
||||
@@ -99,25 +94,5 @@ namespace Robust.Benchmarks.Serialization.Write
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DataNode WriteFlagZero()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DataNode WriteThirtyOne()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagThirtyOne);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class BeforeBrowseContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
public string Url => CefRequest.Url;
|
||||
public string Method => CefRequest.Method;
|
||||
|
||||
public bool IsRedirect { get; }
|
||||
public bool UserGesture { get; }
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal BeforeBrowseContext(
|
||||
bool isRedirect,
|
||||
bool userGesture,
|
||||
CefRequest cefRequest)
|
||||
{
|
||||
CefRequest = cefRequest;
|
||||
IsRedirect = isRedirect;
|
||||
UserGesture = userGesture;
|
||||
}
|
||||
|
||||
public void DoCancel()
|
||||
{
|
||||
IsCancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,593 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
using static Robust.Client.CEF.CefKeyCodes;
|
||||
using static Robust.Client.CEF.CefKeyCodes.ChromiumKeyboardCode;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Funny browser control to integrate in UI.
|
||||
public class BrowserControl : Control, IBrowserControl, IRawInputControl
|
||||
{
|
||||
private const int ScrollSpeed = 50;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputMgr = default!;
|
||||
[Dependency] private readonly CefManager _cef = default!;
|
||||
|
||||
private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
private LiveData? _data;
|
||||
private string _startUrl = "about:blank";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
|
||||
set
|
||||
{
|
||||
if (_data == null)
|
||||
_startUrl = value;
|
||||
else
|
||||
_data.Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
private readonly Dictionary<Key, ChromiumKeyboardCode> _keyMap = new()
|
||||
{
|
||||
[Key.A] = VKEY_A,
|
||||
[Key.B] = VKEY_B,
|
||||
[Key.C] = VKEY_C,
|
||||
[Key.D] = VKEY_D,
|
||||
[Key.E] = VKEY_E,
|
||||
[Key.F] = VKEY_F,
|
||||
[Key.G] = VKEY_G,
|
||||
[Key.H] = VKEY_H,
|
||||
[Key.I] = VKEY_I,
|
||||
[Key.J] = VKEY_J,
|
||||
[Key.K] = VKEY_K,
|
||||
[Key.L] = VKEY_L,
|
||||
[Key.M] = VKEY_M,
|
||||
[Key.N] = VKEY_N,
|
||||
[Key.O] = VKEY_O,
|
||||
[Key.P] = VKEY_P,
|
||||
[Key.Q] = VKEY_Q,
|
||||
[Key.R] = VKEY_R,
|
||||
[Key.S] = VKEY_S,
|
||||
[Key.T] = VKEY_T,
|
||||
[Key.U] = VKEY_U,
|
||||
[Key.V] = VKEY_V,
|
||||
[Key.W] = VKEY_W,
|
||||
[Key.X] = VKEY_X,
|
||||
[Key.Y] = VKEY_Y,
|
||||
[Key.Z] = VKEY_Z,
|
||||
[Key.Num0] = VKEY_0,
|
||||
[Key.Num1] = VKEY_1,
|
||||
[Key.Num2] = VKEY_2,
|
||||
[Key.Num3] = VKEY_3,
|
||||
[Key.Num4] = VKEY_4,
|
||||
[Key.Num5] = VKEY_5,
|
||||
[Key.Num6] = VKEY_6,
|
||||
[Key.Num7] = VKEY_7,
|
||||
[Key.Num8] = VKEY_8,
|
||||
[Key.Num9] = VKEY_9,
|
||||
[Key.NumpadNum0] = VKEY_NUMPAD0,
|
||||
[Key.NumpadNum1] = VKEY_NUMPAD1,
|
||||
[Key.NumpadNum2] = VKEY_NUMPAD2,
|
||||
[Key.NumpadNum3] = VKEY_NUMPAD3,
|
||||
[Key.NumpadNum4] = VKEY_NUMPAD4,
|
||||
[Key.NumpadNum5] = VKEY_NUMPAD5,
|
||||
[Key.NumpadNum6] = VKEY_NUMPAD6,
|
||||
[Key.NumpadNum7] = VKEY_NUMPAD7,
|
||||
[Key.NumpadNum8] = VKEY_NUMPAD8,
|
||||
[Key.NumpadNum9] = VKEY_NUMPAD9,
|
||||
[Key.Escape] = VKEY_ESCAPE,
|
||||
[Key.Control] = VKEY_CONTROL,
|
||||
[Key.Shift] = VKEY_SHIFT,
|
||||
[Key.Alt] = VKEY_MENU,
|
||||
[Key.LSystem] = VKEY_LWIN,
|
||||
[Key.RSystem] = VKEY_RWIN,
|
||||
[Key.LBracket] = VKEY_OEM_4,
|
||||
[Key.RBracket] = VKEY_OEM_6,
|
||||
[Key.SemiColon] = VKEY_OEM_1,
|
||||
[Key.Comma] = VKEY_OEM_COMMA,
|
||||
[Key.Period] = VKEY_OEM_PERIOD,
|
||||
[Key.Apostrophe] = VKEY_OEM_7,
|
||||
[Key.Slash] = VKEY_OEM_2,
|
||||
[Key.BackSlash] = VKEY_OEM_5,
|
||||
[Key.Tilde] = VKEY_OEM_3,
|
||||
[Key.Equal] = VKEY_OEM_PLUS,
|
||||
[Key.Space] = VKEY_SPACE,
|
||||
[Key.Return] = VKEY_RETURN,
|
||||
[Key.BackSpace] = VKEY_BACK,
|
||||
[Key.Tab] = VKEY_TAB,
|
||||
[Key.PageUp] = VKEY_PRIOR,
|
||||
[Key.PageDown] = VKEY_NEXT,
|
||||
[Key.End] = VKEY_END,
|
||||
[Key.Home] = VKEY_HOME,
|
||||
[Key.Insert] = VKEY_INSERT,
|
||||
[Key.Delete] = VKEY_DELETE,
|
||||
[Key.Minus] = VKEY_OEM_MINUS,
|
||||
[Key.NumpadAdd] = VKEY_ADD,
|
||||
[Key.NumpadSubtract] = VKEY_SUBTRACT,
|
||||
[Key.NumpadDivide] = VKEY_DIVIDE,
|
||||
[Key.NumpadMultiply] = VKEY_MULTIPLY,
|
||||
[Key.NumpadDecimal] = VKEY_DECIMAL,
|
||||
[Key.Left] = VKEY_LEFT,
|
||||
[Key.Right] = VKEY_RIGHT,
|
||||
[Key.Up] = VKEY_UP,
|
||||
[Key.Down] = VKEY_DOWN,
|
||||
[Key.F1] = VKEY_F1,
|
||||
[Key.F2] = VKEY_F2,
|
||||
[Key.F3] = VKEY_F3,
|
||||
[Key.F4] = VKEY_F4,
|
||||
[Key.F5] = VKEY_F5,
|
||||
[Key.F6] = VKEY_F6,
|
||||
[Key.F7] = VKEY_F7,
|
||||
[Key.F8] = VKEY_F8,
|
||||
[Key.F9] = VKEY_F9,
|
||||
[Key.F10] = VKEY_F10,
|
||||
[Key.F11] = VKEY_F11,
|
||||
[Key.F12] = VKEY_F12,
|
||||
[Key.F13] = VKEY_F13,
|
||||
[Key.F14] = VKEY_F14,
|
||||
[Key.F15] = VKEY_F15,
|
||||
[Key.Pause] = VKEY_PAUSE,
|
||||
};
|
||||
|
||||
public BrowserControl()
|
||||
{
|
||||
CanKeyboardFocus = true;
|
||||
KeyboardFocusOnClick = true;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_cef.CheckInitialized();
|
||||
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
// A funny render handler that will allow us to render to the control.
|
||||
var renderer = new ControlRenderHandler(this);
|
||||
|
||||
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
|
||||
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
|
||||
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
|
||||
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
|
||||
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
|
||||
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
|
||||
info.WindowlessRenderingEnabled = true;
|
||||
|
||||
var settings = new CefBrowserSettings()
|
||||
{
|
||||
WindowlessFrameRate = 60
|
||||
};
|
||||
|
||||
// Create the web browser! And by default, we go to about:blank.
|
||||
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
|
||||
|
||||
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
|
||||
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
_data!.Texture.Dispose();
|
||||
_data.Browser.GetHost().CloseBrowser(true);
|
||||
_data = null;
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
base.MouseMove(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
// Logger.Debug();
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
|
||||
}
|
||||
|
||||
protected internal override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
|
||||
}
|
||||
|
||||
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseWheelEvent(
|
||||
mouseEvent,
|
||||
(int) args.Delta.X * ScrollSpeed,
|
||||
(int) args.Delta.Y * ScrollSpeed);
|
||||
}
|
||||
|
||||
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
if (_data == null)
|
||||
return false;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
|
||||
{
|
||||
var key = guiRawEvent.Key switch
|
||||
{
|
||||
Key.MouseLeft => CefMouseButtonType.Left,
|
||||
Key.MouseMiddle => CefMouseButtonType.Middle,
|
||||
Key.MouseRight => CefMouseButtonType.Right,
|
||||
_ => default // not possible
|
||||
};
|
||||
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
|
||||
CefEventFlags.None);
|
||||
|
||||
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
|
||||
|
||||
// TODO: double click support?
|
||||
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle left/right modifier keys??
|
||||
if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
|
||||
vkKey = default;
|
||||
|
||||
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
|
||||
|
||||
var lParam = 0;
|
||||
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
|
||||
if (guiRawEvent.Action != RawKeyAction.Down)
|
||||
lParam |= 1 << 30;
|
||||
|
||||
if (guiRawEvent.Action == RawKeyAction.Up)
|
||||
lParam |= 1 << 31;
|
||||
|
||||
var modifiers = CalcModifiers(guiRawEvent.Key);
|
||||
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
// Repeats are sent as key downs, I guess?
|
||||
EventType = guiRawEvent.Action == RawKeyAction.Up
|
||||
? CefKeyEventType.KeyUp
|
||||
: CefKeyEventType.RawKeyDown,
|
||||
NativeKeyCode = lParam,
|
||||
// NativeKeyCode = guiRawEvent.ScanCode,
|
||||
WindowsKeyCode = (int) vkKey,
|
||||
IsSystemKey = false, // TODO
|
||||
Modifiers = modifiers
|
||||
});
|
||||
|
||||
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = '\r',
|
||||
NativeKeyCode = lParam,
|
||||
Modifiers = modifiers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcModifiers(Key key)
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcMouseModifiers()
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseLeft))
|
||||
modifiers |= CefEventFlags.LeftMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
|
||||
modifiers |= CefEventFlags.MiddleMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseRight))
|
||||
modifiers |= CefEventFlags.RightMouseButton;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
protected internal override void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
base.TextEntered(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
Span<char> buf = stackalloc char[2];
|
||||
var written = args.AsRune.EncodeToUtf16(buf);
|
||||
|
||||
for (var i = 0; i < written; i++)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = buf[i],
|
||||
Character = buf[i],
|
||||
UnmodifiedCharacter = buf[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
|
||||
_data.Browser.GetHost().WasResized();
|
||||
_data.Texture.Dispose();
|
||||
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((PixelWidth, PixelHeight));
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var bufImg = _data.Renderer.Buffer.Buffer;
|
||||
|
||||
_data.Texture.SetSubImage(
|
||||
Vector2i.Zero,
|
||||
bufImg,
|
||||
new UIBox2i(
|
||||
0, 0,
|
||||
Math.Min(PixelWidth, bufImg.Width),
|
||||
Math.Min(PixelHeight, bufImg.Height)));
|
||||
|
||||
handle.DrawTexture(_data.Texture, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
// TODO: this should not run until the browser is done loading seriously does this even work?
|
||||
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.AddBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
private sealed class LiveData
|
||||
{
|
||||
public OwnedTexture Texture;
|
||||
public readonly RobustCefClient Client;
|
||||
public readonly CefBrowser Browser;
|
||||
public readonly ControlRenderHandler Renderer;
|
||||
|
||||
public LiveData(
|
||||
OwnedTexture texture,
|
||||
RobustCefClient client,
|
||||
CefBrowser browser,
|
||||
ControlRenderHandler renderer)
|
||||
{
|
||||
Texture = texture;
|
||||
Client = client;
|
||||
Browser = browser;
|
||||
Renderer = renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ControlRenderHandler : CefRenderHandler
|
||||
{
|
||||
public ImageBuffer Buffer { get; }
|
||||
private Control _control;
|
||||
|
||||
internal ControlRenderHandler(Control control)
|
||||
{
|
||||
Buffer = new ImageBuffer(control);
|
||||
_control = control;
|
||||
}
|
||||
|
||||
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
|
||||
|
||||
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
{
|
||||
rect = new CefRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
|
||||
//var screenCoords = _control.ScreenCoordinates;
|
||||
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
|
||||
|
||||
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
|
||||
rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1));
|
||||
}
|
||||
|
||||
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return false;
|
||||
|
||||
// TODO CEF: Get actual scale factor?
|
||||
screenInfo.DeviceScaleFactor = 1.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
|
||||
IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
|
||||
foreach (var dirtyRect in dirtyRects)
|
||||
{
|
||||
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects, IntPtr sharedHandle)
|
||||
{
|
||||
// Unused, but we're forced to implement it so.. NOOP.
|
||||
}
|
||||
|
||||
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
|
||||
CefRectangle[] characterBounds)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class BrowserWindowCreateParameters
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
|
||||
public string Url { get; set; } = "about:blank";
|
||||
|
||||
public BrowserWindowCreateParameters(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
[SuppressMessage("ReSharper", "CA1069")]
|
||||
[SuppressMessage("ReSharper", "CommentTypo")]
|
||||
internal static class CefKeyCodes
|
||||
{
|
||||
// Taken from https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/events/keycodes/keyboard_codes_posix.h
|
||||
// See also https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
public enum ChromiumKeyboardCode
|
||||
{
|
||||
VKEY_CANCEL = 0x03,
|
||||
VKEY_BACK = 0x08,
|
||||
VKEY_TAB = 0x09,
|
||||
VKEY_BACKTAB = 0x0A,
|
||||
VKEY_CLEAR = 0x0C,
|
||||
VKEY_RETURN = 0x0D,
|
||||
VKEY_SHIFT = 0x10,
|
||||
VKEY_CONTROL = 0x11,
|
||||
VKEY_MENU = 0x12,
|
||||
VKEY_PAUSE = 0x13,
|
||||
VKEY_CAPITAL = 0x14,
|
||||
VKEY_KANA = 0x15,
|
||||
VKEY_HANGUL = 0x15,
|
||||
VKEY_PASTE = 0x16,
|
||||
VKEY_JUNJA = 0x17,
|
||||
VKEY_FINAL = 0x18,
|
||||
VKEY_HANJA = 0x19,
|
||||
VKEY_KANJI = 0x19,
|
||||
VKEY_ESCAPE = 0x1B,
|
||||
VKEY_CONVERT = 0x1C,
|
||||
VKEY_NONCONVERT = 0x1D,
|
||||
VKEY_ACCEPT = 0x1E,
|
||||
VKEY_MODECHANGE = 0x1F,
|
||||
VKEY_SPACE = 0x20,
|
||||
VKEY_PRIOR = 0x21,
|
||||
VKEY_NEXT = 0x22,
|
||||
VKEY_END = 0x23,
|
||||
VKEY_HOME = 0x24,
|
||||
VKEY_LEFT = 0x25,
|
||||
VKEY_UP = 0x26,
|
||||
VKEY_RIGHT = 0x27,
|
||||
VKEY_DOWN = 0x28,
|
||||
VKEY_SELECT = 0x29,
|
||||
VKEY_PRINT = 0x2A,
|
||||
VKEY_EXECUTE = 0x2B,
|
||||
VKEY_SNAPSHOT = 0x2C, // Print Screen / SysRq
|
||||
VKEY_INSERT = 0x2D,
|
||||
VKEY_DELETE = 0x2E,
|
||||
VKEY_HELP = 0x2F,
|
||||
VKEY_0 = 0x30,
|
||||
VKEY_1 = 0x31,
|
||||
VKEY_2 = 0x32,
|
||||
VKEY_3 = 0x33,
|
||||
VKEY_4 = 0x34,
|
||||
VKEY_5 = 0x35,
|
||||
VKEY_6 = 0x36,
|
||||
VKEY_7 = 0x37,
|
||||
VKEY_8 = 0x38,
|
||||
VKEY_9 = 0x39,
|
||||
VKEY_A = 0x41,
|
||||
VKEY_B = 0x42,
|
||||
VKEY_C = 0x43,
|
||||
VKEY_D = 0x44,
|
||||
VKEY_E = 0x45,
|
||||
VKEY_F = 0x46,
|
||||
VKEY_G = 0x47,
|
||||
VKEY_H = 0x48,
|
||||
VKEY_I = 0x49,
|
||||
VKEY_J = 0x4A,
|
||||
VKEY_K = 0x4B,
|
||||
VKEY_L = 0x4C,
|
||||
VKEY_M = 0x4D,
|
||||
VKEY_N = 0x4E,
|
||||
VKEY_O = 0x4F,
|
||||
VKEY_P = 0x50,
|
||||
VKEY_Q = 0x51,
|
||||
VKEY_R = 0x52,
|
||||
VKEY_S = 0x53,
|
||||
VKEY_T = 0x54,
|
||||
VKEY_U = 0x55,
|
||||
VKEY_V = 0x56,
|
||||
VKEY_W = 0x57,
|
||||
VKEY_X = 0x58,
|
||||
VKEY_Y = 0x59,
|
||||
VKEY_Z = 0x5A,
|
||||
VKEY_LWIN = 0x5B,
|
||||
VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience.
|
||||
VKEY_RWIN = 0x5C,
|
||||
VKEY_APPS = 0x5D,
|
||||
VKEY_SLEEP = 0x5F,
|
||||
VKEY_NUMPAD0 = 0x60,
|
||||
VKEY_NUMPAD1 = 0x61,
|
||||
VKEY_NUMPAD2 = 0x62,
|
||||
VKEY_NUMPAD3 = 0x63,
|
||||
VKEY_NUMPAD4 = 0x64,
|
||||
VKEY_NUMPAD5 = 0x65,
|
||||
VKEY_NUMPAD6 = 0x66,
|
||||
VKEY_NUMPAD7 = 0x67,
|
||||
VKEY_NUMPAD8 = 0x68,
|
||||
VKEY_NUMPAD9 = 0x69,
|
||||
VKEY_MULTIPLY = 0x6A,
|
||||
VKEY_ADD = 0x6B,
|
||||
VKEY_SEPARATOR = 0x6C,
|
||||
VKEY_SUBTRACT = 0x6D,
|
||||
VKEY_DECIMAL = 0x6E,
|
||||
VKEY_DIVIDE = 0x6F,
|
||||
VKEY_F1 = 0x70,
|
||||
VKEY_F2 = 0x71,
|
||||
VKEY_F3 = 0x72,
|
||||
VKEY_F4 = 0x73,
|
||||
VKEY_F5 = 0x74,
|
||||
VKEY_F6 = 0x75,
|
||||
VKEY_F7 = 0x76,
|
||||
VKEY_F8 = 0x77,
|
||||
VKEY_F9 = 0x78,
|
||||
VKEY_F10 = 0x79,
|
||||
VKEY_F11 = 0x7A,
|
||||
VKEY_F12 = 0x7B,
|
||||
VKEY_F13 = 0x7C,
|
||||
VKEY_F14 = 0x7D,
|
||||
VKEY_F15 = 0x7E,
|
||||
VKEY_F16 = 0x7F,
|
||||
VKEY_F17 = 0x80,
|
||||
VKEY_F18 = 0x81,
|
||||
VKEY_F19 = 0x82,
|
||||
VKEY_F20 = 0x83,
|
||||
VKEY_F21 = 0x84,
|
||||
VKEY_F22 = 0x85,
|
||||
VKEY_F23 = 0x86,
|
||||
VKEY_F24 = 0x87,
|
||||
VKEY_NUMLOCK = 0x90,
|
||||
VKEY_SCROLL = 0x91,
|
||||
VKEY_LSHIFT = 0xA0,
|
||||
VKEY_RSHIFT = 0xA1,
|
||||
VKEY_LCONTROL = 0xA2,
|
||||
VKEY_RCONTROL = 0xA3,
|
||||
VKEY_LMENU = 0xA4,
|
||||
VKEY_RMENU = 0xA5,
|
||||
VKEY_BROWSER_BACK = 0xA6,
|
||||
VKEY_BROWSER_FORWARD = 0xA7,
|
||||
VKEY_BROWSER_REFRESH = 0xA8,
|
||||
VKEY_BROWSER_STOP = 0xA9,
|
||||
VKEY_BROWSER_SEARCH = 0xAA,
|
||||
VKEY_BROWSER_FAVORITES = 0xAB,
|
||||
VKEY_BROWSER_HOME = 0xAC,
|
||||
VKEY_VOLUME_MUTE = 0xAD,
|
||||
VKEY_VOLUME_DOWN = 0xAE,
|
||||
VKEY_VOLUME_UP = 0xAF,
|
||||
VKEY_MEDIA_NEXT_TRACK = 0xB0,
|
||||
VKEY_MEDIA_PREV_TRACK = 0xB1,
|
||||
VKEY_MEDIA_STOP = 0xB2,
|
||||
VKEY_MEDIA_PLAY_PAUSE = 0xB3,
|
||||
VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
|
||||
VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
|
||||
VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
|
||||
VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
|
||||
VKEY_OEM_1 = 0xBA,
|
||||
VKEY_OEM_PLUS = 0xBB,
|
||||
VKEY_OEM_COMMA = 0xBC,
|
||||
VKEY_OEM_MINUS = 0xBD,
|
||||
VKEY_OEM_PERIOD = 0xBE,
|
||||
VKEY_OEM_2 = 0xBF,
|
||||
VKEY_OEM_3 = 0xC0,
|
||||
VKEY_OEM_4 = 0xDB,
|
||||
VKEY_OEM_5 = 0xDC,
|
||||
VKEY_OEM_6 = 0xDD,
|
||||
VKEY_OEM_7 = 0xDE,
|
||||
VKEY_OEM_8 = 0xDF,
|
||||
VKEY_OEM_102 = 0xE2,
|
||||
VKEY_OEM_103 = 0xE3, // GTV KEYCODE_MEDIA_REWIND
|
||||
VKEY_OEM_104 = 0xE4, // GTV KEYCODE_MEDIA_FAST_FORWARD
|
||||
VKEY_PROCESSKEY = 0xE5,
|
||||
VKEY_PACKET = 0xE7,
|
||||
VKEY_OEM_ATTN = 0xF0, // JIS DomKey::ALPHANUMERIC
|
||||
VKEY_OEM_FINISH = 0xF1, // JIS DomKey::KATAKANA
|
||||
VKEY_OEM_COPY = 0xF2, // JIS DomKey::HIRAGANA
|
||||
VKEY_DBE_SBCSCHAR = 0xF3, // JIS DomKey::HANKAKU
|
||||
VKEY_DBE_DBCSCHAR = 0xF4, // JIS DomKey::ZENKAKU
|
||||
VKEY_OEM_BACKTAB = 0xF5, // JIS DomKey::ROMAJI
|
||||
VKEY_ATTN = 0xF6, // DomKey::ATTN or JIS DomKey::KANA_MODE
|
||||
VKEY_CRSEL = 0xF7,
|
||||
VKEY_EXSEL = 0xF8,
|
||||
VKEY_EREOF = 0xF9,
|
||||
VKEY_PLAY = 0xFA,
|
||||
VKEY_ZOOM = 0xFB,
|
||||
VKEY_NONAME = 0xFC,
|
||||
VKEY_PA1 = 0xFD,
|
||||
VKEY_OEM_CLEAR = 0xFE,
|
||||
VKEY_UNKNOWN = 0,
|
||||
|
||||
// POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA,
|
||||
// and 0xE8 are unassigned.
|
||||
VKEY_WLAN = 0x97,
|
||||
VKEY_POWER = 0x98,
|
||||
VKEY_ASSISTANT = 0x99,
|
||||
VKEY_SETTINGS = 0x9A,
|
||||
VKEY_PRIVACY_SCREEN_TOGGLE = 0x9B,
|
||||
VKEY_BRIGHTNESS_DOWN = 0xD8,
|
||||
VKEY_BRIGHTNESS_UP = 0xD9,
|
||||
VKEY_KBD_BRIGHTNESS_DOWN = 0xDA,
|
||||
VKEY_KBD_BRIGHTNESS_UP = 0xE8,
|
||||
|
||||
// Windows does not have a specific key code for AltGr. We use the unused 0xE1
|
||||
// (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on
|
||||
// Linux.
|
||||
VKEY_ALTGR = 0xE1,
|
||||
|
||||
// Windows does not have a specific key code for Compose. We use the unused
|
||||
// 0xE6 (VK_ICO_CLEAR) code to represent Compose.
|
||||
VKEY_COMPOSE = 0xE6,
|
||||
|
||||
// Windows does not have specific key codes for Media Play and Media Pause. We
|
||||
// use the unused 0xE9 (VK_OEM_RESET) and 0xEA (VK_OEM_JUMP) codes to
|
||||
// represent them.
|
||||
VKEY_MEDIA_PLAY = 0xE9,
|
||||
VKEY_MEDIA_PAUSE = 0xEA,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public partial class CefManager
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
private readonly List<BrowserWindowImpl> _browserWindows = new();
|
||||
|
||||
public IEnumerable<IBrowserWindow> AllBrowserWindows => _browserWindows;
|
||||
|
||||
public IBrowserWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
{
|
||||
var mainHWnd = (_clyde.MainWindow as IClydeWindowInternal)?.WindowsHWnd ?? 0;
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
info.Width = createParams.Width;
|
||||
info.Height = createParams.Height;
|
||||
info.SetAsPopup(mainHWnd, "ss14cef");
|
||||
|
||||
var impl = new BrowserWindowImpl(this);
|
||||
|
||||
var lifeSpanHandler = new WindowLifeSpanHandler(impl);
|
||||
var reqHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
var client = new WindowCefClient(lifeSpanHandler, reqHandler);
|
||||
var settings = new CefBrowserSettings();
|
||||
|
||||
impl.Browser = CefBrowserHost.CreateBrowserSync(info, client, settings, createParams.Url);
|
||||
impl.RequestHandler = reqHandler;
|
||||
|
||||
_browserWindows.Add(impl);
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
private sealed class BrowserWindowImpl : IBrowserWindow
|
||||
{
|
||||
private readonly CefManager _manager;
|
||||
internal CefBrowser Browser = default!;
|
||||
internal RobustRequestHandler RequestHandler = default!;
|
||||
|
||||
public Action<RequestHandlerContext>? OnResourceRequest { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckClosed();
|
||||
return Browser.GetMainFrame().Url;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsLoading
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckClosed();
|
||||
return Browser.IsLoading;
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserWindowImpl(CefManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
CheckClosed();
|
||||
if (!Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
CheckClosed();
|
||||
if (!Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Closed)
|
||||
return;
|
||||
|
||||
Browser.GetHost().CloseBrowser(true);
|
||||
Closed = true;
|
||||
}
|
||||
|
||||
public bool Closed { get; private set; }
|
||||
|
||||
public void OnClose()
|
||||
{
|
||||
Closed = true;
|
||||
_manager._browserWindows.Remove(this);
|
||||
Logger.Debug("Removing window");
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (Closed)
|
||||
throw new ObjectDisposedException("BrowserWindow");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowCefClient : CefClient
|
||||
{
|
||||
private readonly CefLifeSpanHandler _lifeSpanHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
public WindowCefClient(CefLifeSpanHandler lifeSpanHandler, CefRequestHandler requestHandler)
|
||||
{
|
||||
_lifeSpanHandler = lifeSpanHandler;
|
||||
_requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
protected override CefLifeSpanHandler GetLifeSpanHandler() => _lifeSpanHandler;
|
||||
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
|
||||
}
|
||||
|
||||
private sealed class WindowLifeSpanHandler : CefLifeSpanHandler
|
||||
{
|
||||
private readonly BrowserWindowImpl _windowImpl;
|
||||
|
||||
public WindowLifeSpanHandler(BrowserWindowImpl windowImpl)
|
||||
{
|
||||
_windowImpl = windowImpl;
|
||||
}
|
||||
|
||||
protected override void OnBeforeClose(CefBrowser browser)
|
||||
{
|
||||
base.OnBeforeClose(browser);
|
||||
|
||||
_windowImpl.OnClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
// The library we're using right now. TODO CEF: Do we want to use something else? We will need to ship it ourselves if so.
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Register this with IoC.
|
||||
// TODO CEF: think if making this inherit CefApp is a good idea...
|
||||
// TODO CEF: A way to handle external window browsers...
|
||||
[UsedImplicitly]
|
||||
public partial class CefManager
|
||||
{
|
||||
private CefApp _app = default!;
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Call this to initialize CEF.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
DebugTools.Assert(!_initialized);
|
||||
|
||||
string subProcessName;
|
||||
if (OperatingSystem.IsWindows())
|
||||
subProcessName = "Robust.Client.CEF.exe";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
subProcessName = "Robust.Client.CEF";
|
||||
else
|
||||
throw new NotSupportedException("Unsupported platform for CEF!");
|
||||
|
||||
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
|
||||
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
|
||||
NoSandbox = true, // Not disabling the sandbox crashes CEF.
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(PathHelpers.GetExecutableDirectory(), "locales"),
|
||||
ResourcesDirPath = PathHelpers.GetExecutableDirectory(),
|
||||
RemoteDebuggingPort = 9222
|
||||
};
|
||||
|
||||
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
|
||||
|
||||
// --------------------------- README --------------------------------------------------
|
||||
// By the way! You're gonna need the CEF binaries in your client's bin folder.
|
||||
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
|
||||
// Here's how to get it to work:
|
||||
// 1. Copy all the contents of "Release" to the bin folder.
|
||||
// 2. Copy all the contents of "Resources" to the bin folder.
|
||||
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
|
||||
// The rest might be optional.
|
||||
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
_app = new RobustCefApp();
|
||||
|
||||
// We pass no main arguments...
|
||||
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
|
||||
|
||||
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
|
||||
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
|
||||
// And nothing seemed to work. Odd.
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void CheckInitialized()
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("CefManager has not been initialized!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be called regularly for CEF to keep working.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
// Calling this makes CEF do its work, without using its own update loop.
|
||||
CefRuntime.DoMessageLoopWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call before program shutdown.
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
CefRuntime.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Current URL of the browser. Set to load a new page.
|
||||
/// </summary>
|
||||
string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the browser is currently loading a page.
|
||||
/// </summary>
|
||||
bool IsLoading { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stops loading the current page.
|
||||
/// </summary>
|
||||
void StopLoad();
|
||||
|
||||
/// <summary>
|
||||
/// Reload the current page.
|
||||
/// </summary>
|
||||
void Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Navigate back.
|
||||
/// </summary>
|
||||
/// <returns>Whether the browser could navigate back.</returns>
|
||||
bool GoBack();
|
||||
|
||||
/// <summary>
|
||||
/// Navigate forward.
|
||||
/// </summary>
|
||||
/// <returns>Whether the browser could navigate forward.</returns>
|
||||
bool GoForward();
|
||||
|
||||
/// <summary>
|
||||
/// Execute arbitrary JavaScript on the current page.
|
||||
/// </summary>
|
||||
/// <param name="code">JavaScript code.</param>
|
||||
void ExecuteJavaScript(string code);
|
||||
|
||||
void AddResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserWindow : IBrowserControl, IDisposable
|
||||
{
|
||||
bool Closed { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal sealed class ImageBuffer
|
||||
{
|
||||
private readonly Control _control;
|
||||
|
||||
public ImageBuffer(Control control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
public Image<Bgra32> Buffer { get; private set; } = new(1, 1);
|
||||
|
||||
public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect)
|
||||
{
|
||||
if (width != Buffer.Width || height != Buffer.Height)
|
||||
UpdateSize(width, height);
|
||||
|
||||
var span = new ReadOnlySpan<Bgra32>((void*) buffer, width * height);
|
||||
|
||||
ImageSharpExt.Blit(
|
||||
span,
|
||||
width,
|
||||
UIBox2i.FromDimensions(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height),
|
||||
Buffer,
|
||||
(dirtyRect.X, dirtyRect.Y));
|
||||
}
|
||||
|
||||
private void UpdateSize(int width, int height)
|
||||
{
|
||||
Buffer = new Image<Bgra32>(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public 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)
|
||||
{
|
||||
// This is a workaround for this to work on UNIX.
|
||||
var argv = args;
|
||||
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
|
||||
{
|
||||
argv = new string[args.Length + 1];
|
||||
Array.Copy(args, 0, argv, 1, args.Length);
|
||||
argv[0] = "-";
|
||||
}
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
|
||||
|
||||
if (code != 0)
|
||||
{
|
||||
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class RequestHandlerContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
public bool IsNavigation { get; }
|
||||
public bool IsDownload { get; }
|
||||
public string RequestInitiator { get; }
|
||||
|
||||
public string Url => CefRequest.Url;
|
||||
public string Method => CefRequest.Method;
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal IRequestResult? Result { get; private set; }
|
||||
|
||||
internal RequestHandlerContext(
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
CefRequest cefRequest)
|
||||
{
|
||||
CefRequest = cefRequest;
|
||||
IsNavigation = isNavigation;
|
||||
IsDownload = isDownload;
|
||||
RequestInitiator = requestInitiator;
|
||||
}
|
||||
|
||||
public void DoCancel()
|
||||
{
|
||||
CheckNotHandled();
|
||||
|
||||
IsHandled = true;
|
||||
IsCancelled = true;
|
||||
}
|
||||
|
||||
public void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK)
|
||||
{
|
||||
Result = new RequestResultStream(stream, contentType, code);
|
||||
}
|
||||
|
||||
private void CheckNotHandled()
|
||||
{
|
||||
if (IsHandled)
|
||||
throw new InvalidOperationException("Request has already been handled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal interface IRequestResult
|
||||
{
|
||||
CefResourceHandler MakeHandler();
|
||||
}
|
||||
|
||||
internal sealed class RequestResultStream : IRequestResult
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpStatusCode _code;
|
||||
private readonly string _contentType;
|
||||
|
||||
public RequestResultStream(Stream stream, string contentType, HttpStatusCode code)
|
||||
{
|
||||
_stream = stream;
|
||||
_code = code;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
public CefResourceHandler MakeHandler()
|
||||
{
|
||||
return new Handler(_stream, _contentType, _code);
|
||||
}
|
||||
|
||||
private sealed class Handler : CefResourceHandler
|
||||
{
|
||||
// TODO: async
|
||||
// TODO: exception handling
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpStatusCode _code;
|
||||
private readonly string _contentType;
|
||||
|
||||
public Handler(Stream stream, string contentType, HttpStatusCode code)
|
||||
{
|
||||
_stream = stream;
|
||||
_code = code;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
protected override bool Open(CefRequest request, out bool handleRequest, CefCallback callback)
|
||||
{
|
||||
handleRequest = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void GetResponseHeaders(CefResponse response, out long responseLength, out string? redirectUrl)
|
||||
{
|
||||
response.Status = (int) _code;
|
||||
response.StatusText = _code.ToString();
|
||||
response.MimeType = _contentType;
|
||||
|
||||
if (_stream.CanSeek)
|
||||
responseLength = _stream.Length;
|
||||
else
|
||||
responseLength = -1;
|
||||
|
||||
redirectUrl = default;
|
||||
}
|
||||
|
||||
protected override bool Skip(long bytesToSkip, out long bytesSkipped, CefResourceSkipCallback callback)
|
||||
{
|
||||
if (!_stream.CanSeek)
|
||||
{
|
||||
bytesSkipped = -2;
|
||||
return false;
|
||||
}
|
||||
|
||||
bytesSkipped = _stream.Seek(bytesToSkip, SeekOrigin.Begin);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
|
||||
{
|
||||
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
|
||||
|
||||
bytesRead = _stream.Read(byteSpan);
|
||||
|
||||
return bytesRead != 0;
|
||||
}
|
||||
|
||||
protected override void Cancel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<Target Name="RobustAfterBuild" AfterTargets="Build" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
|
||||
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal class RobustCefApp : CefApp
|
||||
{
|
||||
private readonly BrowserProcessHandler _browserProcessHandler = new();
|
||||
private readonly RenderProcessHandler _renderProcessHandler = new();
|
||||
|
||||
protected override CefBrowserProcessHandler GetBrowserProcessHandler()
|
||||
{
|
||||
return _browserProcessHandler;
|
||||
}
|
||||
|
||||
protected override CefRenderProcessHandler GetRenderProcessHandler()
|
||||
{
|
||||
return _renderProcessHandler;
|
||||
}
|
||||
|
||||
protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine)
|
||||
{
|
||||
// Disable zygote on Linux.
|
||||
commandLine.AppendSwitch("--no-zygote");
|
||||
|
||||
//commandLine.AppendSwitch("--disable-gpu");
|
||||
//commandLine.AppendSwitch("--disable-gpu-compositing");
|
||||
//commandLine.AppendSwitch("--in-process-gpu");
|
||||
|
||||
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
|
||||
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");
|
||||
|
||||
if(IoCManager.Instance != null)
|
||||
Logger.Debug($"{commandLine}");
|
||||
}
|
||||
|
||||
private class BrowserProcessHandler : CefBrowserProcessHandler
|
||||
{
|
||||
}
|
||||
|
||||
// TODO CEF: Research - Is this even needed?
|
||||
private class RenderProcessHandler : CefRenderProcessHandler
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Simple CEF client.
|
||||
internal class RobustCefClient : CefClient
|
||||
{
|
||||
private readonly CefRenderHandler _renderHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
private readonly CefLoadHandler _loadHandler;
|
||||
|
||||
internal RobustCefClient(CefRenderHandler handler, CefRequestHandler requestHandler, CefLoadHandler loadHandler)
|
||||
{
|
||||
_renderHandler = handler;
|
||||
_requestHandler = requestHandler;
|
||||
_loadHandler = loadHandler;
|
||||
}
|
||||
|
||||
protected override CefRenderHandler GetRenderHandler() => _renderHandler;
|
||||
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
|
||||
protected override CefLoadHandler GetLoadHandler() => _loadHandler;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class RobustLoadHandler : CefLoadHandler
|
||||
{
|
||||
protected override void OnLoadStart(CefBrowser browser, CefFrame frame, CefTransitionType transitionType)
|
||||
{
|
||||
base.OnLoadStart(browser, frame, transitionType);
|
||||
}
|
||||
|
||||
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
|
||||
{
|
||||
base.OnLoadEnd(browser, frame, httpStatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal sealed class RobustRequestHandler : CefRequestHandler
|
||||
{
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly List<Action<RequestHandlerContext>> _resourceRequestHandlers = new();
|
||||
private readonly List<Action<BeforeBrowseContext>> _beforeBrowseHandlers = new();
|
||||
|
||||
public RobustRequestHandler(ISawmill sawmill)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_resourceRequestHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_resourceRequestHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
_beforeBrowseHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
_beforeBrowseHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
protected override CefResourceRequestHandler? GetResourceRequestHandler(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request,
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
ref bool disableDefaultHandling)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_sawmill.Debug($"HANDLING REQUEST: {request.Url}");
|
||||
|
||||
var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
|
||||
|
||||
foreach (var handler in _resourceRequestHandlers)
|
||||
{
|
||||
handler(context);
|
||||
|
||||
if (context.IsHandled)
|
||||
disableDefaultHandling = true;
|
||||
|
||||
if (context.IsCancelled)
|
||||
return null;
|
||||
|
||||
if (context.Result != null)
|
||||
return new WrapReaderResourceHandler(context.Result.MakeHandler());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override bool OnBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, bool userGesture, bool isRedirect)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
var context = new BeforeBrowseContext(isRedirect, userGesture, request);
|
||||
|
||||
foreach (var handler in _beforeBrowseHandlers)
|
||||
{
|
||||
handler(context);
|
||||
|
||||
if (context.IsCancelled)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed class WrapReaderResourceHandler : CefResourceRequestHandler
|
||||
{
|
||||
private readonly CefResourceHandler _handler;
|
||||
|
||||
public WrapReaderResourceHandler(CefResourceHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
protected override CefCookieAccessFilter? GetCookieAccessFilter(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override CefResourceHandler GetResourceHandler(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request)
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,27 +29,26 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
|
||||
class ConsoleBuildEngine : IBuildEngine
|
||||
{
|
||||
public void LogErrorEvent(BuildErrorEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
|
||||
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
}
|
||||
|
||||
public void LogWarningEvent(BuildWarningEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
|
||||
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
}
|
||||
|
||||
public void LogMessageEvent(BuildMessageEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
|
||||
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
}
|
||||
|
||||
public void LogCustomEvent(CustomBuildEventArgs e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine($"CUSTOM: {e.Message}");
|
||||
}
|
||||
|
||||
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using XamlX;
|
||||
using System.Linq;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
@@ -13,14 +11,11 @@ namespace Robust.Build.Tasks
|
||||
/// Emitters & Transformers based on:
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/afb8ae6f3c517dae912729511483995b16cb31af/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
|
||||
/// </summary>
|
||||
public class RobustXamlILCompiler : XamlILCompiler
|
||||
{
|
||||
public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
|
||||
{
|
||||
Transformers.Insert(0, new IgnoredDirectivesTransformer());
|
||||
|
||||
Transformers.Add(new AddNameScopeRegistration());
|
||||
Transformers.Add(new RobustMarkRootObjectScopeNode());
|
||||
|
||||
@@ -202,24 +197,5 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IgnoredDirectivesTransformer : IXamlAstTransformer
|
||||
{
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||
{
|
||||
if (node is XamlAstObjectNode astNode)
|
||||
{
|
||||
astNode.Children.RemoveAll(n =>
|
||||
n is XamlAstXmlDirective dir &&
|
||||
dir.Namespace == XamlNamespaces.Xaml2006 &&
|
||||
(dir.Name == "Class" ||
|
||||
dir.Name == "Precompile" ||
|
||||
dir.Name == "FieldModifier" ||
|
||||
dir.Name == "ClassModifier"));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,8 +280,8 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
|
||||
e.ToString(), "", "CompileRobustXaml"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -148,10 +147,9 @@ namespace {nameSpace}
|
||||
foreach (var typeSymbol in symbols)
|
||||
{
|
||||
var xamlFileName = $"{typeSymbol.Name}.xaml";
|
||||
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
|
||||
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
|
||||
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
|
||||
|
||||
if (relevantXamlFiles.Length == 0)
|
||||
if (relevantXamlFile == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
@@ -167,28 +165,13 @@ namespace {nameSpace}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relevantXamlFiles.Length > 1)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0002",
|
||||
$"Found multiple candidate XAML files for {typeSymbol}",
|
||||
$"Multiple files exist with name {xamlFileName}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFiles[0].GetText()?.ToString();
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
if (txt == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
"RXN0002",
|
||||
$"Unexpected empty Xaml-File was found at {xamlFileName}",
|
||||
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
|
||||
"Usage",
|
||||
@@ -209,7 +192,7 @@ namespace {nameSpace}
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0003",
|
||||
"AXN0003",
|
||||
"Unhandled exception occured while generating typed Name references.",
|
||||
$"Unhandled exception occured while generating typed Name references: {e}",
|
||||
"Usage",
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.Threading;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -63,9 +61,8 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
@@ -97,7 +94,7 @@ namespace Robust.Client.Audio.Midi
|
||||
if (MathHelper.CloseTo(_volume, value))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -134,12 +131,6 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_midiSawmill = Logger.GetSawmill("midi");
|
||||
_sawmill = Logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
@@ -175,7 +166,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
@@ -225,7 +216,7 @@ namespace Robust.Client.Audio.Midi
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
@@ -243,12 +234,12 @@ namespace Robust.Client.Audio.Midi
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
renderer.LoadSoundfont(OsxSoundfont, true);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Asynchronous;
|
||||
@@ -6,7 +7,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using MidiEvent = NFluidsynth.MidiEvent;
|
||||
|
||||
@@ -202,9 +203,7 @@ namespace Robust.Client.Audio.Midi
|
||||
private const int SampleRate = 44100;
|
||||
private const int Buffers = SampleRate / 2205;
|
||||
private readonly object _playerStateLock = new();
|
||||
private bool _debugEvents = false;
|
||||
private SequencerClientId _synthRegister;
|
||||
private SequencerClientId _debugRegister;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
|
||||
@@ -218,7 +217,7 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
for (var i = 0; i < 16; i++)
|
||||
_synth.ProgramChange(i, value);
|
||||
|
||||
_midiProgram = value;
|
||||
@@ -232,7 +231,7 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
for (var i = 0; i < 16; i++)
|
||||
_synth.BankSelect(i, value);
|
||||
|
||||
_midiBank = value;
|
||||
@@ -246,7 +245,7 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
for (var i = 0; i < 16; i++)
|
||||
_synth.SoundFontSelect(i, value);
|
||||
|
||||
_midiSoundfont = value;
|
||||
@@ -314,7 +313,6 @@ namespace Robust.Client.Audio.Midi
|
||||
_soundFontLoader = soundFontLoader;
|
||||
_synth = new Synth(_settings);
|
||||
_sequencer = new Sequencer(false);
|
||||
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
|
||||
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
|
||||
|
||||
_synth.AddSoundFontLoader(soundFontLoader);
|
||||
@@ -324,27 +322,6 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.StartPlaying();
|
||||
}
|
||||
|
||||
private void DumpSequencerEvent(uint time, SequencerEvent @event)
|
||||
{
|
||||
// ReSharper disable once UseStringInterpolation
|
||||
_midiSawmill.Debug(string.Format(
|
||||
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
|
||||
time,
|
||||
@event.Type.ToString().PadLeft(22),
|
||||
@event.Channel,
|
||||
@event.Key,
|
||||
@event.Bank,
|
||||
@event.Control,
|
||||
@event.Duration,
|
||||
@event.Pitch,
|
||||
@event.Program,
|
||||
@event.Value,
|
||||
@event.Velocity));
|
||||
|
||||
@event.Dest = _synthRegister;
|
||||
_sequencer.SendNow(@event);
|
||||
}
|
||||
|
||||
public bool OpenInput()
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -451,6 +428,9 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (Disposed) return;
|
||||
|
||||
// SSE needs this.
|
||||
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
|
||||
|
||||
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
|
||||
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
|
||||
if (buffersProcessed == 0) return;
|
||||
@@ -465,16 +445,36 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.GetBuffersProcessed(buffers);
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
// _sequencer.Process(10);
|
||||
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
|
||||
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
|
||||
}
|
||||
|
||||
if (Mono) // Turn audio to mono
|
||||
{
|
||||
var l = length * buffers.Length;
|
||||
|
||||
NumericsHelpers.Add(audio[..l], audio[l..]);
|
||||
if (Sse.IsSupported)
|
||||
{
|
||||
fixed (float* ptr = audio)
|
||||
{
|
||||
for (var j = 0; j < l; j += 4)
|
||||
{
|
||||
var k = j + l;
|
||||
|
||||
var jV = Sse.LoadVector128(ptr + j);
|
||||
var kV = Sse.LoadVector128(ptr + k);
|
||||
|
||||
Sse.Store(j + ptr, Sse.Add(jV, kV));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < l; j++)
|
||||
{
|
||||
var k = j + l;
|
||||
audio[j] = ((audio[k] + audio[j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < buffers.Length; i++)
|
||||
@@ -532,16 +532,16 @@ namespace Robust.Client.Audio.Midi
|
||||
lock(_playerStateLock)
|
||||
switch (midiEvent.Type)
|
||||
{
|
||||
// Note Off - 0x80
|
||||
case 128:
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
|
||||
// Note On 0x90
|
||||
// Note On 0x80
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// Note Off - 0x90
|
||||
case 128:
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
|
||||
// After Touch - 0xA
|
||||
case 160:
|
||||
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
@@ -573,20 +573,10 @@ namespace Robust.Client.Audio.Midi
|
||||
// Sometimes MIDI files spam these for no good reason and I can't find any info on what they are.
|
||||
case 1:
|
||||
case 5:
|
||||
// MetaEvent -- SetTempo - 0x51
|
||||
case 81:
|
||||
// Already handled by the player.
|
||||
return;
|
||||
// System Messages - 0xF0
|
||||
case 240:
|
||||
switch ((byte)midiEvent.Control)
|
||||
{
|
||||
case 11:
|
||||
_synth.AllNotesOff(midiEvent.Channel);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
return;
|
||||
|
||||
default:
|
||||
_midiSawmill.Warning("Unhandled midi event of type {0}", midiEvent.Type, midiEvent);
|
||||
@@ -607,7 +597,7 @@ namespace Robust.Client.Audio.Midi
|
||||
if (Disposed) return;
|
||||
|
||||
var seqEv = (SequencerEvent) midiEvent;
|
||||
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
|
||||
seqEv.Dest = _synthRegister;
|
||||
_sequencer.SendAt(seqEv, time, absolute);
|
||||
}
|
||||
|
||||
@@ -630,15 +620,10 @@ namespace Robust.Client.Audio.Midi
|
||||
void IMidiRenderer.InternalDispose()
|
||||
{
|
||||
Source?.Dispose();
|
||||
_driver?.Dispose();
|
||||
|
||||
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
|
||||
_sequencer?.UnregisterClient(_debugRegister);
|
||||
_sequencer?.UnregisterClient(_synthRegister);
|
||||
_sequencer?.Dispose();
|
||||
|
||||
_synth?.Dispose();
|
||||
_player?.Dispose();
|
||||
_driver?.Dispose();
|
||||
_sequencer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
@@ -25,7 +25,6 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -47,15 +46,13 @@ namespace Robust.Client
|
||||
IoCManager.Register<IClientMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
|
||||
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<GameController, GameController>();
|
||||
IoCManager.Register<IGameController, GameController>();
|
||||
IoCManager.Register<IGameControllerInternal, GameController>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IResourceManager, ResourceCache>();
|
||||
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
|
||||
IoCManager.Register<IResourceCache, ResourceCache>();
|
||||
@@ -75,9 +72,10 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
IoCManager.Register<IPhysicsManager, PhysicsManager>();
|
||||
switch (mode)
|
||||
{
|
||||
case GameController.DisplayMode.Headless:
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Robust.Client
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
@@ -32,7 +31,6 @@ namespace Robust.Client
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
@@ -126,26 +124,6 @@ namespace Robust.Client
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
@@ -164,7 +142,6 @@ namespace Robust.Client
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions);
|
||||
@@ -185,7 +162,6 @@ Options:
|
||||
--launcher Run in launcher mode (no main menu, auto connect).
|
||||
--username Override username.
|
||||
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
|
||||
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
|
||||
--mount-dir Resource directory to mount.
|
||||
--mount-zip Resource zip to mount.
|
||||
--help Display this help text and exit.
|
||||
@@ -199,7 +175,6 @@ Options:
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions)
|
||||
{
|
||||
@@ -209,7 +184,6 @@ Options:
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.Console
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="IClientConsoleHost" />
|
||||
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
|
||||
{
|
||||
@@ -45,15 +45,11 @@ namespace Robust.Client.Console
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
|
||||
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
Reset();
|
||||
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
||||
}
|
||||
|
||||
@@ -65,6 +61,17 @@ namespace Robust.Client.Console
|
||||
ExecuteCommand(null, text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
AvailableCommands.Clear();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AddStringArgs>? AddString;
|
||||
|
||||
@@ -83,8 +90,6 @@ namespace Robust.Client.Console
|
||||
OutputText(text, true, true);
|
||||
}
|
||||
|
||||
public override event ConAnyCommandCallback? AnyCommandExecuted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
@@ -92,7 +97,7 @@ namespace Robust.Client.Console
|
||||
return;
|
||||
|
||||
// echo the command locally
|
||||
WriteLine(null, "> " + command);
|
||||
WriteError(null, "> " + command);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -105,11 +110,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
var shell = new ConsoleShell(this, null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
command1.Execute(shell, command, cmdArgs);
|
||||
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
|
||||
}
|
||||
else
|
||||
WriteError(null, "Unknown command: " + commandName);
|
||||
@@ -141,9 +142,6 @@ namespace Robust.Client.Console
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
|
||||
var level = error ? LogLevel.Warning : LogLevel.Info;
|
||||
Logger.LogS(level, "CON", text);
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
|
||||
@@ -27,7 +27,6 @@ using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -78,7 +77,7 @@ namespace Robust.Client.Console.Commands
|
||||
message.Append($"net ID: {registration.NetID}");
|
||||
}
|
||||
|
||||
message.Append($", References:");
|
||||
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
|
||||
|
||||
shell.WriteLine(message.ToString());
|
||||
|
||||
@@ -460,18 +459,13 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var uiMgr = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
|
||||
var res = IoCManager.Resolve<IResourceManager>();
|
||||
|
||||
using (var stream = res.UserData.Create(new ResourcePath("/guidump.txt")))
|
||||
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
|
||||
{
|
||||
foreach (var root in uiMgr.AllRoots)
|
||||
{
|
||||
writer.WriteLine($"ROOT: {root}");
|
||||
_writeNode(root, 0, writer);
|
||||
writer.WriteLine("---------------");
|
||||
}
|
||||
_writeNode(root, 0, writer);
|
||||
}
|
||||
|
||||
shell.WriteLine("Saved guidump");
|
||||
@@ -481,7 +475,7 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var indentation = new string(' ', indents * 2);
|
||||
writer.WriteLine("{0}{1}", indentation, control);
|
||||
foreach (var (key, value) in PropertyValuesFor(control))
|
||||
foreach (var (key, value) in _propertyValuesFor(control))
|
||||
{
|
||||
writer.WriteLine("{2} * {0}: {1}", key, value, indentation);
|
||||
}
|
||||
@@ -492,14 +486,14 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
private static List<(string, string)> _propertyValuesFor(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
var type = control.GetType();
|
||||
|
||||
foreach (var fieldInfo in type.GetAllFields())
|
||||
{
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(fieldInfo, out _))
|
||||
if (fieldInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -509,7 +503,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
foreach (var propertyInfo in type.GetAllProperties())
|
||||
{
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(propertyInfo, out _))
|
||||
if (propertyInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -542,10 +536,7 @@ namespace Robust.Client.Console.Commands
|
||||
var scroll = new ScrollContainer();
|
||||
tabContainer.AddChild(scroll);
|
||||
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
var vBox = new VBoxContainer();
|
||||
scroll.AddChild(vBox);
|
||||
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
@@ -603,10 +594,7 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
|
||||
var group = new ButtonGroup();
|
||||
var vBoxRadioButtons = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
var vBoxRadioButtons = new VBoxContainer();
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
vBoxRadioButtons.AddChild(new Button
|
||||
@@ -622,9 +610,8 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
|
||||
|
||||
tabContainer.AddChild(new BoxContainer
|
||||
tabContainer.AddChild(new VBoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Name = "Slider",
|
||||
Children =
|
||||
{
|
||||
@@ -876,7 +863,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class GridChunkBBCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showchunkbb";
|
||||
public string Description => "Displays chunk bounds for the purposes of rendering";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LightDebugCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lightbb";
|
||||
public string Description => "Toggles whether to show light bounding boxes";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -40,7 +41,7 @@ namespace Robust.Client.Console.Commands
|
||||
var mgr = IoCManager.Resolve<IScriptClient>();
|
||||
if (!mgr.CanScript)
|
||||
{
|
||||
shell.WriteError("You do not have server side scripting permission.");
|
||||
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ namespace Robust.Client.Console
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the console to a post-initialized state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
event EventHandler<AddStringArgs> AddString;
|
||||
event EventHandler<AddFormattedMessageArgs> AddFormatted;
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace Robust.Client.Console
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>();
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>();
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>();
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME, ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME, ReceiveScriptStartAckResponse);
|
||||
}
|
||||
|
||||
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#if CLIENT_SCRIPTING
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -13,11 +14,11 @@ using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -42,14 +43,14 @@ namespace Robust.Client.Console
|
||||
|
||||
public ScriptConsoleClient()
|
||||
{
|
||||
Title = "Robust C# Interactive (CLIENT)";
|
||||
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
_globals = new ScriptGlobalsImpl(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
|
||||
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
@@ -117,7 +118,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
else
|
||||
{
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
|
||||
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
private readonly BoxContainer _watchesVBox;
|
||||
private readonly VBoxContainer _watchesVBox;
|
||||
private readonly LineEdit _addWatchEdit;
|
||||
private readonly Button _addWatchButton;
|
||||
|
||||
@@ -30,32 +31,29 @@ namespace Robust.Client.Console
|
||||
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
Title = "Watch Window";
|
||||
Title = Loc.GetString("Watch Window");
|
||||
|
||||
var mainVBox = new BoxContainer
|
||||
var mainVBox = new VBoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (500, 300),
|
||||
Children =
|
||||
{
|
||||
(_watchesVBox = new BoxContainer
|
||||
(_watchesVBox = new VBoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
VerticalExpand = true
|
||||
}),
|
||||
new BoxContainer
|
||||
new HBoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = "Add watch (C# interactive)"
|
||||
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
|
||||
}),
|
||||
(_addWatchButton = new Button
|
||||
{
|
||||
Text = "Add"
|
||||
Text = Loc.GetString("Add")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -109,9 +107,8 @@ namespace Robust.Client.Console
|
||||
Button delButton;
|
||||
_runner = runner;
|
||||
|
||||
AddChild(new BoxContainer
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_outputLabel = new Label
|
||||
@@ -121,7 +118,7 @@ namespace Robust.Client.Console
|
||||
}),
|
||||
(delButton = new Button
|
||||
{
|
||||
Text = "Remove"
|
||||
Text = Loc.GetString("Remove")
|
||||
}),
|
||||
}
|
||||
});
|
||||
@@ -171,9 +168,8 @@ namespace Robust.Client.Console
|
||||
public CompilationErrorControl(string message)
|
||||
{
|
||||
Button delButton;
|
||||
AddChild(new BoxContainer
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
@@ -182,7 +178,7 @@ namespace Robust.Client.Console
|
||||
ClipText = true,
|
||||
HorizontalExpand = true
|
||||
},
|
||||
(delButton = new Button {Text = "Remove"})
|
||||
(delButton = new Button {Text = Loc.GetString("Remove")})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Robust.Client
|
||||
#if FULL_RELEASE
|
||||
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
|
||||
#else
|
||||
GameController.Start(args, new GameControllerOptions(), true);
|
||||
GameController.Start(args, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StartLibrary(string[] args, GameControllerOptions options)
|
||||
{
|
||||
GameController.Start(args, options, true, null);
|
||||
GameController.Start(args, true, null, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Robust.Client.Debugging
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager));
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -130,17 +130,17 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
if (body != _hoverBodies[0])
|
||||
{
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
@@ -162,25 +162,23 @@ namespace Robust.Client.Debugging
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var drawnJoints = new HashSet<Joint>();
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Owner.Transform;
|
||||
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
var worldBox = physBody.GetWorldAABB(_mapManager);
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
|
||||
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
foreach (var joint in physBody.Joints)
|
||||
@@ -189,7 +187,6 @@ namespace Robust.Client.Debugging
|
||||
drawnJoints.Add(joint);
|
||||
|
||||
joint.DebugDraw(drawing, in viewport);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
@@ -202,6 +199,17 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysDrawingAdapter : DebugDrawingHandle
|
||||
{
|
||||
private readonly DrawingHandleWorld _handle;
|
||||
@@ -262,14 +270,14 @@ namespace Robust.Client.Debugging
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityLookup _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
|
||||
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
}
|
||||
|
||||
@@ -278,17 +286,18 @@ namespace Robust.Client.Debugging
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
|
||||
foreach (var entity in _entityManager.GetEntities())
|
||||
{
|
||||
var transform = entity.Transform;
|
||||
if (transform.MapID != _eyeManager.CurrentMap ||
|
||||
!_eyeManager.GetWorldViewport().Contains(transform.WorldPosition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var center = transform.WorldPosition;
|
||||
var worldRotation = transform.WorldRotation;
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
var xLine = transform.WorldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = transform.WorldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
|
||||
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
@@ -54,7 +54,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
|
||||
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME, HandleDrawRay);
|
||||
}
|
||||
|
||||
private void HandleDrawRay(MsgRay msg)
|
||||
|
||||
@@ -28,7 +28,6 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
@@ -41,8 +40,6 @@ namespace Robust.Client.Debugging
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
@@ -82,7 +79,8 @@ namespace Robust.Client.Debugging
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
contact.GetWorldManifold(_physicsManager, out var normal, points);
|
||||
Vector2 normal;
|
||||
contact.GetWorldManifold(out normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client
|
||||
{
|
||||
public void Main(IMainArgs args)
|
||||
{
|
||||
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
|
||||
Start(args.Args, contentStart: false, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace Robust.Client
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Start(args, new GameControllerOptions());
|
||||
Start(args);
|
||||
}
|
||||
|
||||
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
|
||||
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
|
||||
{
|
||||
if (_hasStarted)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
|
||||
@@ -51,13 +51,15 @@ namespace Robust.Client
|
||||
var gc = IoCManager.Resolve<GameController>();
|
||||
gc.SetCommandLineArgs(args);
|
||||
gc._loaderArgs = loaderArgs;
|
||||
if(options != null)
|
||||
gc.Options = options;
|
||||
|
||||
// When the game is ran with the startup executable being content,
|
||||
// we have to disable the separate load context.
|
||||
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
|
||||
gc.ContentStart = contentStart;
|
||||
|
||||
gc.Run(mode, options);
|
||||
gc.Run(mode);
|
||||
}
|
||||
|
||||
public void OverrideMainLoop(IGameLoop gameLoop)
|
||||
@@ -65,9 +67,9 @@ namespace Robust.Client
|
||||
_mainLoop = gameLoop;
|
||||
}
|
||||
|
||||
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
if (!StartupSystemSplash(options, logHandlerFactory))
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
{
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
|
||||
@@ -75,12 +75,14 @@ namespace Robust.Client
|
||||
public GameControllerOptions Options { get; private set; } = new();
|
||||
public InitialLaunchState LaunchState { get; private set; } = default!;
|
||||
|
||||
public bool LoadConfigAndUserData { get; set; } = true;
|
||||
|
||||
public void SetCommandLineArgs(CommandLineArgs args)
|
||||
{
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
private bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
_clyde.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
|
||||
@@ -91,9 +93,9 @@ namespace Robust.Client
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
_modLoader.SetUseLoadContext(!ContentStart);
|
||||
_modLoader.SetEnableSandboxing(Options.Sandboxing);
|
||||
_modLoader.SetEnableSandboxing(false && Options.Sandboxing);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
|
||||
{
|
||||
Logger.Fatal("Errors while loading content assemblies.");
|
||||
return false;
|
||||
@@ -194,35 +196,12 @@ namespace Robust.Client
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
|
||||
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
Options = options;
|
||||
ReadInitialLaunchState();
|
||||
|
||||
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
|
||||
|
||||
if (_commandLineArgs != null)
|
||||
{
|
||||
foreach (var (sawmill, level) in _commandLineArgs.LogLevels)
|
||||
{
|
||||
LogLevel? logLevel;
|
||||
if (level == "null")
|
||||
logLevel = null;
|
||||
else
|
||||
{
|
||||
if (!Enum.TryParse<LogLevel>(level, out var result))
|
||||
{
|
||||
System.Console.WriteLine($"LogLevel {level} does not exist!");
|
||||
continue;
|
||||
}
|
||||
logLevel = result;
|
||||
}
|
||||
_logManager.GetSawmill(sawmill).Level = logLevel;
|
||||
}
|
||||
}
|
||||
|
||||
ProgramShared.PrintRuntimeInfo(_logManager.RootSawmill);
|
||||
|
||||
// Figure out user data directory.
|
||||
var userDataDir = GetUserDataDir();
|
||||
|
||||
@@ -233,7 +212,7 @@ namespace Robust.Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
||||
|
||||
if (Options.LoadConfigAndUserData)
|
||||
if (LoadConfigAndUserData)
|
||||
{
|
||||
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
|
||||
if (File.Exists(configFile))
|
||||
@@ -255,24 +234,15 @@ namespace Robust.Client
|
||||
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
|
||||
}
|
||||
|
||||
{
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
_configurationManager.OverrideConVars(new []
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name, options.WindowIconSet.ToString()),
|
||||
(CVars.DisplaySplashLogo.Name, options.SplashLogo.ToString())
|
||||
});
|
||||
}
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
|
||||
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
|
||||
if (_loaderArgs != null)
|
||||
{
|
||||
@@ -479,10 +449,8 @@ namespace Robust.Client
|
||||
Clyde,
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
private void Cleanup()
|
||||
{
|
||||
_modLoader.Shutdown();
|
||||
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
|
||||
@@ -42,41 +42,16 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public string ContentBuildDirectory { get; init; } = "Content.Client";
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all assemblies from.
|
||||
/// </summary>
|
||||
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all prototypes from.
|
||||
/// </summary>
|
||||
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
|
||||
|
||||
/// <summary>
|
||||
/// Directory resource path containing window icons to load.
|
||||
/// </summary>
|
||||
public ResourcePath WindowIconSet { get; init; } = new("/Textures/Logo/icon");
|
||||
|
||||
/// <summary>
|
||||
/// Resource path for splash image to show when the game starts up.
|
||||
/// </summary>
|
||||
public ResourcePath SplashLogo { get; init; } = new("/Textures/Logo/logo.png");
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool ResourceMountDisabled { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to mount content resources when not on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool LoadContentResources { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to load config and user data.
|
||||
/// </summary>
|
||||
public bool LoadConfigAndUserData { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable command line args server auto-connecting.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal class ClientComponentFactory : ComponentFactory
|
||||
public class ClientComponentFactory : ComponentFactory
|
||||
{
|
||||
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
|
||||
: base(typeFactory, reflectionManager, conHost)
|
||||
public ClientComponentFactory()
|
||||
{
|
||||
// Required for the engine to work
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
@@ -25,7 +22,6 @@ namespace Robust.Client.GameObjects
|
||||
RegisterClass<InputComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<OccluderTreeComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AppearanceTestComponent>();
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Prometheus;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -17,7 +21,6 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -64,7 +67,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
{
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
|
||||
}
|
||||
|
||||
public override void TickUpdate(float frameTime, Histogram? histogram)
|
||||
@@ -109,15 +112,13 @@ namespace Robust.Client.GameObjects
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
{
|
||||
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
|
||||
|
||||
if (!netId.HasValue)
|
||||
if (!component.NetID.HasValue)
|
||||
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.ComponentMessage;
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = netId.Value;
|
||||
msg.NetId = component.NetID.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
@@ -147,11 +148,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
|
||||
case EntityMessageType.SystemMessage:
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.GameObjects
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Robust.Client.GameObjects
|
||||
public MapCoordinates? Position => _eye?.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Robust.Client.GameObjects
|
||||
VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -8,7 +10,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Defines data fields used in the <see cref="InputSystem"/>.
|
||||
/// </summary>
|
||||
public class InputComponent : Component
|
||||
class InputComponent : Component
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Input";
|
||||
|
||||
@@ -34,11 +34,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
AnchorStateChanged();
|
||||
SnapGridOnPositionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void AnchorStateChanged()
|
||||
public void SnapGridOnPositionChanged()
|
||||
{
|
||||
SendDirty();
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -18,12 +15,12 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public override string Name => "PointLight";
|
||||
public override uint? NetID => NetIDs.POINT_LIGHT;
|
||||
|
||||
internal bool TreeUpdateQueued { get; set; }
|
||||
|
||||
@@ -47,29 +44,11 @@ namespace Robust.Client.GameObjects
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
set => _enabled = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded
|
||||
{
|
||||
get => _containerOccluded;
|
||||
set
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _containerOccluded;
|
||||
public bool ContainerOccluded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the light mask should automatically rotate with the entity. (like a flashlight)
|
||||
@@ -138,13 +117,30 @@ namespace Robust.Client.GameObjects
|
||||
public bool VisibleNested
|
||||
{
|
||||
get => _visibleNested;
|
||||
set => _visibleNested = value;
|
||||
set
|
||||
{
|
||||
if (_visibleNested == value) return;
|
||||
_visibleNested = value;
|
||||
if (value)
|
||||
{
|
||||
if (Owner.Transform.Parent == null) return;
|
||||
|
||||
_lightOnParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_lightOnParent) return;
|
||||
|
||||
_lightOnParent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("radius")]
|
||||
private float _radius = 5f;
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
private bool _lightOnParent;
|
||||
[DataField("color")]
|
||||
private Color _color = Color.White;
|
||||
[DataField("offset")]
|
||||
@@ -171,8 +167,6 @@ namespace Robust.Client.GameObjects
|
||||
get => _radius;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(value, _radius)) return;
|
||||
|
||||
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
}
|
||||
@@ -186,8 +180,17 @@ namespace Robust.Client.GameObjects
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What MapId we are intersecting for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal RenderingTreeComponent? RenderTree { get; set; }
|
||||
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
|
||||
|
||||
/// <summary>
|
||||
/// What grids we're on for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal List<GridId> IntersectingGrids = new();
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
@@ -197,13 +200,42 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdateMask();
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
/// <inheritdoc />
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if (message is ParentChangedMessage msg)
|
||||
{
|
||||
HandleTransformParentChanged(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTransformParentChanged(ParentChangedMessage obj)
|
||||
{
|
||||
// TODO: this does not work for things nested multiply layers deep.
|
||||
if (!VisibleNested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.NewParent != null && obj.NewParent.IsValid())
|
||||
{
|
||||
_lightOnParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lightOnParent = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
@@ -238,9 +270,4 @@ namespace Robust.Client.GameObjects
|
||||
PointLightComponent = pointLightComponent;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PointLightUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,6 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Calculate sprite bounding box in world-space coordinates.
|
||||
/// </summary>
|
||||
Box2 CalculateBoundingBox(Vector2 worldPos);
|
||||
Box2 CalculateBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class ShowSpriteBBCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showspritebb";
|
||||
public string Description => "Toggle whether sprite bounds are shown";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<SpriteBoundsSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SpriteBoundsSystem : EntitySystem
|
||||
{
|
||||
private SpriteBoundsOverlay? _overlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.AssertNull(_overlay);
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new SpriteBoundsOverlay(EntitySystem.Get<RenderingTreeSystem>(), IoCManager.Resolve<IEyeManager>());
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
overlayManager.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
public class SpriteBoundsOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly IEyeManager _eyeManager = default!;
|
||||
private RenderingTreeSystem _renderTree;
|
||||
|
||||
public SpriteBoundsOverlay(RenderingTreeSystem renderTree, IEyeManager eyeManager)
|
||||
{
|
||||
_renderTree = renderTree;
|
||||
_eyeManager = eyeManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var comp in _renderTree.GetRenderTrees(currentMap, viewport))
|
||||
{
|
||||
var localAABB = viewport.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
|
||||
{
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
var bounds = sprite.CalculateBoundingBox(worldPos);
|
||||
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
|
||||
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,7 @@ namespace Robust.Client.GameObjects
|
||||
public override bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set
|
||||
{
|
||||
if (_visible == value) return;
|
||||
_visible = value;
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
set => _visible = value;
|
||||
}
|
||||
|
||||
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
||||
@@ -131,8 +125,17 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("directional")]
|
||||
private bool _directional = true;
|
||||
|
||||
/// <summary>
|
||||
/// What MapId we are intersecting for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal RenderingTreeComponent? RenderTree { get; set; } = null;
|
||||
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
|
||||
|
||||
/// <summary>
|
||||
/// What grids we're on for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal List<GridId> IntersectingGrids { get; } = new();
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
@@ -149,7 +152,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
if(value == null) return;
|
||||
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
@@ -159,12 +162,11 @@ namespace Robust.Client.GameObjects
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
|
||||
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
|
||||
try
|
||||
{
|
||||
layer.RSI = resource.RSI;
|
||||
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
|
||||
}
|
||||
else
|
||||
catch
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
@@ -263,7 +265,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
QueueUpdateIsInert();
|
||||
UpdateIsInert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,30 +308,17 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[DataField("sprite", readOnly: true)] private string? rsi;
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new();
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
|
||||
|
||||
[DataField("state", readOnly: true)] private string? state;
|
||||
[DataField("texture", readOnly: true)] private string? texture;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded
|
||||
{
|
||||
get => _containerOccluded;
|
||||
set
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _containerOccluded;
|
||||
public bool ContainerOccluded { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _inertUpdateQueued;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ShaderInstance? PostShader { get; set; }
|
||||
|
||||
@@ -359,13 +348,13 @@ namespace Robust.Client.GameObjects
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
|
||||
try
|
||||
{
|
||||
BaseRSI = resource.RSI;
|
||||
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
|
||||
}
|
||||
else
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
|
||||
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,7 +482,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddBlankLayer(int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) { Visible = false };
|
||||
var layer = new Layer(this) {Visible = false};
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
@@ -522,13 +511,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddLayer(Texture? texture, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) { Texture = texture };
|
||||
var layer = new Layer(this) {Texture = texture};
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
public int AddLayer(RSI.StateId stateId, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) { State = stateId };
|
||||
var layer = new Layer(this) {State = stateId};
|
||||
if (BaseRSI != null && BaseRSI.TryGetState(stateId, out var state))
|
||||
{
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
@@ -574,7 +563,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddLayer(RSI.StateId stateId, RSI? rsi, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) { State = stateId, RSI = rsi };
|
||||
var layer = new Layer(this) {State = stateId, RSI = rsi};
|
||||
if (rsi != null && rsi.TryGetState(stateId, out var state))
|
||||
{
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
@@ -630,7 +619,7 @@ namespace Robust.Client.GameObjects
|
||||
index = Layers.Count - 1;
|
||||
}
|
||||
|
||||
QueueUpdateIsInert();
|
||||
UpdateIsInert();
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -657,7 +646,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
QueueUpdateIsInert();
|
||||
UpdateIsInert();
|
||||
}
|
||||
|
||||
public void RemoveLayer(object layerKey)
|
||||
@@ -1212,9 +1201,9 @@ namespace Robust.Client.GameObjects
|
||||
public IEnumerable<ISpriteLayer> AllLayers => Layers;
|
||||
|
||||
// Lobby SpriteView rendering path
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
|
||||
{
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
|
||||
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
|
||||
}
|
||||
|
||||
[DataField("noRot")]
|
||||
@@ -1239,7 +1228,7 @@ namespace Robust.Client.GameObjects
|
||||
public bool EnableDirectionOverride { get => _enableOverrideDirection; set => _enableOverrideDirection = value; }
|
||||
|
||||
// Sprite rendering path
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, in Angle worldRotation, in Vector2 worldPosition)
|
||||
internal void Render(DrawingHandleWorld drawingHandle, in Angle worldRotation, in Vector2 worldPosition)
|
||||
{
|
||||
Direction? overrideDir = null;
|
||||
if (_enableOverrideDirection)
|
||||
@@ -1247,18 +1236,16 @@ namespace Robust.Client.GameObjects
|
||||
overrideDir = _overrideDirection;
|
||||
}
|
||||
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
|
||||
RenderInternal(drawingHandle, worldRotation, worldPosition, overrideDir);
|
||||
}
|
||||
|
||||
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
private void CalcModelMatrix(int numDirs, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
{
|
||||
Angle angle;
|
||||
|
||||
if (_screenLock)
|
||||
{
|
||||
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
|
||||
// sprite will be locked upright to the screen
|
||||
angle = new Angle(-eyeRotation.Theta);
|
||||
angle = Angle.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1269,7 +1256,7 @@ namespace Robust.Client.GameObjects
|
||||
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
|
||||
}
|
||||
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
{
|
||||
var localMatrix = GetLocalMatrix();
|
||||
|
||||
@@ -1282,7 +1269,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var numDirs = GetLayerDirectionCount(layer);
|
||||
|
||||
CalcModelMatrix(numDirs, eyeRotation, worldRotation, worldPosition, out var modelMatrix);
|
||||
CalcModelMatrix(numDirs, worldRotation, worldPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
@@ -1301,8 +1288,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
@@ -1322,7 +1309,7 @@ namespace Robust.Client.GameObjects
|
||||
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
|
||||
{
|
||||
var theta = worldAngle.Theta;
|
||||
var segSize = (MathF.PI * 2) / (numDirections * 2);
|
||||
var segSize = (MathF.PI*2) / (numDirections * 2);
|
||||
var segments = (int)(theta / segSize);
|
||||
var odd = segments % 2;
|
||||
var result = theta - (segments * segSize) - (odd * segSize);
|
||||
@@ -1372,6 +1359,18 @@ namespace Robust.Client.GameObjects
|
||||
return texture;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
var map = Owner.Transform.MapID;
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveSpriteEvent(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
public void FrameUpdate(float delta)
|
||||
{
|
||||
foreach (var t in Layers)
|
||||
@@ -1422,7 +1421,7 @@ namespace Robust.Client.GameObjects
|
||||
if (curState == null)
|
||||
return;
|
||||
|
||||
var thestate = (SpriteComponentState)curState;
|
||||
var thestate = (SpriteComponentState) curState;
|
||||
|
||||
Visible = thestate.Visible;
|
||||
DrawDepth = thestate.DrawDepth;
|
||||
@@ -1510,25 +1509,8 @@ namespace Robust.Client.GameObjects
|
||||
};
|
||||
}
|
||||
|
||||
private void QueueUpdateIsInert()
|
||||
private void UpdateIsInert()
|
||||
{
|
||||
// Look this was an easy way to get bounds checks for layer updates.
|
||||
// If you really want it optimal you'll need to comb through all 2k lines of spritecomponent.
|
||||
if (Owner?.EntityManager?.EventBus != null)
|
||||
UpdateBounds();
|
||||
|
||||
if (_inertUpdateQueued)
|
||||
return;
|
||||
|
||||
_inertUpdateQueued = true;
|
||||
// Yes that null check is valid because of that stupid fucking dummy IEntity.
|
||||
// Who thought that was a good idea.
|
||||
Owner?.EntityManager?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
|
||||
}
|
||||
|
||||
internal void DoUpdateIsInert()
|
||||
{
|
||||
_inertUpdateQueued = false;
|
||||
IsInert = true;
|
||||
|
||||
foreach (var layer in Layers)
|
||||
@@ -1631,11 +1613,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox(Vector2 worldPos)
|
||||
public Box2 CalculateBoundingBox()
|
||||
{
|
||||
// fast check for empty sprites
|
||||
if (Layers.Count == 0)
|
||||
return new Box2(worldPos, worldPos);
|
||||
return new Box2();
|
||||
|
||||
// we need to calculate bounding box taking into account all nested layers
|
||||
// because layers can have offsets, scale or rotation we need to calculate a new BB
|
||||
@@ -1658,15 +1640,11 @@ namespace Robust.Client.GameObjects
|
||||
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
|
||||
|
||||
// move it all to world transform system (with sprite offset)
|
||||
var worldBB = spriteBB.Translated(Offset + worldPos);
|
||||
var worldPosition = Owner.Transform.WorldPosition;
|
||||
var worldBB = spriteBB.Translated(Offset + worldPosition);
|
||||
return worldBB;
|
||||
}
|
||||
|
||||
internal void UpdateBounds()
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to "offset" a cardinal direction.
|
||||
/// </summary>
|
||||
@@ -1722,19 +1700,7 @@ namespace Robust.Client.GameObjects
|
||||
public bool AutoAnimated = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
if (_offset.EqualsApprox(value)) return;
|
||||
|
||||
_offset = value;
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _offset;
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public DirectionOffset DirOffset { get; set; }
|
||||
@@ -1894,14 +1860,14 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
AutoAnimated = value;
|
||||
|
||||
_parent.QueueUpdateIsInert();
|
||||
_parent.UpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetVisible(bool value)
|
||||
{
|
||||
Visible = value;
|
||||
|
||||
_parent.QueueUpdateIsInert();
|
||||
_parent.UpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetRsi(RSI? rsi)
|
||||
@@ -1933,7 +1899,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
_parent.QueueUpdateIsInert();
|
||||
_parent.UpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetState(RSI.StateId stateId)
|
||||
@@ -1965,7 +1931,7 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTime = 0;
|
||||
AnimationTimeLeft = state.GetDelay(0);
|
||||
|
||||
_parent.QueueUpdateIsInert();
|
||||
_parent.UpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetTexture(Texture? texture)
|
||||
@@ -1973,7 +1939,7 @@ namespace Robust.Client.GameObjects
|
||||
State = default;
|
||||
Texture = texture;
|
||||
|
||||
_parent.QueueUpdateIsInert();
|
||||
_parent.UpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
@@ -2006,6 +1972,7 @@ namespace Robust.Client.GameObjects
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
@@ -2024,13 +1991,13 @@ namespace Robust.Client.GameObjects
|
||||
switch (layerProp)
|
||||
{
|
||||
case "texture":
|
||||
LayerSetTexture(index, (string)value);
|
||||
LayerSetTexture(index, (string) value);
|
||||
return;
|
||||
case "state":
|
||||
LayerSetState(index, (string)value);
|
||||
LayerSetState(index, (string) value);
|
||||
return;
|
||||
case "color":
|
||||
LayerSetColor(index, (Color)value);
|
||||
LayerSetColor(index, (Color) value);
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown layer property '{layerProp}'");
|
||||
@@ -2075,7 +2042,7 @@ namespace Robust.Client.GameObjects
|
||||
yield break;
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
|
||||
if (prototype.Components.TryGetValue("Appearance", out _))
|
||||
@@ -2119,7 +2086,7 @@ namespace Robust.Client.GameObjects
|
||||
return GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
dummy.Delete();
|
||||
|
||||
@@ -2156,7 +2123,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
_components[typeof(T)] = comp;
|
||||
comp.Owner = this;
|
||||
|
||||
@@ -2190,7 +2157,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public T GetComponent<T>()
|
||||
{
|
||||
return (T)_components[typeof(T)];
|
||||
return (T) _components[typeof(T)];
|
||||
}
|
||||
|
||||
public IComponent GetComponent(Type type)
|
||||
@@ -2202,7 +2169,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
component = null;
|
||||
if (!_components.TryGetValue(typeof(T), out var value)) return false;
|
||||
component = (T)value;
|
||||
component = (T) value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2258,14 +2225,4 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class SpriteUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal struct SpriteUpdateInertEvent
|
||||
{
|
||||
public SpriteComponent Sprite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class RenderingTreeComponent : Component
|
||||
{
|
||||
public override string Name => "RenderingTree";
|
||||
|
||||
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
|
||||
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
|
||||
|
||||
private static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var bounds = value.CalculateBoundingBox(worldPos);
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var offset = -tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return bounds.Translated(offset);
|
||||
}
|
||||
|
||||
private static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return Box2.CenteredAround(pos, (boxSize, boxSize));
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var bounds = value.CalculateBoundingBox(worldPos.Value);
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var offset = -tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return bounds.Translated(offset);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return Box2.CenteredAround(pos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IClydeAudio _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
@@ -37,6 +38,7 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
@@ -252,7 +254,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(entity.Transform.WorldPosition))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
Logger.Warning("Can't play positional audio, can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -299,7 +301,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
Logger.Warning("Can't play positional audio, can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ClientContainerSystem : ContainerSystem
|
||||
{
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
|
||||
{
|
||||
_updateQueue.Add(ev.Entity);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
|
||||
|
||||
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
|
||||
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -62,9 +62,9 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
|
||||
private static void HandleSnapGridMove(EntityUid uid, ClientOccluderComponent component, SnapGridPositionChangedEvent args)
|
||||
{
|
||||
component.AnchorStateChanged();
|
||||
component.SnapGridOnPositionChanged();
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(OccluderDirtyEvent ev)
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
|
||||
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
|
||||
SubscribeLocalEvent<EntityInitializedMessage>(HandleEntityInitialized);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
|
||||
{
|
||||
_updateQueue.Add(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleEntityInitialized(EntityInitializedMessage ev)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(ev.Entity.Uid, out var container))
|
||||
return;
|
||||
|
||||
RemoveExpectedEntity(ev.Entity.Uid);
|
||||
|
||||
if (container.Deleted)
|
||||
return;
|
||||
|
||||
container.Insert(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ContainerManagerComponentState cast)
|
||||
return;
|
||||
|
||||
// Delete now-gone containers.
|
||||
List<string>? toDelete = null;
|
||||
foreach (var (id, container) in component.Containers)
|
||||
{
|
||||
if (!cast.ContainerSet.Any(data => data.Id == id))
|
||||
{
|
||||
container.Shutdown();
|
||||
toDelete ??= new List<string>();
|
||||
toDelete.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete != null)
|
||||
{
|
||||
foreach (var dead in toDelete)
|
||||
{
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new containers and update existing contents.
|
||||
|
||||
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
|
||||
{
|
||||
if (!component.Containers.TryGetValue(id, out var container))
|
||||
{
|
||||
container = ContainerFactory(component, containerType, id);
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
// sync show flag
|
||||
container.ShowContents = showEnts;
|
||||
container.OccludesLight = occludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
List<IEntity>? toRemove = null;
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entity.Uid))
|
||||
{
|
||||
toRemove ??= new List<IEntity>();
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var goner in toRemove)
|
||||
container.Remove(goner);
|
||||
}
|
||||
|
||||
// Remove entities that were expected, but have been removed from the container.
|
||||
List<EntityUid>? removedExpected = null;
|
||||
foreach (var entityUid in container.ExpectedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entityUid))
|
||||
{
|
||||
removedExpected ??= new List<EntityUid>();
|
||||
removedExpected.Add(entityUid);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedExpected != null)
|
||||
{
|
||||
foreach (var entityUid in removedExpected)
|
||||
RemoveExpectedEntity(entityUid);
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
foreach (var entityUid in entityUids)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(entityUid, out var entity))
|
||||
{
|
||||
AddExpectedEntity(entityUid, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!container.ContainedEntities.Contains(entity))
|
||||
container.Insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
|
||||
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
|
||||
|
||||
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
|
||||
newContainer.ID = id;
|
||||
newContainer.Manager = component;
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(EntityUid uid, IContainer container)
|
||||
{
|
||||
if (ExpectedEntities.ContainsKey(uid))
|
||||
return;
|
||||
|
||||
ExpectedEntities.Add(uid, container);
|
||||
container.ExpectedEntities.Add(uid);
|
||||
}
|
||||
|
||||
public void RemoveExpectedEntity(EntityUid uid)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(uid, out var container))
|
||||
return;
|
||||
|
||||
ExpectedEntities.Remove(uid);
|
||||
container.ExpectedEntities.Remove(uid);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
#if DEBUG
|
||||
using System;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class DebugGridTileLookupSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
_label.Visible = true;
|
||||
LastTile = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
_label.Text = null;
|
||||
_label.Visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
private (GridId Grid, Vector2i Indices) LastTile;
|
||||
|
||||
// Label and shit that follows cursor
|
||||
private Label _label = new()
|
||||
{
|
||||
Visible = false,
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<SendGridTileLookupMessage>(HandleSentEntities);
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
UnsubscribeNetworkEvent<SendGridTileLookupMessage>();
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label);
|
||||
}
|
||||
|
||||
private void RequestEntities(GridId gridId, Vector2i indices)
|
||||
{
|
||||
if (gridId == GridId.Invalid) return;
|
||||
RaiseNetworkEvent(new RequestGridTileLookupMessage(gridId, indices));
|
||||
}
|
||||
|
||||
private void HandleSentEntities(SendGridTileLookupMessage message)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine($"GridId: {LastTile.Grid}, Tile: {LastTile.Indices}");
|
||||
|
||||
for (var i = 0; i < message.Entities.Count; i++)
|
||||
{
|
||||
var uid = message.Entities[i];
|
||||
|
||||
if (!EntityManager.TryGetEntity(uid, out var entity)) continue;
|
||||
|
||||
text.AppendLine(entity.ToString());
|
||||
}
|
||||
|
||||
_label.Text = text.ToString();
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled) return;
|
||||
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
var worldPos = _eyeManager.ScreenToMap(mousePos);
|
||||
|
||||
GridId gridId;
|
||||
Vector2i tile;
|
||||
|
||||
if (_mapManager.TryFindGridAt(worldPos, out var grid))
|
||||
{
|
||||
gridId = grid.Index;
|
||||
tile = grid.WorldToTile(worldPos.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridId = GridId.Invalid;
|
||||
tile = new Vector2i((int) MathF.Floor(worldPos.Position.X), (int) MathF.Floor(worldPos.Position.Y));
|
||||
}
|
||||
|
||||
LayoutContainer.SetPosition(_label, mousePos.Position);
|
||||
|
||||
if ((gridId, tile).Equals(LastTile)) return;
|
||||
|
||||
_label.Text = null;
|
||||
LastTile = (gridId, tile);
|
||||
RequestEntities(gridId, tile);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RequestTileEntities : IConsoleCommand
|
||||
{
|
||||
public string Command => "tilelookup";
|
||||
public string Description => "Used for debugging GridTileLookupSystem";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugGridTileLookupSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,84 +0,0 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class DebugLightTreeSystem : EntitySystem
|
||||
{
|
||||
private DebugLightOverlay? _lightOverlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
_lightOverlay = new DebugLightOverlay(
|
||||
IoCManager.Resolve<IEntityLookup>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
Get<RenderingTreeSystem>());
|
||||
|
||||
overlayManager.AddOverlay(_lightOverlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayManager.RemoveOverlay(_lightOverlay!);
|
||||
_lightOverlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
private sealed class DebugLightOverlay : Overlay
|
||||
{
|
||||
private IEntityLookup _lookup;
|
||||
private IEyeManager _eyeManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
private RenderingTreeSystem _tree;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
_tree = tree;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _eyeManager.CurrentMap;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var tree in _tree.GetLightTrees(map, viewport))
|
||||
{
|
||||
foreach (var light in tree)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
|
||||
if (!aabb.Intersects(viewport)) continue;
|
||||
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,90 +0,0 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class GridChunkBoundsDebugSystem : EntitySystem
|
||||
{
|
||||
private GridChunkBoundsOverlay? _overlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.Assert(_overlay == null);
|
||||
_overlay = new GridChunkBoundsOverlay(
|
||||
IoCManager.Resolve<IEntityManager>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>());
|
||||
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(_overlay!);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
internal sealed class GridChunkBoundsOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
|
||||
{
|
||||
var mapGrid = (IMapGridInternal) grid;
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
|
||||
var worldPos = gridEnt.Transform.WorldPosition;
|
||||
var worldRot = gridEnt.Transform.WorldRotation;
|
||||
|
||||
foreach (var (_, chunk) in mapGrid.GetMapChunks())
|
||||
{
|
||||
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
|
||||
var aabb = chunkBounds.CalcBoundingBox();
|
||||
|
||||
// Calc world bounds for chunk.
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
|
||||
args.WorldHandle.DrawRect(aabb, Color.Red, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -173,24 +173,4 @@ namespace Robust.Client.GameObjects
|
||||
AttachedEntity = attachedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerAttachedEvent : EntityEventArgs
|
||||
{
|
||||
public PlayerAttachedEvent(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
|
||||
public class PlayerDetachedEvent : EntityEventArgs
|
||||
{
|
||||
public PlayerDetachedEvent(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,8 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -21,48 +18,23 @@ namespace Robust.Client.GameObjects
|
||||
[UsedImplicitly]
|
||||
public sealed class RenderingTreeSystem : EntitySystem
|
||||
{
|
||||
internal const string LoggerSawmill = "rendertree";
|
||||
|
||||
// Nullspace is not indexed. Keep that in mind.
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
|
||||
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
|
||||
|
||||
private readonly List<SpriteComponent> _spriteQueue = new();
|
||||
private readonly List<PointLightComponent> _lightQueue = new();
|
||||
|
||||
private HashSet<EntityUid> _checkedChildren = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CVars.MaxLightRadius"/>
|
||||
/// </summary>
|
||||
public float MaxLightRadius { get; private set; }
|
||||
|
||||
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
|
||||
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
|
||||
return _gridTrees[map][grid].SpriteTree;
|
||||
}
|
||||
|
||||
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
|
||||
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
|
||||
{
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.SpriteTree;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.LightTree;
|
||||
}
|
||||
return _gridTrees[map][grid].LightTree;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -74,65 +46,20 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
_mapManager.MapCreated += MapManagerOnMapCreated;
|
||||
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated += MapManagerOnGridCreated;
|
||||
|
||||
// Due to how recursion works, this must be done.
|
||||
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
|
||||
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
|
||||
|
||||
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, MoveEvent>(SpriteMoved);
|
||||
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
|
||||
SubscribeLocalEvent<SpriteComponent, RenderTreeRemoveSpriteEvent>(RemoveSprite);
|
||||
|
||||
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, MoveEvent>(LightMoved);
|
||||
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
|
||||
|
||||
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
|
||||
}
|
||||
|
||||
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void HandleSpriteUpdate(EntityUid uid, SpriteComponent component, SpriteUpdateEvent args)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void AnythingMoved(ref MoveEvent args)
|
||||
{
|
||||
AnythingMovedSubHandler(args.Sender.Transform);
|
||||
}
|
||||
|
||||
private void AnythingMovedSubHandler(ITransformComponent sender)
|
||||
{
|
||||
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
|
||||
if (!_checkedChildren.Add(sender.Owner.Uid) ||
|
||||
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
|
||||
|
||||
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
|
||||
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
|
||||
// (Struct-based events ok though)
|
||||
// Ironically this was lagging the GC lolz
|
||||
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
QueueSpriteUpdate(sprite);
|
||||
|
||||
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
|
||||
QueueLightUpdate(light);
|
||||
|
||||
foreach (ITransformComponent child in sender.Children)
|
||||
{
|
||||
AnythingMovedSubHandler(child);
|
||||
}
|
||||
}
|
||||
|
||||
// For the RemoveX methods
|
||||
@@ -146,22 +73,33 @@ namespace Robust.Client.GameObjects
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, ref EntParentChangedMessage args)
|
||||
private void SpriteMoved(EntityUid uid, SpriteComponent component, MoveEvent args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveSprite(EntityUid uid, SpriteComponent component, ComponentRemove args)
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveSprite(EntityUid uid, SpriteComponent component, RenderTreeRemoveSpriteEvent args)
|
||||
{
|
||||
ClearSprite(component);
|
||||
}
|
||||
|
||||
private void ClearSprite(SpriteComponent component)
|
||||
{
|
||||
if (component.RenderTree == null) return;
|
||||
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
|
||||
{
|
||||
foreach (var gridId in component.IntersectingGrids)
|
||||
{
|
||||
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
|
||||
tree.SpriteTree.Remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
component.RenderTree.SpriteTree.Remove(component);
|
||||
component.RenderTree = null;
|
||||
component.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(SpriteComponent component)
|
||||
@@ -170,6 +108,22 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_spriteQueue.Add(component);
|
||||
|
||||
foreach (var child in component.Owner.Transform.Children)
|
||||
{
|
||||
QueueSpriteUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out SpriteComponent? spriteComponent)) return;
|
||||
QueueSpriteUpdate(spriteComponent);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
QueueSpriteUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -179,7 +133,12 @@ namespace Robust.Client.GameObjects
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, ref EntParentChangedMessage args)
|
||||
private void LightMoved(EntityUid uid, PointLightComponent component, MoveEvent args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
@@ -196,10 +155,16 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void ClearLight(PointLightComponent component)
|
||||
{
|
||||
if (component.RenderTree == null) return;
|
||||
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
|
||||
{
|
||||
foreach (var gridId in component.IntersectingGrids)
|
||||
{
|
||||
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
|
||||
tree.LightTree.Remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
component.RenderTree.LightTree.Remove(component);
|
||||
component.RenderTree = null;
|
||||
component.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(PointLightComponent component)
|
||||
@@ -208,6 +173,22 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_lightQueue.Add(component);
|
||||
|
||||
foreach (var child in component.Owner.Transform.Children)
|
||||
{
|
||||
QueueLightUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out PointLightComponent? lightComponent)) return;
|
||||
QueueLightUpdate(lightComponent);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
QueueLightUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -215,23 +196,31 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.MapCreated -= MapManagerOnMapCreated;
|
||||
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
|
||||
}
|
||||
|
||||
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
|
||||
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
|
||||
{
|
||||
foreach (var sprite in component.SpriteTree)
|
||||
foreach (var (_, gridTree) in _gridTrees[e.Map])
|
||||
{
|
||||
sprite.RenderTree = null;
|
||||
foreach (var comp in gridTree.LightTree)
|
||||
{
|
||||
comp.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
foreach (var comp in gridTree.SpriteTree)
|
||||
{
|
||||
comp.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
// Just in case?
|
||||
gridTree.LightTree.Clear();
|
||||
gridTree.SpriteTree.Clear();
|
||||
}
|
||||
|
||||
foreach (var light in component.LightTree)
|
||||
{
|
||||
light.RenderTree = null;
|
||||
}
|
||||
|
||||
component.SpriteTree.Clear();
|
||||
component.LightTree.Clear();
|
||||
_gridTrees.Remove(e.Map);
|
||||
}
|
||||
|
||||
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
|
||||
@@ -241,125 +230,166 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
|
||||
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
|
||||
{
|
||||
{GridId.Invalid, new MapTrees()}
|
||||
});
|
||||
}
|
||||
|
||||
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
|
||||
_gridTrees[mapId].Add(gridId, new MapTrees());
|
||||
}
|
||||
|
||||
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
|
||||
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (entity.Transform.MapID == MapId.Nullspace ||
|
||||
entity.HasComponent<RenderingTreeComponent>()) return null;
|
||||
var gridTree = _gridTrees[mapId][gridId];
|
||||
|
||||
var parent = entity.Transform.Parent?.Owner;
|
||||
|
||||
while (true)
|
||||
foreach (var sprite in gridTree.SpriteTree)
|
||||
{
|
||||
if (parent == null) break;
|
||||
|
||||
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
sprite.IntersectingGrids.Remove(gridId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
foreach (var light in gridTree.LightTree)
|
||||
{
|
||||
light.IntersectingGrids.Remove(gridId);
|
||||
}
|
||||
|
||||
private bool IsVisible(SpriteComponent component)
|
||||
{
|
||||
return component.Visible && !component.ContainerOccluded;
|
||||
// Clear in case
|
||||
gridTree.LightTree.Clear();
|
||||
gridTree.SpriteTree.Clear();
|
||||
_gridTrees[mapId].Remove(gridId);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
_checkedChildren.Clear();
|
||||
|
||||
foreach (var sprite in _spriteQueue)
|
||||
{
|
||||
sprite.TreeUpdateQueued = false;
|
||||
if (!IsVisible(sprite))
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldMapTree = sprite.RenderTree;
|
||||
var newMapTree = GetRenderTree(sprite.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
|
||||
var mapId = sprite.Owner.Transform.MapID;
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
if (sprite.IntersectingMapId != mapId)
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
newMapTree?.SpriteTree.Add(sprite, aabb);
|
||||
}
|
||||
else
|
||||
{
|
||||
newMapTree?.SpriteTree.Update(sprite, aabb);
|
||||
}
|
||||
|
||||
sprite.RenderTree = newMapTree;
|
||||
sprite.IntersectingMapId = mapId;
|
||||
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
|
||||
var mapTree = _gridTrees[mapId];
|
||||
var aabb = MapTrees.SpriteAabbFunc(sprite);
|
||||
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
|
||||
|
||||
// Remove from old
|
||||
foreach (var gridId in sprite.IntersectingGrids)
|
||||
{
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].SpriteTree.Remove(sprite);
|
||||
}
|
||||
|
||||
// Rebuild in the update below
|
||||
sprite.IntersectingGrids.Clear();
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
|
||||
|
||||
sprite.IntersectingGrids.Add(gridId);
|
||||
}
|
||||
|
||||
sprite.TreeUpdateQueued = false;
|
||||
}
|
||||
|
||||
foreach (var light in _lightQueue)
|
||||
{
|
||||
light.TreeUpdateQueued = false;
|
||||
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldMapTree = light.RenderTree;
|
||||
var newMapTree = GetRenderTree(light.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = light.Owner.Transform.WorldPosition;
|
||||
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
|
||||
// this is fine for now IMO as it's 1 float check for every light that moves
|
||||
if (light.Radius > MaxLightRadius)
|
||||
{
|
||||
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
|
||||
}
|
||||
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
|
||||
var mapId = light.Owner.Transform.MapID;
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
if (light.IntersectingMapId != mapId)
|
||||
{
|
||||
ClearLight(light);
|
||||
newMapTree?.LightTree.Add(light, aabb);
|
||||
}
|
||||
else
|
||||
{
|
||||
newMapTree?.LightTree.Update(light, aabb);
|
||||
}
|
||||
|
||||
light.RenderTree = newMapTree;
|
||||
light.IntersectingMapId = mapId;
|
||||
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
|
||||
var mapTree = _gridTrees[mapId];
|
||||
var aabb = MapTrees.LightAabbFunc(light);
|
||||
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
|
||||
|
||||
// Remove from old
|
||||
foreach (var gridId in intersectingGrids)
|
||||
{
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].LightTree.Remove(light);
|
||||
}
|
||||
|
||||
// Rebuild in the update below
|
||||
light.IntersectingGrids.Clear();
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].LightTree.AddOrUpdate(light, translated);
|
||||
light.IntersectingGrids.Add(gridId);
|
||||
}
|
||||
|
||||
light.TreeUpdateQueued = false;
|
||||
}
|
||||
|
||||
_spriteQueue.Clear();
|
||||
_lightQueue.Clear();
|
||||
}
|
||||
|
||||
private sealed class MapTrees
|
||||
{
|
||||
public readonly DynamicTree<SpriteComponent> SpriteTree;
|
||||
public readonly DynamicTree<PointLightComponent> LightTree;
|
||||
|
||||
public MapTrees()
|
||||
{
|
||||
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
|
||||
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
return new Box2(worldPos, worldPos);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class RenderTreeRemoveSpriteEvent : EntityEventArgs
|
||||
{
|
||||
public RenderTreeRemoveSpriteEvent(SpriteComponent sprite, MapId map)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Map = map;
|
||||
}
|
||||
|
||||
public SpriteComponent Sprite { get; }
|
||||
public MapId Map { get; }
|
||||
}
|
||||
|
||||
internal class RenderTreeRemoveLightEvent : EntityEventArgs
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -15,30 +15,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
private RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
}
|
||||
|
||||
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
|
||||
{
|
||||
_inertUpdateQueue.Enqueue(ev.Sprite);
|
||||
_treeSystem = Get<RenderingTreeSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
while (_inertUpdateQueue.TryDequeue(out var sprite))
|
||||
{
|
||||
sprite.DoUpdateIsInert();
|
||||
}
|
||||
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5);
|
||||
@@ -49,11 +37,13 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
|
||||
{
|
||||
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
|
||||
|
||||
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
{
|
||||
if (value.IsInert)
|
||||
{
|
||||
@@ -62,7 +52,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
value.FrameUpdate(state);
|
||||
return true;
|
||||
}, bounds, true);
|
||||
}, gridBounds, approx: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
|
||||
17
Robust.Client/GameObjects/PlayerMessages.cs
Normal file
17
Robust.Client/GameObjects/PlayerMessages.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerAttachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerDetachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,29 @@
|
||||
// ReSharper disable once RedundantUsingDirective
|
||||
// Used in EXCEPTION_TOLERANCE preprocessor
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[UsedImplicitly]
|
||||
public class ClientGameStateManager : IClientGameStateManager
|
||||
{
|
||||
private GameStateProcessor _processor = default!;
|
||||
@@ -38,8 +35,6 @@ namespace Robust.Client.GameStates
|
||||
_pendingSystemMessages
|
||||
= new();
|
||||
|
||||
private uint _metaCompNetId;
|
||||
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
@@ -85,8 +80,8 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
_processor = new GameStateProcessor(_timing);
|
||||
|
||||
_network.RegisterNetMessage<MsgState>(HandleStateMessage);
|
||||
_network.RegisterNetMessage<MsgStateAck>();
|
||||
_network.RegisterNetMessage<MsgState>(MsgState.NAME, HandleStateMessage);
|
||||
_network.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME);
|
||||
_client.RunLevelChanged += RunLevelChanged;
|
||||
|
||||
_config.OnValueChanged(CVars.NetInterp, b => _processor.Interpolation = b, true);
|
||||
@@ -103,12 +98,6 @@ namespace Robust.Client.GameStates
|
||||
Predicting = _config.GetCVar(CVars.NetPredict);
|
||||
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
|
||||
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
|
||||
|
||||
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
|
||||
if (!metaId.HasValue)
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
|
||||
_metaCompNetId = metaId.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -220,11 +209,6 @@ namespace Robust.Client.GameStates
|
||||
ResetPredictedEntities(_timing.CurTick);
|
||||
}
|
||||
|
||||
if (!curState.Extrapolated)
|
||||
{
|
||||
_processor.UpdateFullRep(curState);
|
||||
}
|
||||
|
||||
// Store last tick we got from the GameStateProcessor.
|
||||
_lastProcessedTick = _timing.CurTick;
|
||||
|
||||
@@ -334,8 +318,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
{
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
foreach (var entity in _entities.GetEntities())
|
||||
{
|
||||
// TODO: 99% there's an off-by-one here.
|
||||
@@ -353,18 +335,17 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// TODO: handle component deletions/creations.
|
||||
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
|
||||
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
|
||||
{
|
||||
DebugTools.AssertNotNull(netId);
|
||||
DebugTools.AssertNotNull(comp.NetID);
|
||||
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(netId, out var compState))
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID!.Value, out var compState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.Name}");
|
||||
// TODO: Handle interpolation.
|
||||
bus.RaiseComponentEvent(comp, new ComponentHandleState(compState, null));
|
||||
comp.HandleComponentState(compState, null);
|
||||
}
|
||||
}
|
||||
@@ -378,22 +359,24 @@ namespace Robust.Client.GameStates
|
||||
// so that we can later roll back to it (if necessary).
|
||||
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
|
||||
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
var player = _players.LocalPlayer.Session;
|
||||
|
||||
foreach (var createdEntity in createdEntities)
|
||||
{
|
||||
var compData = new Dictionary<uint, ComponentState>();
|
||||
outputData.Add(createdEntity, compData);
|
||||
|
||||
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
|
||||
foreach (var component in _componentManager.GetNetComponents(createdEntity))
|
||||
{
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
|
||||
var player = _players.LocalPlayer.Session;
|
||||
var state = component.GetComponentState(player);
|
||||
|
||||
if(state.GetType() == typeof(ComponentState))
|
||||
if (state.GetType() == typeof(ComponentState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
compData.Add(netId, state);
|
||||
compData.Add(state.NetID, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +393,7 @@ namespace Robust.Client.GameStates
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates);
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates);
|
||||
@@ -435,17 +418,16 @@ namespace Robust.Client.GameStates
|
||||
//Known entities
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
|
||||
toApply.Add(entity, (es, null));
|
||||
}
|
||||
else //Unknown entities
|
||||
{
|
||||
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
var metaState = (MetaDataComponentState?) es.ComponentStates
|
||||
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
|
||||
if (metaState == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
|
||||
}
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
|
||||
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
@@ -472,20 +454,17 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
// Make sure this is done after all entities have been instantiated.
|
||||
foreach (var kvStates in toApply)
|
||||
{
|
||||
var ent = kvStates.Key;
|
||||
var entity = (Entity) ent;
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, bus, kvStates.Value.Item1,
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
|
||||
kvStates.Value.Item2);
|
||||
}
|
||||
|
||||
foreach (var id in deletions)
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
|
||||
_entities.DeleteEntity(id);
|
||||
}
|
||||
|
||||
@@ -547,10 +526,10 @@ namespace Robust.Client.GameStates
|
||||
return created;
|
||||
}
|
||||
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
|
||||
EntityState? nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var entityUid = entity.Uid;
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
@@ -566,50 +545,45 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
else
|
||||
{
|
||||
//Right now we just assume every state from an unseen entity is added
|
||||
|
||||
if (compMan.HasComponent(entityUid, compChange.NetID))
|
||||
continue;
|
||||
|
||||
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
|
||||
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
|
||||
newComp.Owner = entity;
|
||||
compMan.AddComponent(entity, newComp, true);
|
||||
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
if (curState?.ComponentStates != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
foreach (var compState in curState.ComponentStates)
|
||||
{
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
compStateWork[compState.NetID] = (compState, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextState?.ComponentChanges != null)
|
||||
if (nextState?.ComponentStates != null)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentChanges)
|
||||
foreach (var compState in nextState.ComponentStates)
|
||||
{
|
||||
if (compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
{
|
||||
compStateWork[compState.NetID] = (state.curState, compState.State);
|
||||
compStateWork[compState.NetID] = (state.curState, compState);
|
||||
}
|
||||
else
|
||||
{
|
||||
compStateWork[compState.NetID] = (null, compState.State);
|
||||
compStateWork[compState.NetID] = (null, compState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (netId, (cur, next)) in compStateWork)
|
||||
{
|
||||
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
|
||||
if (compMan.TryGetComponent(entityUid, netId, out var component))
|
||||
{
|
||||
try
|
||||
{
|
||||
bus.RaiseComponentEvent(component, new ComponentHandleState(cur, next));
|
||||
component.HandleComponentState(cur, next);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -149,6 +149,11 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
Logger.DebugS("net.state", $"Applying State: ext={curState!.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
|
||||
}
|
||||
|
||||
if (!curState!.Extrapolated)
|
||||
{
|
||||
UpdateFullRep(curState);
|
||||
}
|
||||
}
|
||||
|
||||
var cState = curState!;
|
||||
@@ -157,10 +162,8 @@ namespace Robust.Client.GameStates
|
||||
return applyNextState;
|
||||
}
|
||||
|
||||
public void UpdateFullRep(GameState state)
|
||||
private void UpdateFullRep(GameState state)
|
||||
{
|
||||
// Logger.Debug($"UPDATE FULL REP: {string.Join(", ", state.EntityStates?.Select(e => e.Uid) ?? Enumerable.Empty<EntityUid>())}");
|
||||
|
||||
if (state.FromSequence == GameTick.Zero)
|
||||
{
|
||||
// Full state.
|
||||
@@ -195,10 +198,14 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
compData.Remove(change.NetID);
|
||||
}
|
||||
else if (change.State is not null)
|
||||
{
|
||||
compData[change.NetID] = change.State;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entityState.ComponentStates != null)
|
||||
{
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
{
|
||||
compData[compState.NetID] = compState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Robust.Client.GameStates
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
screenHandle.DrawString(_font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,17 @@ namespace Robust.Client.GameStates
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private struct NetEntity
|
||||
{
|
||||
public GameTick LastUpdate;
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
|
||||
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
|
||||
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
|
||||
@@ -91,14 +90,17 @@ namespace Robust.Client.GameStates
|
||||
sb.Append($"\n Changes:");
|
||||
foreach (var compChange in entState.ComponentChanges)
|
||||
{
|
||||
var registration = _componentFactory.GetRegistration(compChange.NetID);
|
||||
var create = compChange.Created ? 'C' : '\0';
|
||||
var mod = !(compChange.Created || compChange.Created) ? 'M' : '\0';
|
||||
var del = compChange.Deleted ? 'D' : '\0';
|
||||
sb.Append($"\n [{create}{mod}{del}]{compChange.NetID}:{registration.Name}");
|
||||
var del = compChange.Deleted ? 'D' : 'C';
|
||||
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
|
||||
}
|
||||
}
|
||||
|
||||
if(compChange.State is not null)
|
||||
sb.Append($"\n STATE:{compChange.State.GetType().Name}");
|
||||
if (entState.ComponentStates is not null)
|
||||
{
|
||||
sb.Append($"\n States:");
|
||||
foreach (var compState in entState.ComponentStates)
|
||||
{
|
||||
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +184,7 @@ namespace Robust.Client.GameStates
|
||||
// Draw size if above average
|
||||
if (drawSizeThreshold * 1.5 < state.Payload)
|
||||
{
|
||||
handle.DrawString(_font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
DrawString(handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
}
|
||||
|
||||
// second tick marks
|
||||
@@ -222,14 +224,14 @@ namespace Robust.Client.GameStates
|
||||
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// payload text
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
DrawString(handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
DrawString(handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
|
||||
// interp text info
|
||||
if(lastLagY != -1)
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
DrawString(handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
|
||||
handle.DrawString(_font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
DrawString(handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
@@ -239,6 +241,17 @@ namespace Robust.Client.GameStates
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class NetShowGraphCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_graph";
|
||||
|
||||
@@ -111,11 +111,10 @@ namespace Robust.Client.Graphics
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
var (pos, window) = point;
|
||||
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
if (window != MainViewport.Window?.Id)
|
||||
return default;
|
||||
|
||||
return viewport.ScreenToMap(pos);
|
||||
return MainViewport.ScreenToMap(pos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics
|
||||
ScreenCoordinates CoordinatesToScreen(EntityCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the viewport under the screen coordinates.
|
||||
/// Unprojects a point from UI screen space to world space using the current camera.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
|
||||
@@ -64,7 +64,7 @@ namespace Robust.Client.Graphics
|
||||
MapCoordinates ScreenToMap(ScreenCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the main viewport.
|
||||
/// Unprojects a point from UI screen space to world space using the current camera.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
|
||||
|
||||
@@ -44,12 +44,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal bool IsEfxSupported;
|
||||
|
||||
private ISawmill _openALSawmill = default!;
|
||||
|
||||
private void _initializeAudio()
|
||||
{
|
||||
_openALSawmill = Logger.GetSawmill("clyde.oal");
|
||||
|
||||
_audioOpenDevice();
|
||||
|
||||
// Create OpenAL context.
|
||||
@@ -78,9 +74,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_alContextExtensions.Add(extension);
|
||||
}
|
||||
|
||||
_openALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
_openALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
_openALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
}
|
||||
|
||||
private void _audioOpenDevice()
|
||||
@@ -93,7 +89,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_openALDevice = ALC.OpenDevice(preferredDevice);
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
_openALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
Logger.WarningS("clyde.oal", "Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
preferredDevice, ALC.GetError(ALDevice.Null));
|
||||
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
@@ -157,7 +153,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Clear out finalized audio sources.
|
||||
while (_sourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
_openALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
Logger.DebugS("clyde.oal", "Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
@@ -167,7 +163,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Clear out finalized buffered audio sources.
|
||||
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
_openALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
Logger.DebugS("clyde.oal", "Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
@@ -187,6 +183,32 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (handles.filterHandle != 0) EFX.DeleteFilter(handles.filterHandle);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate)
|
||||
{
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(
|
||||
buffer,
|
||||
channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16,
|
||||
(IntPtr) ptr,
|
||||
samples.Length * 2,
|
||||
sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
// ReSharper disable once PossibleLossOfFraction
|
||||
var length = TimeSpan.FromSeconds(samples.Length / channels / (double) sampleRate);
|
||||
return new AudioStream(handle, length, channels);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
|
||||
@@ -215,24 +237,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
private void _checkAlcError(ALDevice device,
|
||||
private static void _checkAlcError(ALDevice device,
|
||||
[CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = ALC.GetError(device);
|
||||
if (error != AlcError.NoError)
|
||||
{
|
||||
_openALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
Logger.ErrorS("clyde.oal", "[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
private void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
private static void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
_openALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
Logger.ErrorS("clyde.oal", "[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,35 +356,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var fmt = channels switch
|
||||
{
|
||||
1 => ALFormat.Mono16,
|
||||
2 => ALFormat.Stereo16,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(channels), "Only stereo and mono is currently supported")
|
||||
};
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
_checkAlError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
private sealed class LoadedAudioSample
|
||||
{
|
||||
public readonly int BufferHandle;
|
||||
@@ -414,14 +407,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourcePlay(SourceHandle);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourceStop(SourceHandle);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
@@ -440,14 +433,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
return ret;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.Looping, value);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +448,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
@@ -469,10 +462,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
public void SetVolumeDirect(float decibels)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
@@ -481,9 +474,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = scale;
|
||||
|
||||
_gain = decibels;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
@@ -500,7 +494,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
||||
}
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
@@ -520,7 +514,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
@@ -546,7 +540,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
#endif
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -573,14 +567,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
~AudioSource()
|
||||
@@ -606,7 +600,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
|
||||
AL.DeleteSource(SourceHandle);
|
||||
_master._audioSources.Remove(SourceHandle);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = -1;
|
||||
@@ -656,7 +650,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
@@ -664,7 +658,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
@@ -690,7 +684,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mono = false;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetLooping()
|
||||
@@ -709,21 +703,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = scale;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
@@ -741,7 +721,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
|
||||
}
|
||||
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
@@ -761,7 +741,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
@@ -778,7 +758,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mono = true;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -805,7 +785,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float masterVolumeDecay)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = masterVolumeDecay;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
@@ -813,7 +808,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
~BufferedAudioSource()
|
||||
@@ -844,7 +839,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.DeleteSource(SourceHandle.Value);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
_master._bufferedAudioSources.Remove(SourceHandle.Value);
|
||||
_master._checkAlError();
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = null;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
static Clyde()
|
||||
{
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
|
||||
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
|
||||
{
|
||||
@@ -28,20 +28,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.SetDllImportResolver(typeof(GL).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
if (OperatingSystem.IsLinux()
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
&& _dllMapLinux.TryGetValue(name, out var mappedName))
|
||||
{
|
||||
return NativeLibrary.Load(mappedName);
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS()
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
|
||||
&& _dllMapMacOS.TryGetValue(name, out mappedName))
|
||||
{
|
||||
return NativeLibrary.Load(mappedName);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
TextEntered?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowClosed(var reg, var args):
|
||||
reg.RequestClosed?.Invoke(args);
|
||||
reg.Closed?.Invoke(args);
|
||||
CloseWindow?.Invoke(args);
|
||||
|
||||
if (reg.DisposeOnClose && !reg.IsMainWindow)
|
||||
@@ -95,7 +95,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_eventDispatchQueue.Enqueue(new DEventScroll(ev));
|
||||
}
|
||||
|
||||
private void SendCloseWindow(WindowReg windowReg, WindowRequestClosedEventArgs ev)
|
||||
private void SendCloseWindow(WindowReg windowReg, WindowClosedEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowClosed(windowReg, ev));
|
||||
}
|
||||
@@ -156,7 +156,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed record DEventScroll(MouseWheelEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowRequestClosedEventArgs Args) : DEventBase;
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowClosedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowResized(WindowResizedEventArgs Args) : DEventBase;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -20,17 +19,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Viewport viewport, Box2 worldBounds, IEye eye)
|
||||
private void _drawGrids(Box2 worldBounds)
|
||||
{
|
||||
var mapId = eye.Position.MapId;
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
{
|
||||
// fall back to nullspace map
|
||||
mapId = MapId.Nullspace;
|
||||
// fall back to the default eye's map
|
||||
_eyeManager.ClearCurrentEye();
|
||||
mapId = _eyeManager.CurrentMap;
|
||||
}
|
||||
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? _currentViewport!.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
|
||||
var (gridProgram, _) = ActivateShaderInstance(_defaultShader.Handle);
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
@@ -53,8 +53,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var transform = compMan.GetComponent<ITransformComponent>(grid.GridEntityId);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks(worldBounds))
|
||||
foreach (var (_, chunk) in grid.GetMapChunks())
|
||||
{
|
||||
// Calc world bounds for chunk.
|
||||
if (!chunk.CalcWorldBounds().Intersects(in worldBounds))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_isChunkDirty(grid, chunk))
|
||||
{
|
||||
_updateChunkMesh(grid, chunk);
|
||||
@@ -204,14 +210,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _updateOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
Logger.DebugS("grid", $"Adding {gridId} to grid renderer");
|
||||
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
|
||||
}
|
||||
|
||||
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
Logger.DebugS("grid", $"Removing {gridId} from grid renderer");
|
||||
|
||||
var data = _mapChunkData[gridId];
|
||||
foreach (var chunkDatum in data.Values)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -202,10 +201,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
|
||||
private void DrawEntities(Viewport viewport, Box2 worldBounds, IEye eye)
|
||||
private void DrawEntities(Viewport viewport, Box2 worldBounds)
|
||||
{
|
||||
var mapId = eye.Position.MapId;
|
||||
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
|
||||
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -214,7 +212,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
|
||||
ProcessSpriteEntities(mapId, worldBounds, _drawingSpriteList);
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
// TODO: Make this check more accurate.
|
||||
var widerBounds = worldBounds.Enlarged(5);
|
||||
|
||||
ProcessSpriteEntities(_eyeManager.CurrentMap, widerBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -270,15 +273,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
break;
|
||||
}
|
||||
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
|
||||
RenderTexture? entityPostRenderTarget = null;
|
||||
Vector2i roundedPos = default;
|
||||
if (entry.sprite.PostShader != null)
|
||||
{
|
||||
// calculate world bounding box
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox();
|
||||
var spriteLB = spriteBB.BottomLeft;
|
||||
var spriteRT = spriteBB.TopRight;
|
||||
|
||||
@@ -290,9 +291,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
|
||||
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
|
||||
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
@@ -325,7 +323,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, in entry.worldRotation, in worldPosition);
|
||||
|
||||
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
|
||||
{
|
||||
@@ -366,14 +366,33 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
{
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
{
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
|
||||
comp.SpriteTree.QueryAabb(ref list, (
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = worldBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
|
||||
|
||||
tree.QueryAabb(ref list, ((
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
// TODO: Probably value in storing this as its own DynamicTree
|
||||
if (value.ContainerOccluded || !value.Visible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var entity = value.Owner;
|
||||
var transform = entity.Transform;
|
||||
|
||||
@@ -381,20 +400,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
entry.sprite = value;
|
||||
entry.worldRot = transform.WorldRotation;
|
||||
entry.matrix = transform.WorldMatrix;
|
||||
var worldPos = new Vector2(entry.matrix.R0C2, entry.matrix.R1C2);
|
||||
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
|
||||
var bounds = value.CalculateBoundingBox(worldPos);
|
||||
entry.yWorldPos = worldPos.Y - bounds.Extents.Y;
|
||||
var worldPos = entry.matrix.Transform(transform.LocalPosition);
|
||||
entry.yWorldPos = worldPos.Y;
|
||||
return true;
|
||||
|
||||
}, bounds);
|
||||
}), gridBounds, approx: true);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSplash(IRenderHandle handle)
|
||||
{
|
||||
var splashTex = _cfg.GetCVar(CVars.DisplaySplashLogo);
|
||||
var texture = _resourceCache.GetResource<TextureResource>(splashTex).Texture;
|
||||
var texture = _resourceCache.GetResource<TextureResource>("/Textures/Logo/logo.png").Texture;
|
||||
|
||||
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
|
||||
}
|
||||
@@ -470,13 +486,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
using (DebugGroup("Grids"))
|
||||
{
|
||||
_drawGrids(viewport, worldBounds, eye);
|
||||
_drawGrids(worldBounds);
|
||||
}
|
||||
|
||||
// We will also render worldspace overlays here so we can do them under / above entities as necessary
|
||||
using (DebugGroup("Entities"))
|
||||
{
|
||||
DrawEntities(viewport, worldBounds, eye);
|
||||
DrawEntities(viewport, worldBounds);
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user