mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69daaca399 | ||
|
|
5af7e60043 | ||
|
|
ab823dcd12 | ||
|
|
b3976eb8d7 | ||
|
|
91759cdd3c | ||
|
|
b000c3178b | ||
|
|
63b3ecdd13 | ||
|
|
a183a98f75 | ||
|
|
bded7ad228 | ||
|
|
75c4f48496 | ||
|
|
49fe2d59cf | ||
|
|
8a5b7f5146 | ||
|
|
ae4f470e1f | ||
|
|
18fcab6f71 | ||
|
|
1ae6c32c62 | ||
|
|
cf6925f19b | ||
|
|
b4c7ffe38a | ||
|
|
d5c2f45f14 | ||
|
|
dcc88d2d36 | ||
|
|
a274b8dfc2 | ||
|
|
f42c1379e0 | ||
|
|
c4fa7e98d4 | ||
|
|
8b9dadffb1 | ||
|
|
5e99c6d04d | ||
|
|
606d232dbb | ||
|
|
df877582a6 | ||
|
|
8ffdb090e6 | ||
|
|
033d6ffb22 | ||
|
|
3eb6e067f9 | ||
|
|
1f64f93ef4 | ||
|
|
7dd617b0d6 | ||
|
|
0567b70704 | ||
|
|
9963e89c14 | ||
|
|
29e0faed88 | ||
|
|
0ce6ff9870 | ||
|
|
dbe9a96dfa | ||
|
|
fc5e3ab69d | ||
|
|
ed4974141c | ||
|
|
3e5efd5ed0 | ||
|
|
e1110eadb4 | ||
|
|
06ace83a73 | ||
|
|
cd01ca924b | ||
|
|
38ad8ce132 | ||
|
|
ee440c2df9 | ||
|
|
32f3c863fb | ||
|
|
b00e0bef5a | ||
|
|
0a09b27918 | ||
|
|
c9f6a4e32a | ||
|
|
b205a14f69 | ||
|
|
d5f3292e0a | ||
|
|
561e4b330e | ||
|
|
36a5d102ff | ||
|
|
b9c39e0953 | ||
|
|
ad4c8be132 | ||
|
|
988cbf9a87 | ||
|
|
e26512001a | ||
|
|
8e97982f1e | ||
|
|
3ca686298e | ||
|
|
5e914cb13a | ||
|
|
a1bdfca8ba | ||
|
|
79deaca409 | ||
|
|
2eeb21431b | ||
|
|
c4062bcae9 | ||
|
|
cd3a85ea04 | ||
|
|
d15b5c7f22 | ||
|
|
18bbe2271d | ||
|
|
ee2b7a3a66 | ||
|
|
ca36671131 | ||
|
|
604a1a6960 | ||
|
|
2898f5396f | ||
|
|
39541639c5 | ||
|
|
50981ad1a1 | ||
|
|
0cbfbeffae | ||
|
|
e603153016 | ||
|
|
e7c417ca0c | ||
|
|
a3989f28eb | ||
|
|
38ace3c348 | ||
|
|
0e00170f45 | ||
|
|
261ee96cad | ||
|
|
2c851885db | ||
|
|
849be86455 | ||
|
|
ffd5c120be | ||
|
|
76b15dda70 | ||
|
|
0bf7f519ad | ||
|
|
f97f325a36 | ||
|
|
24315fa787 | ||
|
|
792179657b | ||
|
|
9b92bcf911 | ||
|
|
86e34ea27b | ||
|
|
62cf778958 | ||
|
|
5d667e44c3 | ||
|
|
6d84b8741c | ||
|
|
d08ca59b75 | ||
|
|
f06b046c1c | ||
|
|
bb412a6906 | ||
|
|
1baee3004c | ||
|
|
f18068c13a | ||
|
|
0ba00a1845 | ||
|
|
28caf0d74c | ||
|
|
ecbb32b70b | ||
|
|
8e2a9cc597 | ||
|
|
a7eb8201c9 | ||
|
|
1f95fe6782 | ||
|
|
07c1f9e1af | ||
|
|
826dce6659 | ||
|
|
cdf714f3ba | ||
|
|
671ca7959c | ||
|
|
b7a1345d3a | ||
|
|
835b6ebdba | ||
|
|
0ecabd6553 | ||
|
|
feaa69f825 | ||
|
|
857904a3d9 | ||
|
|
0b37418477 | ||
|
|
f234ecb2c3 | ||
|
|
b449959865 |
20
.github/workflows/codeql-analysis.yml
vendored
20
.github/workflows/codeql-analysis.yml
vendored
@@ -11,14 +11,14 @@
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '30 18 * * 6'
|
||||
#on:
|
||||
# push:
|
||||
# branches: [ master ]
|
||||
# pull_request:
|
||||
# # The branches below must be a subset of the branches above
|
||||
# branches: [ master ]
|
||||
# schedule:
|
||||
# - cron: '30 18 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -38,12 +38,12 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
|
||||
|
||||
5
MSBuild/Robust.Analyzers.targets
Normal file
5
MSBuild/Robust.Analyzers.targets
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Analyzers\Robust.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,2 +1,3 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<Import Project="Robust.Analyzers.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -1,24 +1,65 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<PropertyGroup>
|
||||
<!-- Avoid MSBuild adding a None entry for XAML files because they'd show up TWICE in the project view. -->
|
||||
<DefaultItemExcludes>**/*.xaml</DefaultItemExcludes>
|
||||
<RobustUseExternalMSBuild>true</RobustUseExternalMSBuild>
|
||||
<_RobustUseExternalMSBuild>$(RobustUseExternalMSBuild)</_RobustUseExternalMSBuild>
|
||||
<_RobustUseExternalMSBuild Condition="'$(_RobustForceInternalMSBuild)' == 'true'">false</_RobustUseExternalMSBuild>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="**\*.xaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="**\*.xaml" />
|
||||
<AdditionalFiles Include="**\*.xaml" />
|
||||
<EmbeddedResource Include="**\*.xaml"/>
|
||||
<AdditionalFiles Include="**\*.xaml"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<UsingTask TaskName="CompileRobustXamlTask" AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(Configuration)\net5.0\Robust.Client.Injectors.dll" />
|
||||
<Target Name="CompileRobustXaml" AfterTargets="AfterCompile">
|
||||
<UsingTask
|
||||
Condition="'$(_RobustUseExternalMSBuild)' != 'true' And $(DesignTimeBuild) != true"
|
||||
TaskName="CompileRobustXamlTask"
|
||||
AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(Configuration)\netstandard2.0\Robust.Client.Injectors.dll"/>
|
||||
<Target
|
||||
Name="CompileRobustXaml"
|
||||
Condition="Exists('@(IntermediateAssembly)')"
|
||||
AfterTargets="AfterCompile"
|
||||
Inputs="@(IntermediateAssembly);@(ReferencePathWithRefAssemblies)"
|
||||
Outputs="$(IntermediateOutputPath)XAML/doot">
|
||||
<PropertyGroup>
|
||||
<RobustXamlReferencesTemporaryFilePath Condition="'$(RobustXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)XAML/references</RobustXamlReferencesTemporaryFilePath>
|
||||
<RobustXamlOriginalCopyFilePath Condition="'$(RobustXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)XAML/original.dll</RobustXamlOriginalCopyFilePath>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile File="$(RobustXamlReferencesTemporaryFilePath)" Lines="@(ReferencePathWithRefAssemblies)" Overwrite="true" />
|
||||
<CompileRobustXamlTask AssemblyFile="@(IntermediateAssembly)" ReferencesFilePath="$(RobustXamlReferencesTemporaryFilePath)" OriginalCopyPath="$(RobustXamlOriginalCopyFilePath)" ProjectDirectory="$(MSBuildProjectDirectory)" AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)" SignAssembly="$(SignAssembly)" DelaySign="$(DelaySign)" />
|
||||
<WriteLinesToFile
|
||||
Condition="'$(_RobustForceInternalMSBuild)' != 'true'"
|
||||
File="$(RobustXamlReferencesTemporaryFilePath)"
|
||||
Lines="@(ReferencePathWithRefAssemblies)"
|
||||
Overwrite="true"/>
|
||||
|
||||
<!--
|
||||
UpdateBuildIndicator is done so that we can use MSBuild Inputs and Outputs on the target
|
||||
to avoid unecessary execution of this target
|
||||
Saves compile time if e.g. ONLY Robust.Client changes (Content.Client doesn't have to re-xaml).
|
||||
-->
|
||||
<CompileRobustXamlTask
|
||||
Condition="'$(_RobustUseExternalMSBuild)' != 'true'"
|
||||
AssemblyFile="@(IntermediateAssembly)"
|
||||
ReferencesFilePath="$(RobustXamlReferencesTemporaryFilePath)"
|
||||
OriginalCopyPath="$(RobustXamlOriginalCopyFilePath)"
|
||||
ProjectDirectory="$(MSBuildProjectDirectory)"
|
||||
AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)"
|
||||
SignAssembly="$(SignAssembly)"
|
||||
DelaySign="$(DelaySign)"
|
||||
UpdateBuildIndicator="$(IntermediateOutputPath)XAML/doot"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<DOTNET_HOST_PATH Condition="'$(DOTNET_HOST_PATH)' == ''">dotnet</DOTNET_HOST_PATH>
|
||||
</PropertyGroup>
|
||||
<Exec
|
||||
Condition="'$(_RobustUseExternalMSBuild)' == 'true'"
|
||||
Command=""$(DOTNET_HOST_PATH)" msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
Submodule NetSerializer updated: 60d23c01f9...1e103e8e29
@@ -17,6 +17,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
// On net472, we rely on Mono's DllMap for this. See the .dll.config file.
|
||||
NativeLibrary.SetDllImportResolver(typeof(GLFWNative).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
// Please keep in sync with what Robust.Shared/DllMapHelper.cs does.
|
||||
if (name != "glfw3.dll")
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
|
||||
92
Robust.Analyzers/ExplicitInterfaceAnalyzer.cs
Normal file
92
Robust.Analyzers/ExplicitInterfaceAnalyzer.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Document = Microsoft.CodeAnalysis.Document;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class ExplicitInterfaceAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
public readonly SyntaxKind[] ExcludedModifiers =
|
||||
{
|
||||
SyntaxKind.VirtualKeyword,
|
||||
SyntaxKind.AbstractKeyword,
|
||||
SyntaxKind.OverrideKeyword
|
||||
};
|
||||
|
||||
public const string DiagnosticId = "RA0000";
|
||||
|
||||
private const string Title = "No explicit interface specified";
|
||||
private const string MessageFormat = "No explicit interface specified";
|
||||
private const string Description = "Make sure to specify the interface in your method-declaration.";
|
||||
private const string Category = "Usage";
|
||||
|
||||
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
|
||||
|
||||
private const string RequiresExplicitImplementationAttributeMetadataName =
|
||||
"Robust.Shared.Analyzers.RequiresExplicitImplementationAttribute";
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.PropertyDeclaration);
|
||||
}
|
||||
|
||||
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
ISymbol symbol;
|
||||
Location location;
|
||||
switch (context.Node)
|
||||
{
|
||||
//we already have a explicit interface specified, no need to check further
|
||||
case MethodDeclarationSyntax methodDecl when methodDecl.ExplicitInterfaceSpecifier != null || methodDecl.Modifiers.Any(m => ExcludedModifiers.Contains(m.Kind())):
|
||||
return;
|
||||
case PropertyDeclarationSyntax propertyDecl when propertyDecl.ExplicitInterfaceSpecifier != null || propertyDecl.Modifiers.Any(m => ExcludedModifiers.Contains(m.Kind())):
|
||||
return;
|
||||
|
||||
case MethodDeclarationSyntax methodDecl:
|
||||
symbol = context.SemanticModel.GetDeclaredSymbol(methodDecl);
|
||||
location = methodDecl.Identifier.GetLocation();
|
||||
break;
|
||||
case PropertyDeclarationSyntax propertyDecl:
|
||||
symbol = context.SemanticModel.GetDeclaredSymbol(propertyDecl);
|
||||
location = propertyDecl.Identifier.GetLocation();
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var attrSymbol = context.Compilation.GetTypeByMetadataName(RequiresExplicitImplementationAttributeMetadataName);
|
||||
|
||||
var isInterfaceMember = symbol?.ContainingType.AllInterfaces.Any(
|
||||
i =>
|
||||
i.GetMembers().Any(m => SymbolEqualityComparer.Default.Equals(symbol, symbol.ContainingType.FindImplementationForInterfaceMember(m)))
|
||||
&& i.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol))
|
||||
) ?? false;
|
||||
|
||||
if (isInterfaceMember)
|
||||
{
|
||||
//we do not have an explicit interface specified. bad!
|
||||
var diagnostic = Diagnostic.Create(
|
||||
Rule,
|
||||
location);
|
||||
context.ReportDiagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Robust.Analyzers/Robust.Analyzers.csproj
Normal file
13
Robust.Analyzers/Robust.Analyzers.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
150
Robust.Analyzers/SerializableAnalyzer.cs
Normal file
150
Robust.Analyzers/SerializableAnalyzer.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class SerializableAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
// Metadata of the analyzer
|
||||
public const string DiagnosticId = "RA0001";
|
||||
|
||||
// You could use LocalizedString but it's a little more complicated for this sample
|
||||
private const string Title = "Class not marked as (Net)Serializable";
|
||||
private const string MessageFormat = "Class not marked as (Net)Serializable";
|
||||
private const string Description = "The class should be marked as (Net)Serializable.";
|
||||
private const string Category = "Usage";
|
||||
|
||||
private const string RequiresSerializableAttributeMetadataName = "Robust.Shared.Analyzers.RequiresSerializableAttribute";
|
||||
private const string SerializableAttributeMetadataName = "System.SerializableAttribute";
|
||||
private const string NetSerializableAttributeMetadataName = "Robust.Shared.Serialization.NetSerializableAttribute";
|
||||
|
||||
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
|
||||
}
|
||||
|
||||
private bool Marked(INamedTypeSymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
|
||||
{
|
||||
if (namedTypeSymbol == null) return false;
|
||||
if (namedTypeSymbol.GetAttributes()
|
||||
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol))) return true;
|
||||
return Marked(namedTypeSymbol.BaseType, attrSymbol);
|
||||
}
|
||||
|
||||
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
var attrSymbol = context.Compilation.GetTypeByMetadataName(RequiresSerializableAttributeMetadataName);
|
||||
var classDecl = (ClassDeclarationSyntax) context.Node;
|
||||
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
|
||||
if (classSymbol == null) return;
|
||||
|
||||
if (Marked(classSymbol, attrSymbol))
|
||||
{
|
||||
var attributes = classSymbol.GetAttributes();
|
||||
var serAttr = context.Compilation.GetTypeByMetadataName(SerializableAttributeMetadataName);
|
||||
var netSerAttr = context.Compilation.GetTypeByMetadataName(NetSerializableAttributeMetadataName);
|
||||
|
||||
var hasSerAttr = attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, serAttr));
|
||||
var hasNetSerAttr =
|
||||
attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, netSerAttr));
|
||||
|
||||
if (!hasSerAttr || !hasNetSerAttr)
|
||||
{
|
||||
var requiredAttributes = new List<string>();
|
||||
if(!hasSerAttr) requiredAttributes.Add(SerializableAttributeMetadataName);
|
||||
if(!hasNetSerAttr) requiredAttributes.Add(NetSerializableAttributeMetadataName);
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
Rule,
|
||||
classDecl.Identifier.GetLocation(),
|
||||
ImmutableDictionary.CreateRange(new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
"requiredAttributes", string.Join(",", requiredAttributes)
|
||||
}
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
public class SerializableCodeFixProvider : CodeFixProvider
|
||||
{
|
||||
private const string Title = "Annotate class as (Net)Serializable.";
|
||||
|
||||
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
|
||||
foreach (var diagnostic in context.Diagnostics)
|
||||
{
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var classDecl = root.FindToken(span.Start).Parent.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().First();
|
||||
|
||||
if(!diagnostic.Properties.TryGetValue("requiredAttributes", out var requiredAttributes)) return;
|
||||
|
||||
context.RegisterCodeFix(
|
||||
CodeAction.Create(
|
||||
Title,
|
||||
c => FixAsync(context.Document, classDecl, requiredAttributes, c),
|
||||
Title),
|
||||
diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Document> FixAsync(Document document, ClassDeclarationSyntax classDecl,
|
||||
string requiredAttributes, CancellationToken cancellationToken)
|
||||
{
|
||||
var attributes = new List<AttributeSyntax>();
|
||||
var namespaces = new List<string>();
|
||||
foreach (var attribute in requiredAttributes.Split(','))
|
||||
{
|
||||
var tempSplit = attribute.Split('.');
|
||||
namespaces.Add(string.Join(".",tempSplit.Take(tempSplit.Length-1)));
|
||||
var @class = tempSplit.Last();
|
||||
@class = @class.Substring(0, @class.Length - 9); //cut out "Attribute" at the end
|
||||
attributes.Add(SyntaxFactory.Attribute(SyntaxFactory.ParseName(@class)));
|
||||
}
|
||||
|
||||
var newClassDecl =
|
||||
classDecl.AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(attributes)));
|
||||
|
||||
var root = (CompilationUnitSyntax) await document.GetSyntaxRootAsync(cancellationToken);
|
||||
root = root.ReplaceNode(classDecl, newClassDecl);
|
||||
|
||||
foreach (var ns in namespaces)
|
||||
{
|
||||
if(root.Usings.Any(u => u.Name.ToString() == ns)) continue;
|
||||
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)));
|
||||
}
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SerializableAnalyzer.DiagnosticId);
|
||||
|
||||
public override FixAllProvider GetFixAllProvider()
|
||||
{
|
||||
return WellKnownFixAllProviders.BatchFixer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Framework;
|
||||
@@ -48,6 +49,19 @@ namespace Robust.Build.Tasks
|
||||
if(File.Exists(inputPdb))
|
||||
File.Copy(inputPdb, outputPdb, true);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(UpdateBuildIndicator))
|
||||
{
|
||||
if (!File.Exists(UpdateBuildIndicator))
|
||||
{
|
||||
File.Create(UpdateBuildIndicator).Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
File.SetLastWriteTime(UpdateBuildIndicator, DateTime.Now);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,6 +78,7 @@ namespace Robust.Build.Tasks
|
||||
public string OriginalCopyPath { get; set; }
|
||||
|
||||
public string OutputPath { get; set; }
|
||||
public string UpdateBuildIndicator { get; set; }
|
||||
|
||||
public string AssemblyOriginatorKeyFile { get; set; }
|
||||
public bool SignAssembly { get; set; }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -31,6 +31,16 @@ namespace Robust.Build.Tasks
|
||||
|
||||
var asm = typeSystem.TargetAssemblyDefinition;
|
||||
|
||||
if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null)
|
||||
{
|
||||
// If this type exists, the assembly has already been processed by us.
|
||||
// Do not run again, it would corrupt the file.
|
||||
// This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system,
|
||||
// but better safe than sorry eh?
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", ""));
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
var compileRes = CompileCore(engine, typeSystem);
|
||||
if (compileRes == null)
|
||||
return (true, false);
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace {nameSpace}
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
@@ -169,7 +169,7 @@ namespace {nameSpace}
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -189,7 +189,7 @@ namespace {nameSpace}
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.Audio.Midi
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
|
||||
private const string FallbackSoundfont = "/Resources/Midi/fallback.sf2";
|
||||
private const string FallbackSoundfont = "/Midi/fallback.sf2";
|
||||
|
||||
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
|
||||
|
||||
@@ -207,13 +207,10 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader);
|
||||
|
||||
foreach (var file in _resourceManager.ContentFindFiles(new ResourcePath("/Audio/MidiCustom/")))
|
||||
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls") continue;
|
||||
if (_resourceManager.TryGetDiskFilePath(file, out var path))
|
||||
{
|
||||
renderer.LoadSoundfont(path);
|
||||
}
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
@@ -382,10 +379,18 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public override IntPtr Open(string filename)
|
||||
{
|
||||
Stream? stream;
|
||||
if (filename.StartsWith("/Resources/"))
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
if (!IoCManager.Resolve<IResourceCache>().TryContentFileRead(filename.Substring(10), out stream))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
Stream? stream;
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var resourcePath = new ResourcePath(filename);
|
||||
|
||||
if (resourcePath.IsRooted && resourceCache.ContentFileExists(filename))
|
||||
{
|
||||
if (!resourceCache.TryContentFileRead(filename, out stream))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
else if (File.Exists(filename))
|
||||
|
||||
@@ -72,9 +72,9 @@ namespace Robust.Client.Audio.Midi
|
||||
int PlayerTotalTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tick of the MIDI player.
|
||||
/// Gets or sets (seeks) the current tick of the MIDI player.
|
||||
/// </summary>
|
||||
int PlayerTick { get; }
|
||||
int PlayerTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tick of the sequencer.
|
||||
@@ -235,7 +235,16 @@ namespace Robust.Client.Audio.Midi
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
public int PlayerTick => _player?.CurrentTick ?? 0;
|
||||
public int PlayerTick
|
||||
{
|
||||
get => _player?.CurrentTick ?? 0;
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
_player?.Seek(Math.Max(Math.Min(value, PlayerTotalTick), 0));
|
||||
}
|
||||
}
|
||||
|
||||
public uint SequencerTick => _sequencer?.Tick ?? 0;
|
||||
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
|
||||
|
||||
@@ -310,9 +319,8 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
if (_player == null)
|
||||
_player = new NFluidsynth.Player(_synth);
|
||||
_player.Stop();
|
||||
_player?.Dispose();
|
||||
_player = new NFluidsynth.Player(_synth);
|
||||
_player.AddMem(buffer);
|
||||
_player.SetPlaybackCallback(MidiPlayerEventHandler);
|
||||
_player.Play();
|
||||
@@ -351,10 +359,7 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public void StopAllNotes()
|
||||
{
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
_synth.AllNotesOff(i);
|
||||
}
|
||||
_synth.AllNotesOff(-1);
|
||||
}
|
||||
|
||||
public void LoadSoundfont(string filename, bool resetPresets = false)
|
||||
@@ -475,12 +480,6 @@ namespace Robust.Client.Audio.Midi
|
||||
lock(_playerStateLock)
|
||||
switch (midiEvent.Type)
|
||||
{
|
||||
// Sometimes MIDI files spam these for no good reason and I can't find any info on what they are.
|
||||
case 1:
|
||||
case 5:
|
||||
case 81:
|
||||
break;
|
||||
|
||||
// Note On 0x80
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
@@ -503,8 +502,10 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
// Program Change - 0xC0
|
||||
case 192:
|
||||
if(!DisableProgramChangeEvent)
|
||||
if (!DisableProgramChangeEvent)
|
||||
_synth.ProgramChange(midiEvent.Channel, midiEvent.Program);
|
||||
else
|
||||
return;
|
||||
break;
|
||||
|
||||
// Channel Pressure - 0xD0
|
||||
@@ -517,13 +518,17 @@ namespace Robust.Client.Audio.Midi
|
||||
_synth.PitchBend(midiEvent.Channel, midiEvent.Pitch);
|
||||
break;
|
||||
|
||||
// Sometimes MIDI files spam these for no good reason and I can't find any info on what they are.
|
||||
case 1:
|
||||
case 5:
|
||||
case 81:
|
||||
// System Messages - 0xF0
|
||||
case 240:
|
||||
break;
|
||||
return;
|
||||
|
||||
default:
|
||||
_midiSawmill.Warning("Unhandled midi event of type {0}", midiEvent.Type, midiEvent);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (FluidSynthInteropException)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Interfaces;
|
||||
using Robust.Client.Interfaces.Debugging;
|
||||
@@ -7,15 +7,14 @@ using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Utility;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client
|
||||
@@ -25,7 +24,7 @@ namespace Robust.Client
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
@@ -50,18 +49,28 @@ namespace Robust.Client
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgServerInfo>(MsgServerInfo.NAME, HandleServerInfo);
|
||||
_net.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME, HandleSetTickRate);
|
||||
_net.RegisterNetMessage<MsgServerInfoReq>(MsgServerInfoReq.NAME);
|
||||
_net.Connected += OnConnected;
|
||||
_net.ConnectFailed += OnConnectFailed;
|
||||
_net.Disconnect += OnNetDisconnect;
|
||||
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
|
||||
|
||||
_playMan.Initialize();
|
||||
_debugDrawMan.Initialize();
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void TickRateChanged(int tickrate)
|
||||
{
|
||||
if (GameInfo != null)
|
||||
{
|
||||
GameInfo.TickRate = (byte) tickrate;
|
||||
}
|
||||
|
||||
_timing.TickRate = (byte) tickrate;
|
||||
Logger.InfoS("client", $"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ConnectToServer(DnsEndPoint endPoint)
|
||||
{
|
||||
@@ -98,9 +107,39 @@ namespace Robust.Client
|
||||
|
||||
private void OnConnected(object? sender, NetChannelArgs args)
|
||||
{
|
||||
// request base info about the server
|
||||
var msgInfo = _net.CreateNetMessage<MsgServerInfoReq>();
|
||||
_net.ClientSendMessage(msgInfo);
|
||||
_configManager.SyncWithServer();
|
||||
_configManager.ReceivedInitialNwVars += OnReceivedClientData;
|
||||
}
|
||||
|
||||
private void OnReceivedClientData(object? sender, EventArgs e)
|
||||
{
|
||||
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
|
||||
|
||||
var info = GameInfo;
|
||||
|
||||
var serverName = _configManager.GetCVar<string>("game.hostname");
|
||||
if (info == null)
|
||||
{
|
||||
GameInfo = info = new ServerInfo(serverName);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ServerName = serverName;
|
||||
}
|
||||
|
||||
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
|
||||
info.ServerMaxPlayers = maxPlayers;
|
||||
|
||||
var userName = _net.ServerChannel!.UserName;
|
||||
var userId = _net.ServerChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,6 +174,7 @@ namespace Robust.Client
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_configManager.ClearReceivedInitialNwVars();
|
||||
OnRunLevelChanged(ClientRunLevel.Initialize);
|
||||
}
|
||||
|
||||
@@ -152,6 +192,7 @@ namespace Robust.Client
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
@@ -160,42 +201,6 @@ namespace Robust.Client
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void HandleServerInfo(MsgServerInfo msg)
|
||||
{
|
||||
var info = GameInfo;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
GameInfo = info = new ServerInfo(msg.ServerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ServerName = msg.ServerName;
|
||||
}
|
||||
|
||||
info.ServerMaxPlayers = msg.ServerMaxPlayers;
|
||||
info.TickRate = msg.TickRate;
|
||||
_timing.TickRate = msg.TickRate;
|
||||
Logger.InfoS("client", $"Tickrate changed to: {msg.TickRate}");
|
||||
|
||||
var userName = msg.MsgChannel.UserName;
|
||||
var userId = msg.MsgChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleSetTickRate(MsgSetTickRate message)
|
||||
{
|
||||
_timing.TickRate = message.NewTickRate;
|
||||
Logger.InfoS("client", $"Tickrate changed to: {message.NewTickRate} on tick {_timing.CurTick}");
|
||||
}
|
||||
|
||||
private void OnLocalStatusChanged(object? obj, StatusEventArgs eventArgs)
|
||||
{
|
||||
// player finished fully connecting to the server.
|
||||
|
||||
@@ -78,10 +78,11 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsole, ClientConsole>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
switch (mode)
|
||||
{
|
||||
case GameController.DisplayMode.Headless:
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Log;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
public class AddStringArgs : EventArgs
|
||||
{
|
||||
public string Text { get; }
|
||||
public Color Color { get; }
|
||||
|
||||
public AddStringArgs(string text, Color color)
|
||||
{
|
||||
Text = text;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public class AddFormattedMessageArgs : EventArgs
|
||||
{
|
||||
public readonly FormattedMessage Message;
|
||||
|
||||
public AddFormattedMessageArgs(FormattedMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ClientConsole : IClientConsole, IDebugConsole
|
||||
{
|
||||
private static readonly Color MsgColor = new Color(65, 105, 225);
|
||||
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly ILogManager logManager = default!;
|
||||
|
||||
private readonly Dictionary<string, IConsoleCommand> _commands = new Dictionary<string, IConsoleCommand>();
|
||||
private bool _requestedCommands;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
_network.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
|
||||
_network.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
|
||||
_network.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME);
|
||||
|
||||
Reset();
|
||||
logManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
_commands.Clear();
|
||||
_requestedCommands = false;
|
||||
_network.Connected += OnNetworkConnected;
|
||||
|
||||
InitializeCommands();
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
{
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
// We don't have anything to dispose.
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, IConsoleCommand> Commands => _commands;
|
||||
|
||||
public void AddLine(string text, Color color)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, color));
|
||||
}
|
||||
|
||||
public void AddLine(string text)
|
||||
{
|
||||
AddLine(text, Color.White);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ClearText?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler<AddStringArgs>? AddString;
|
||||
public event EventHandler? ClearText;
|
||||
public event EventHandler<AddFormattedMessageArgs>? AddFormatted;
|
||||
|
||||
private void HandleConCmdAck(MsgConCmdAck msg)
|
||||
{
|
||||
AddLine("< " + msg.Text, MsgColor);
|
||||
}
|
||||
|
||||
private void HandleConCmdReg(MsgConCmdReg msg)
|
||||
{
|
||||
foreach (var cmd in msg.Commands)
|
||||
{
|
||||
var commandName = cmd.Name;
|
||||
|
||||
// Do not do duplicate commands.
|
||||
if (_commands.ContainsKey(commandName))
|
||||
{
|
||||
Logger.DebugS("console", $"Server sent console command {commandName}, but we already have one with the same name. Ignoring.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var command = new ServerDummyCommand(commandName, cmd.Help, cmd.Description);
|
||||
_commands[commandName] = command;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes commands (chat messages starting with /)
|
||||
/// </summary>
|
||||
/// <param name="text">input text</param>
|
||||
public void ProcessCommand(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
// echo the command locally
|
||||
AddLine("> " + text, Color.Lime);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
|
||||
CommandParsing.ParseArguments(text, args);
|
||||
|
||||
var commandname = args[0];
|
||||
|
||||
var forward = true;
|
||||
if (_commands.ContainsKey(commandname))
|
||||
{
|
||||
var command = _commands[commandname];
|
||||
args.RemoveAt(0);
|
||||
forward = command.Execute(this, args.ToArray());
|
||||
}
|
||||
else if (!_network.IsConnected)
|
||||
{
|
||||
AddLine("Unknown command: " + commandname, Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (forward)
|
||||
SendServerConsoleCommand(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates and registeres all local commands.
|
||||
/// </summary>
|
||||
private void InitializeCommands()
|
||||
{
|
||||
foreach (var t in _reflectionManager.GetAllChildren<IConsoleCommand>())
|
||||
{
|
||||
var instance = (IConsoleCommand)Activator.CreateInstance(t)!;
|
||||
if (_commands.ContainsKey(instance.Command))
|
||||
throw new InvalidOperationException($"Command already registered: {instance.Command}");
|
||||
|
||||
_commands[instance.Command] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests remote commands from server.
|
||||
/// </summary>
|
||||
public void SendServerCommandRequest()
|
||||
{
|
||||
if (_requestedCommands)
|
||||
return;
|
||||
|
||||
if (!_network.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = _network.CreateNetMessage<MsgConCmdReg>();
|
||||
_network.ClientSendMessage(msg);
|
||||
|
||||
_requestedCommands = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command directly to the server.
|
||||
/// </summary>
|
||||
private void SendServerConsoleCommand(string text)
|
||||
{
|
||||
if (_network == null || !_network.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = _network.CreateNetMessage<MsgConCmd>();
|
||||
msg.Text = text;
|
||||
_network.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
public void AddFormattedLine(FormattedMessage message)
|
||||
{
|
||||
// Why the hell does this class implement IDebugConsole.
|
||||
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These dummies are made purely so list and help can list server-side commands.
|
||||
/// </summary>
|
||||
[Reflect(false)]
|
||||
internal class ServerDummyCommand : IConsoleCommand
|
||||
{
|
||||
internal ServerDummyCommand(string command, string help, string description)
|
||||
{
|
||||
Command = command;
|
||||
Help = help;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Command { get; }
|
||||
|
||||
public string Help { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
// Always forward to server.
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
210
Robust.Client/Console/ClientConsoleHost.cs
Normal file
210
Robust.Client/Console/ClientConsoleHost.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Log;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
public class AddStringArgs : EventArgs
|
||||
{
|
||||
public string Text { get; }
|
||||
|
||||
public bool Local { get; }
|
||||
|
||||
public bool Error { get; }
|
||||
|
||||
public AddStringArgs(string text, bool local, bool error)
|
||||
{
|
||||
Text = text;
|
||||
Local = local;
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public class AddFormattedMessageArgs : EventArgs
|
||||
{
|
||||
public readonly FormattedMessage Message;
|
||||
|
||||
public AddFormattedMessageArgs(FormattedMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IClientConsoleHost" />
|
||||
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
|
||||
{
|
||||
private bool _requestedCommands;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME);
|
||||
|
||||
Reset();
|
||||
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
AvailableCommands.Clear();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AddStringArgs>? AddString;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AddFormattedMessageArgs>? AddFormatted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddFormattedLine(FormattedMessage message)
|
||||
{
|
||||
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteError(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
return;
|
||||
|
||||
// echo the command locally
|
||||
WriteError(null, "> " + command);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
|
||||
CommandParsing.ParseArguments(command, args);
|
||||
|
||||
var commandName = args[0];
|
||||
|
||||
if (AvailableCommands.ContainsKey(commandName))
|
||||
{
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
|
||||
}
|
||||
else if (!NetManager.IsConnected)
|
||||
WriteError(null, "Unknown command: " + commandName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RemoteExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
if (!NetManager.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = NetManager.CreateNetMessage<MsgConCmd>();
|
||||
msg.Text = command;
|
||||
NetManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
// We don't have anything to dispose.
|
||||
}
|
||||
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
{
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
private void HandleConCmdAck(MsgConCmdAck msg)
|
||||
{
|
||||
OutputText("< " + msg.Text, false, msg.Error);
|
||||
}
|
||||
|
||||
private void HandleConCmdReg(MsgConCmdReg msg)
|
||||
{
|
||||
foreach (var cmd in msg.Commands)
|
||||
{
|
||||
string? commandName = cmd.Name;
|
||||
|
||||
// Do not do duplicate commands.
|
||||
if (AvailableCommands.ContainsKey(commandName))
|
||||
{
|
||||
Logger.DebugS("console", $"Server sent console command {commandName}, but we already have one with the same name. Ignoring.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var command = new ServerDummyCommand(commandName, cmd.Help, cmd.Description);
|
||||
AvailableCommands[commandName] = command;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests remote commands from server.
|
||||
/// </summary>
|
||||
private void SendServerCommandRequest()
|
||||
{
|
||||
if (_requestedCommands)
|
||||
return;
|
||||
|
||||
if (!NetManager.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = NetManager.CreateNetMessage<MsgConCmdReg>();
|
||||
NetManager.ClientSendMessage(msg);
|
||||
|
||||
_requestedCommands = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These dummies are made purely so list and help can list server-side commands.
|
||||
/// </summary>
|
||||
[Reflect(false)]
|
||||
internal class ServerDummyCommand : IConsoleCommand
|
||||
{
|
||||
internal ServerDummyCommand(string command, string help, string description)
|
||||
{
|
||||
Command = command;
|
||||
Help = help;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Command { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public string Help { get; }
|
||||
|
||||
// Always forward to server.
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
shell.RemoteExecuteCommand(argStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Robust.Client/Console/Commands/AddCompCommand.cs
Normal file
68
Robust.Client/Console/Commands/AddCompCommand.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AddCompCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "addcompc";
|
||||
public string Description => "Adds a component to an entity on the client";
|
||||
public string Help => "addcompc <uid> <componentName>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine("Wrong number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
var entityUid = EntityUid.Parse(args[0]);
|
||||
var componentName = args[1];
|
||||
|
||||
var compManager = IoCManager.Resolve<IComponentManager>();
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var entity = entityManager.GetEntity(entityUid);
|
||||
var component = (Component) compFactory.GetComponent(componentName);
|
||||
|
||||
component.Owner = entity;
|
||||
|
||||
compManager.AddComponent(entity, component);
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class RemoveCompCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "rmcompc";
|
||||
public string Description => "Removes a component from an entity.";
|
||||
public string Help => "rmcompc <uid> <componentName>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine("Wrong number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
var entityUid = EntityUid.Parse(args[0]);
|
||||
var componentName = args[1];
|
||||
|
||||
var compManager = IoCManager.Resolve<IComponentManager>();
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
var registration = compFactory.GetRegistration(componentName);
|
||||
|
||||
compManager.RemoveComponent(entityUid, registration.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
@@ -13,18 +13,17 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Spawns a client-side entity with specific type at your feet.";
|
||||
public string Help => "cspawn <entity type>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
|
||||
if (player?.ControlledEntity == null)
|
||||
{
|
||||
console.AddLine("You don't have an attached entity.");
|
||||
return false;
|
||||
shell.WriteLine("You don't have an attached entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
entityManager.SpawnEntity(args[0], player.ControlledEntity.Transform.Coordinates);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -12,12 +12,12 @@ namespace Robust.Client.Console.Commands
|
||||
[UsedImplicitly]
|
||||
internal sealed class CVarCommand : SharedCVarCommand, IConsoleCommand
|
||||
{
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1 || args.Length > 2)
|
||||
{
|
||||
console.AddLine("Must provide exactly one or two arguments.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Must provide exactly one or two arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
@@ -26,21 +26,21 @@ namespace Robust.Client.Console.Commands
|
||||
if (name == "?")
|
||||
{
|
||||
var cvars = configManager.GetRegisteredCVars().OrderBy(c => c);
|
||||
console.AddLine(string.Join("\n", cvars));
|
||||
return false;
|
||||
shell.WriteLine(string.Join("\n", cvars));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configManager.IsCVarRegistered(name))
|
||||
{
|
||||
console.AddLine($"CVar '{name}' is not registered. Use 'cvar ?' to get a list of all registered CVars.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError($"CVar '{name}' is not registered. Use 'cvar ?' to get a list of all registered CVars.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
// Read CVar
|
||||
var value = configManager.GetCVar<object>(name);
|
||||
console.AddLine(value.ToString() ?? "");
|
||||
shell.WriteLine(value.ToString() ?? "");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -54,11 +54,9 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
console.AddLine($"Input value is in incorrect format for type {type}");
|
||||
shell.WriteLine($"Input value is in incorrect format for type {type}");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +67,9 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Saves the client configuration to the config file";
|
||||
public string Help => "saveconfig";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IConfigurationManager>().SaveToFile();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// This file is for commands that do something to the console itself.
|
||||
// This file is for commands that do something to the console itself.
|
||||
// Not some generic console command type.
|
||||
// Couldn't think of a better name sorry.
|
||||
|
||||
using System;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -17,10 +16,9 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "Clears the debug console of all messages.";
|
||||
public string Description => "Clears the console.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
console.Clear();
|
||||
return false;
|
||||
shell.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +28,12 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "Fills the console with some nonsense for debugging.";
|
||||
public string Description => "Fill up the console for debugging.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
Color[] colors = { Color.Green, Color.Blue, Color.Red };
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
for (int x = 0; x < 50; x++)
|
||||
{
|
||||
console.AddLine("filling...", colors[random.Next(0, colors.Length)]);
|
||||
shell.WriteLine("filling...");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Text.RegularExpressions;
|
||||
using Robust.Client.Input;
|
||||
using System.Threading;
|
||||
using Robust.Client.Interfaces;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Interfaces.Debugging;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
@@ -26,6 +25,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
@@ -49,16 +49,14 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "Dump entity list";
|
||||
public string Description => "Dumps entity list of UIDs and prototype.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
foreach (var e in entityManager.GetEntities().OrderBy(e => e.Uid))
|
||||
{
|
||||
console.AddLine($"entity {e.Uid}, {e.Prototype?.ID}, {e.Transform.Coordinates}.", Color.White);
|
||||
shell.WriteLine($"entity {e.Uid}, {e.Prototype?.ID}, {e.Transform.Coordinates}.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,12 +66,12 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "Usage: getcomponentregistration <componentName>";
|
||||
public string Description => "Gets component registration information";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
@@ -94,19 +92,17 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
|
||||
|
||||
console.AddLine(message.ToString(), Color.White);
|
||||
shell.WriteLine(message.ToString());
|
||||
|
||||
foreach (var type in registration.References)
|
||||
{
|
||||
console.AddLine($" {type}", Color.White);
|
||||
shell.WriteLine($" {type}");
|
||||
}
|
||||
}
|
||||
catch (UnknownComponentException)
|
||||
{
|
||||
console.AddLine($"No registration found for '{args[0]}'", Color.Red);
|
||||
shell.WriteError($"No registration found for '{args[0]}'");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,14 +115,14 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public string Description => "Toggles a debug monitor in the F3 menu.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var monitor = IoCManager.Resolve<IUserInterfaceManager>().DebugMonitors;
|
||||
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0])
|
||||
@@ -159,11 +155,9 @@ namespace Robust.Client.Console.Commands
|
||||
monitor.ShowInput ^= true;
|
||||
break;
|
||||
default:
|
||||
console.AddLine($"Invalid key: {args[0]}");
|
||||
shell.WriteLine($"Invalid key: {args[0]}");
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +167,7 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "Throws an exception";
|
||||
public string Description => "Throws an exception";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
throw new InvalidOperationException("Fuck");
|
||||
}
|
||||
@@ -185,11 +179,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "";
|
||||
public string Description => "Enables debug drawing over all bounding boxes in the game, showing their size.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IDebugDrawing>();
|
||||
mgr.DebugColliders = !mgr.DebugColliders;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,11 +192,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "";
|
||||
public string Description => "Enables debug drawing over all entity positions in the game.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IDebugDrawing>();
|
||||
mgr.DebugPositions = !mgr.DebugPositions;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,25 +205,24 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "Usage: showrays <raylifetime>";
|
||||
public string Description => "Toggles debug drawing of physics rays. An integer for <raylifetime> must be provided";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
console.AddLine($"{args[0]} is not a valid integer.",Color.Red);
|
||||
return false;
|
||||
shell.WriteError($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
|
||||
mgr.DebugDrawRays = !mgr.DebugDrawRays;
|
||||
console.AddLine("Toggled showing rays to:" + mgr.DebugDrawRays.ToString(), Color.Green);
|
||||
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,10 +232,9 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "";
|
||||
public string Description => "Immediately disconnect from the server and go back to the main menu.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IClientNetManager>().ClientDisconnect("Disconnect command used.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,33 +247,33 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public string Description => "Displays verbose diagnostics for an entity.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!new Regex(@"^c?[0-9]+$").IsMatch(args[0])))
|
||||
{
|
||||
console.AddLine("Malformed UID", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Malformed UID");
|
||||
return;
|
||||
}
|
||||
|
||||
var uid = EntityUid.Parse(args[0]);
|
||||
var entmgr = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entmgr.TryGetEntity(uid, out var entity))
|
||||
{
|
||||
console.AddLine("That entity does not exist. Sorry lad.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("That entity does not exist. Sorry lad.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.AddLine($"{entity.Uid}: {entity.Prototype?.ID}/{entity.Name}");
|
||||
console.AddLine($"init/del/lmt: {entity.Initialized}/{entity.Deleted}/{entity.LastModifiedTick}");
|
||||
shell.WriteLine($"{entity.Uid}: {entity.Prototype?.ID}/{entity.Name}");
|
||||
shell.WriteLine($"init/del/lmt: {entity.Initialized}/{entity.Deleted}/{entity.LastModifiedTick}");
|
||||
foreach (var component in entity.GetAllComponents())
|
||||
{
|
||||
console.AddLine(component.ToString() ?? "");
|
||||
shell.WriteLine(component.ToString() ?? "");
|
||||
if (component is IComponentDebug debug)
|
||||
{
|
||||
foreach (var line in debug.GetDebugString().Split('\n'))
|
||||
@@ -293,12 +283,10 @@ namespace Robust.Client.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
console.AddLine("\t" + line);
|
||||
shell.WriteLine("\t" + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,12 +296,12 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "sggcell <gridID> <vector2i> [offset]\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Description => "Lists entities on a snap grid cell.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2 && args.Length != 3)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
string gridId = args[0];
|
||||
@@ -322,14 +310,14 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
console.AddLine($"{args[0]} is not a valid integer.",Color.Red);
|
||||
return false;
|
||||
shell.WriteError($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!new Regex(@"^-?[0-9]+,-?[0-9]+$").IsMatch(indices))
|
||||
{
|
||||
console.AddLine("mapIndicies must be of form x<int>,y<int>", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("mapIndicies must be of form x<int>,y<int>");
|
||||
return;
|
||||
}
|
||||
|
||||
SnapGridOffset selectedOffset;
|
||||
@@ -339,8 +327,8 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
else
|
||||
{
|
||||
console.AddLine("given offset type is not defined", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("given offset type is not defined");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
@@ -354,16 +342,13 @@ namespace Robust.Client.Console.Commands
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture)),
|
||||
selectedOffset))
|
||||
{
|
||||
console.AddLine(entity.Owner.Uid.ToString());
|
||||
shell.WriteLine(entity.Owner.Uid.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.AddLine("grid does not exist", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("grid does not exist");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,19 +358,17 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Changes the name used when attempting to connect to the server.";
|
||||
public string Help => Command + " <name>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
var client = IoCManager.Resolve<IBaseClient>();
|
||||
client.PlayerNameOverride = args[0];
|
||||
|
||||
console.AddLine($"Overriding player name to \"{args[0]}\".", Color.White);
|
||||
|
||||
return false;
|
||||
shell.WriteLine($"Overriding player name to \"{args[0]}\".");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,12 +378,12 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Pre-caches a resource.";
|
||||
public string Help => "ldrsc <path> <type>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var reflection = IoCManager.Resolve<IReflectionManager>();
|
||||
@@ -412,8 +395,8 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
catch(ArgumentException)
|
||||
{
|
||||
console.AddLine("Unable to find type", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Unable to find type");
|
||||
return;
|
||||
}
|
||||
|
||||
var getResourceMethod =
|
||||
@@ -423,7 +406,6 @@ namespace Robust.Client.Console.Commands
|
||||
DebugTools.Assert(getResourceMethod != null);
|
||||
var generic = getResourceMethod!.MakeGenericMethod(type);
|
||||
generic.Invoke(resourceCache, new object[] { args[0], true });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,12 +415,12 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Reloads a resource.";
|
||||
public string Help => "rldrsc <path> <type>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var reflection = IoCManager.Resolve<IReflectionManager>();
|
||||
@@ -450,15 +432,14 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
catch(ArgumentException)
|
||||
{
|
||||
console.AddLine("Unable to find type", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Unable to find type");
|
||||
return;
|
||||
}
|
||||
|
||||
var getResourceMethod = resourceCache.GetType().GetMethod("ReloadResource", new[] { typeof(string) });
|
||||
DebugTools.Assert(getResourceMethod != null);
|
||||
var generic = getResourceMethod!.MakeGenericMethod(type);
|
||||
generic.Invoke(resourceCache, new object[] { args[0] });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,18 +449,18 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Gets the tile count of a grid";
|
||||
public string Help => "Usage: gridtc <gridId>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
console.AddLine($"{args[0]} is not a valid integer.");
|
||||
return false;
|
||||
shell.WriteLine($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
var gridId = new GridId(int.Parse(args[0]));
|
||||
@@ -487,13 +468,11 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
if (mapManager.TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
console.AddLine(mapManager.GetGrid(gridId).GetAllTiles().Count().ToString());
|
||||
return false;
|
||||
shell.WriteLine(mapManager.GetGrid(gridId).GetAllTiles().Count().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
console.AddLine($"No grid exists with id {id}",Color.Red);
|
||||
return false;
|
||||
shell.WriteError($"No grid exists with id {id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,7 +483,7 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Dump GUI tree to /guidump.txt in user data.";
|
||||
public string Help => "guidump";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
|
||||
var res = IoCManager.Resolve<IResourceManager>();
|
||||
@@ -514,8 +493,6 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
_writeNode(root, 0, writer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void _writeNode(Control control, int indents, TextWriter writer)
|
||||
@@ -575,7 +552,7 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Open a dummy UI testing window";
|
||||
public string Help => "uitest";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var window = new SS14Window { CustomMinimumSize = (500, 400)};
|
||||
var tabContainer = new TabContainer();
|
||||
@@ -667,8 +644,6 @@ namespace Robust.Client.Console.Commands
|
||||
});
|
||||
|
||||
window.OpenCentered();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,11 +653,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Sets the system clipboard";
|
||||
public string Help => "setclipboard <text>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IClipboardManager>();
|
||||
mgr.SetText(args[0]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,11 +666,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Gets the system clipboard";
|
||||
public string Help => "getclipboard";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IClipboardManager>();
|
||||
console.AddLine(mgr.GetText());
|
||||
return false;
|
||||
shell.WriteLine(mgr.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,11 +679,11 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Toggles light rendering.";
|
||||
public string Help => "togglelight";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<ILightManager>();
|
||||
mgr.Enabled = !mgr.Enabled;
|
||||
return false;
|
||||
if (!mgr.LockConsoleAccess)
|
||||
mgr.Enabled = !mgr.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,12 +693,11 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Toggles fov for client.";
|
||||
public string Help => "togglefov";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IEyeManager>();
|
||||
if (mgr.CurrentEye != null)
|
||||
mgr.CurrentEye.DrawFov = !mgr.CurrentEye.DrawFov;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,11 +707,11 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Toggles hard fov for client (for debugging space-station-14#2353).";
|
||||
public string Help => "togglehardfov";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<ILightManager>();
|
||||
mgr.DrawHardFov = !mgr.DrawHardFov;
|
||||
return false;
|
||||
if (!mgr.LockConsoleAccess)
|
||||
mgr.DrawHardFov = !mgr.DrawHardFov;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,11 +721,24 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Toggles shadow rendering.";
|
||||
public string Help => "toggleshadows";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<ILightManager>();
|
||||
mgr.DrawShadows = !mgr.DrawShadows;
|
||||
return false;
|
||||
if (!mgr.LockConsoleAccess)
|
||||
mgr.DrawShadows = !mgr.DrawShadows;
|
||||
}
|
||||
}
|
||||
internal class ToggleLightBuf : IConsoleCommand
|
||||
{
|
||||
public string Command => "togglelightbuf";
|
||||
public string Description => "Toggles lighting rendering. This includes shadows but not FOV.";
|
||||
public string Help => "togglelightbuf";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<ILightManager>();
|
||||
if (!mgr.LockConsoleAccess)
|
||||
mgr.DrawLighting = !mgr.DrawLighting;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +748,7 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Run the GC.";
|
||||
public string Help => "gc [generation]";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
@@ -774,10 +759,21 @@ namespace Robust.Client.Console.Commands
|
||||
if (int.TryParse(args[0], out int result))
|
||||
GC.Collect(result);
|
||||
else
|
||||
console.AddLine("Failed to parse argument.",Color.Red);
|
||||
shell.WriteError("Failed to parse argument.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
internal class GcFullCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "gcf";
|
||||
public string Description => "Run the GC, fully, compacting LOH and everything.";
|
||||
public string Help => "gcf";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect(2, GCCollectionMode.Forced, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,16 +786,16 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public string Help => "gc_mode\nSee current GC Latencymode\ngc_mode [type]\n Change GC Latency mode to [type]";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var prevMode = GCSettings.LatencyMode;
|
||||
if (args.Length == 0)
|
||||
{
|
||||
console.AddLine($"current gc latency mode: {(int) prevMode} ({prevMode})");
|
||||
console.AddLine("possible modes:");
|
||||
shell.WriteLine($"current gc latency mode: {(int) prevMode} ({prevMode})");
|
||||
shell.WriteLine("possible modes:");
|
||||
foreach (int mode in (int[]) Enum.GetValues(typeof(GCLatencyMode)))
|
||||
{
|
||||
console.AddLine($" {mode}: {Enum.GetName(typeof(GCLatencyMode), mode)}");
|
||||
shell.WriteLine($" {mode}: {Enum.GetName(typeof(GCLatencyMode), mode)}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -811,16 +807,14 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
else if (!Enum.TryParse(args[0], true, out mode))
|
||||
{
|
||||
console.AddLine($"unknown gc latency mode: {args[0]}");
|
||||
return false;
|
||||
shell.WriteLine($"unknown gc latency mode: {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
console.AddLine($"attempting gc latency mode change: {(int) prevMode} ({prevMode}) -> {(int) mode} ({mode})");
|
||||
shell.WriteLine($"attempting gc latency mode change: {(int) prevMode} ({prevMode}) -> {(int) mode} ({mode})");
|
||||
GCSettings.LatencyMode = mode;
|
||||
console.AddLine($"resulting gc latency mode: {(int) GCSettings.LatencyMode} ({GCSettings.LatencyMode})");
|
||||
shell.WriteLine($"resulting gc latency mode: {(int) GCSettings.LatencyMode} ({GCSettings.LatencyMode})");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -834,15 +828,13 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public string Help => "szr_stats";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
|
||||
console.AddLine($"serialized: {RobustSerializer.BytesSerialized} bytes, {RobustSerializer.ObjectsSerialized} objects");
|
||||
console.AddLine($"largest serialized: {RobustSerializer.LargestObjectSerializedBytes} bytes, {RobustSerializer.LargestObjectSerializedType} objects");
|
||||
console.AddLine($"deserialized: {RobustSerializer.BytesDeserialized} bytes, {RobustSerializer.ObjectsDeserialized} objects");
|
||||
console.AddLine($"largest serialized: {RobustSerializer.LargestObjectDeserializedBytes} bytes, {RobustSerializer.LargestObjectDeserializedType} objects");
|
||||
|
||||
return false;
|
||||
shell.WriteLine($"serialized: {RobustSerializer.BytesSerialized} bytes, {RobustSerializer.ObjectsSerialized} objects");
|
||||
shell.WriteLine($"largest serialized: {RobustSerializer.LargestObjectSerializedBytes} bytes, {RobustSerializer.LargestObjectSerializedType} objects");
|
||||
shell.WriteLine($"deserialized: {RobustSerializer.BytesDeserialized} bytes, {RobustSerializer.ObjectsDeserialized} objects");
|
||||
shell.WriteLine($"largest serialized: {RobustSerializer.LargestObjectDeserializedBytes} bytes, {RobustSerializer.LargestObjectDeserializedType} objects");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -853,7 +845,7 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Gets info about a chunk under your mouse cursor.";
|
||||
public string Help => Command;
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
var inputMan = IoCManager.Resolve<IInputManager>();
|
||||
@@ -863,8 +855,8 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
if (!mapMan.TryFindGridAt(mousePos, out var grid))
|
||||
{
|
||||
console.AddLine("No grid under your mouse cursor.");
|
||||
return false;
|
||||
shell.WriteLine("No grid under your mouse cursor.");
|
||||
return;
|
||||
}
|
||||
|
||||
var internalGrid = (IMapGridInternal)grid;
|
||||
@@ -872,8 +864,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
console.AddLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
return false;
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,7 +881,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public static ConcurrentDictionary<string, bool>? _reloadShadersQueued = new();
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IResourceCacheInternal resC;
|
||||
if (args.Length == 1)
|
||||
@@ -899,8 +890,8 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
if (_watchers != null)
|
||||
{
|
||||
console.AddLine("Already watching.");
|
||||
return false;
|
||||
shell.WriteLine("Already watching.");
|
||||
return;
|
||||
}
|
||||
resC = IoCManager.Resolve<IResourceCacheInternal>();
|
||||
|
||||
@@ -969,11 +960,11 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
IoCManager.Resolve<IResourceCache>()
|
||||
.ReloadResource<ShaderSourceResource>(resPath);
|
||||
console.AddLine($"Reloaded shader: {resPath}");
|
||||
shell.WriteLine($"Reloaded shader: {resPath}");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
console.AddLine($"Failed to reload shader: {resPath}");
|
||||
shell.WriteLine($"Failed to reload shader: {resPath}");
|
||||
}
|
||||
|
||||
_reloadShadersQueued.TryRemove(ev.FullPath, out var _);
|
||||
@@ -993,17 +984,17 @@ namespace Robust.Client.Console.Commands
|
||||
++created;
|
||||
}
|
||||
|
||||
console.AddLine($"Created {created} shader directory watchers for {shaderCount} shaders.");
|
||||
shell.WriteLine($"Created {created} shader directory watchers for {shaderCount} shaders.");
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[0] == "-watch")
|
||||
{
|
||||
if (_watchers == null)
|
||||
{
|
||||
console.AddLine("No shader directory watchers active.");
|
||||
return false;
|
||||
shell.WriteLine("No shader directory watchers active.");
|
||||
return;
|
||||
}
|
||||
|
||||
var disposed = 0;
|
||||
@@ -1015,19 +1006,19 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
_watchers = null;
|
||||
|
||||
console.AddLine($"Disposed of {disposed} shader directory watchers.");
|
||||
shell.WriteLine($"Disposed of {disposed} shader directory watchers.");
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
console.AddLine("Not implemented.");
|
||||
return false;
|
||||
shell.WriteLine("Not implemented.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.AddLine("Reloading content shader resources...");
|
||||
shell.WriteLine("Reloading content shader resources...");
|
||||
|
||||
resC = IoCManager.Resolve<IResourceCacheInternal>();
|
||||
|
||||
@@ -1039,13 +1030,11 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
console.AddLine($"Failed to reload shader: {path}");
|
||||
shell.WriteLine($"Failed to reload shader: {path}");
|
||||
}
|
||||
}
|
||||
|
||||
console.AddLine("Done.");
|
||||
|
||||
return false;
|
||||
shell.WriteLine("Done.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1056,14 +1045,14 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Toggle fov and light debug layers";
|
||||
public string Help => "cldbglyr <layer>: Toggle <layer>\ncldbglyr: Turn all Layers off";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClydeInternal>();
|
||||
|
||||
if (args.Length < 1)
|
||||
{
|
||||
clyde.DebugLayers = ClydeDebugLayers.None;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
clyde.DebugLayers = args[0] switch
|
||||
@@ -1072,8 +1061,6 @@ namespace Robust.Client.Console.Commands
|
||||
"light" => ClydeDebugLayers.Light,
|
||||
_ => ClydeDebugLayers.None
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1083,12 +1070,12 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Keys key info for a key";
|
||||
public string Help => "keyinfo <Key>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine(Help);
|
||||
return false;
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClydeInternal>();
|
||||
@@ -1101,15 +1088,13 @@ namespace Robust.Client.Console.Commands
|
||||
var scanCode = clyde.GetKeyScanCode(key);
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
|
||||
console.AddLine($"name: '{name}' scan code: '{scanCode}' name via scan code: '{nameScanCode}'");
|
||||
shell.WriteLine($"name: '{name}' scan code: '{scanCode}' name via scan code: '{nameScanCode}'");
|
||||
}
|
||||
else if (int.TryParse(args[0], out var scanCode))
|
||||
{
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
console.AddLine($"name via scan code: '{nameScanCode}'");
|
||||
shell.WriteLine($"name via scan code: '{nameScanCode}'");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -12,23 +12,21 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Dumps a type's members in a format suitable for the sandbox configuration file.";
|
||||
public string Help => "Usage: dmetamem <type>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
console.AddLine("That type does not exist", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("That type does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sig in AssemblyTypeChecker.DumpMetaMembers(type))
|
||||
{
|
||||
System.Console.WriteLine(@$"- ""{sig}""");
|
||||
console.AddLine(sig);
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -12,37 +12,36 @@ namespace Robust.Client.Console.Commands
|
||||
public string Help => "When no arguments are provided, displays a generic help text. When an argument is passed, display the help text for the command with that name.";
|
||||
public string Description => "Display help text.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
console.AddLine("To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'.");
|
||||
shell.WriteLine("To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'.");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
string commandname = args[0];
|
||||
if (!console.Commands.ContainsKey(commandname))
|
||||
if (!shell.ConsoleHost.RegisteredCommands.ContainsKey(commandname))
|
||||
{
|
||||
if (!IoCManager.Resolve<IClientNetManager>().IsConnected)
|
||||
{
|
||||
// No server so nothing to respond with unknown command.
|
||||
console.AddLine("Unknown command: " + commandname, Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Unknown command: " + commandname);
|
||||
return;
|
||||
}
|
||||
// TODO: Maybe have a server side help?
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
IConsoleCommand command = console.Commands[commandname];
|
||||
console.AddLine(string.Format("{0} - {1}", command.Command, command.Description));
|
||||
console.AddLine(command.Help);
|
||||
IConsoleCommand command = shell.ConsoleHost.RegisteredCommands[commandname];
|
||||
shell.WriteLine(string.Format("{0} - {1}", command.Command, command.Description));
|
||||
shell.WriteLine(command.Help);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.AddLine("Invalid amount of arguments.", Color.Red);
|
||||
shell.WriteError("Invalid amount of arguments.");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ namespace Robust.Client.Console.Commands
|
||||
"only commands that contain the given string in their name will be listed.";
|
||||
public string Description => "List all commands, optionally with a filter.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var filter = "";
|
||||
if (args.Length == 1)
|
||||
@@ -64,14 +63,12 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
|
||||
var conGroup = IoCManager.Resolve<IClientConGroupController>();
|
||||
foreach (var command in console.Commands.Values
|
||||
foreach (var command in shell.ConsoleHost.RegisteredCommands.Values
|
||||
.Where(p => p.Command.Contains(filter) && conGroup.CanCommand(p.Command))
|
||||
.OrderBy(c => c.Command))
|
||||
{
|
||||
console.AddLine(command.Command + ": " + command.Description);
|
||||
shell.WriteLine(command.Command + ": " + command.Description);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
Robust.Client/Console/Commands/LauncherAuthCommand.cs
Normal file
67
Robust.Client/Console/Commands/LauncherAuthCommand.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
#if !FULL_RELEASE
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LauncherAuthCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "launchauth";
|
||||
public string Description => "Load authentication tokens from launcher data to aid in testing of live servers";
|
||||
public string Help => "launchauth [account name]";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var wantName = args.Length > 0 ? args[0] : null;
|
||||
|
||||
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir())!;
|
||||
var cfgPath = Path.Combine(basePath, "launcher", "launcher_config.json");
|
||||
|
||||
var data = JsonSerializer.Deserialize<LauncherConfig>(File.ReadAllText(cfgPath))!;
|
||||
|
||||
var login = wantName != null
|
||||
? data.Logins.FirstOrDefault(p => p.Username == wantName)
|
||||
: data.Logins.FirstOrDefault();
|
||||
|
||||
if (login == null)
|
||||
{
|
||||
shell.WriteLine("Unable to find a matching login");
|
||||
return;
|
||||
}
|
||||
|
||||
var token = login.Token.Token;
|
||||
var userId = login.UserId;
|
||||
|
||||
var cfg = IoCManager.Resolve<IAuthManager>();
|
||||
cfg.Token = token;
|
||||
cfg.UserId = new NetUserId(Guid.Parse(userId));
|
||||
}
|
||||
|
||||
private sealed class LauncherConfig
|
||||
{
|
||||
[JsonInclude] [JsonPropertyName("logins")]
|
||||
public LauncherLogin[] Logins = default!;
|
||||
}
|
||||
|
||||
private sealed class LauncherLogin
|
||||
{
|
||||
[JsonInclude] public string Username = default!;
|
||||
[JsonInclude] public string UserId = default!;
|
||||
[JsonInclude] public LauncherToken Token = default!;
|
||||
}
|
||||
|
||||
private sealed class LauncherToken
|
||||
{
|
||||
[JsonInclude] public string Token = default!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,7 +2,7 @@ using System.Linq;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -13,18 +13,16 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Lists loaded assemblies by load context.";
|
||||
public string Help => Command;
|
||||
|
||||
public bool Execute(IDebugConsole console, string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
foreach (var context in AssemblyLoadContext.All)
|
||||
{
|
||||
console.AddLine($"{context.Name}:");
|
||||
shell.WriteLine($"{context.Name}:");
|
||||
foreach (var assembly in context.Assemblies.OrderBy(a => a.FullName))
|
||||
{
|
||||
console.AddLine($" {assembly.FullName}");
|
||||
shell.WriteLine($" {assembly.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -13,12 +13,12 @@ namespace Robust.Client.Console.Commands
|
||||
+ "\n sawmill: A label prefixing log messages. This is the one you're setting the level for."
|
||||
+ "\n level: The log level. Must match one of the values of the LogLevel enum.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
console.AddLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args[0];
|
||||
@@ -32,13 +32,12 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
if (!Enum.TryParse<LogLevel>(levelname, out var result))
|
||||
{
|
||||
console.AddLine("Failed to parse 2nd argument. Must be one of the values of the LogLevel enum.");
|
||||
return false;
|
||||
shell.WriteLine("Failed to parse 2nd argument. Must be one of the values of the LogLevel enum.");
|
||||
return;
|
||||
}
|
||||
level = result;
|
||||
}
|
||||
Logger.GetSawmill(name).Level = level;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +50,12 @@ namespace Robust.Client.Console.Commands
|
||||
+ "\n level: The log level. Must match one of the values of the LogLevel enum."
|
||||
+ "\n message: The message to be logged. Wrap this in double quotes if you want to use spaces.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 3)
|
||||
{
|
||||
console.AddLine("Invalid argument amount. Expected 3 arguments.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Invalid argument amount. Expected 3 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args[0];
|
||||
@@ -64,13 +63,12 @@ namespace Robust.Client.Console.Commands
|
||||
var message = args[2]; // yes this doesn't support spaces idgaf.
|
||||
if (!Enum.TryParse<LogLevel>(levelname, out var result))
|
||||
{
|
||||
console.AddLine("Failed to parse 2nd argument. Must be one of the values of the LogLevel enum.");
|
||||
return false;
|
||||
shell.WriteLine("Failed to parse 2nd argument. Must be one of the values of the LogLevel enum.");
|
||||
return;
|
||||
}
|
||||
var level = result;
|
||||
|
||||
Logger.LogS(level, name, message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using System;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -9,10 +9,9 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Kills the game client instantly.";
|
||||
public string Help => "Kills the game client instantly, leaving no traces. No telling the server goodbye";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -6,17 +6,15 @@ using Robust.Shared.Maths;
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
#if CLIENT_SCRIPTING
|
||||
internal sealed class ScriptConsoleCommand : IConsoleCommand
|
||||
internal sealed class ScriptCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "csi";
|
||||
public string Description => "Opens a C# interactive console.";
|
||||
public string Help => "csi";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new ScriptConsoleClient().OpenCentered();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,33 +24,29 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Opens a variable watch window.";
|
||||
public string Help => "watch";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new WatchWindow().OpenCentered();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal sealed class ServerScriptConsoleCommand : IConsoleCommand
|
||||
internal sealed class ServerScriptCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "scsi";
|
||||
public string Description => "Opens a C# interactive console on the server.";
|
||||
public string Help => "scsi";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IScriptClient>();
|
||||
if (!mgr.CanScript)
|
||||
{
|
||||
console.AddLine(Loc.GetString("You do not have server side scripting permission."), Color.Red);
|
||||
return false;
|
||||
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
|
||||
return;
|
||||
}
|
||||
|
||||
mgr.StartSession();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Sends garbage to the server.";
|
||||
public string Help => "The server will reply with 'no u'";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
// MsgStringTableEntries is registered as NetMessageAccept.Client so the server will immediately deny it.
|
||||
// And kick us.
|
||||
@@ -19,8 +19,6 @@ namespace Robust.Client.Console.Commands
|
||||
var msg = net.CreateNetMessage<MsgStringTableEntries>();
|
||||
msg.Entries = new MsgStringTableEntries.Entry[0];
|
||||
net.ClientSendMessage(msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
Robust.Client/Console/Commands/SetInputContextCommand.cs
Normal file
34
Robust.Client/Console/Commands/SetInputContextCommand.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class SetInputContextCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "setinputcontext";
|
||||
public string Description => "Sets the active input context.";
|
||||
public string Help => "setinputcontext <context>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine("Invalid number of arguments!");
|
||||
return;
|
||||
}
|
||||
|
||||
var inputMan = IoCManager.Resolve<IInputManager>();
|
||||
|
||||
if (!inputMan.Contexts.Exists(args[0]))
|
||||
{
|
||||
shell.WriteLine("Context not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
inputMan.Contexts.SetActiveContext(args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
public interface IClientConsole : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the console into a useable state.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the console to a post-initialized state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
|
||||
event EventHandler<AddStringArgs> AddString;
|
||||
event EventHandler<AddFormattedMessageArgs> AddFormatted;
|
||||
event EventHandler ClearText;
|
||||
|
||||
IReadOnlyDictionary<string, IConsoleCommand> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses console commands (verbs).
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
void ProcessCommand(string text);
|
||||
|
||||
void SendServerCommandRequest();
|
||||
}
|
||||
}
|
||||
24
Robust.Client/Console/IClientConsoleHost.cs
Normal file
24
Robust.Client/Console/IClientConsoleHost.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
public interface IClientConsoleHost : IConsoleHost, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the console into a useable state.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the console to a post-initialized state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
event EventHandler<AddStringArgs> AddString;
|
||||
event EventHandler<AddFormattedMessageArgs> AddFormatted;
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -26,6 +27,7 @@ namespace Robust.Client.Debugging
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
@@ -48,7 +50,7 @@ namespace Robust.Client.Debugging
|
||||
if (value)
|
||||
{
|
||||
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
|
||||
_prototypeManager, _inputManager));
|
||||
_prototypeManager, _inputManager, _physicsManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -86,6 +88,7 @@ namespace Robust.Client.Debugging
|
||||
private readonly IComponentManager _componentManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IInputManager _inputManager;
|
||||
private readonly IPhysicsManager _physicsManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
@@ -94,12 +97,13 @@ namespace Robust.Client.Debugging
|
||||
private Vector2 _hoverStartScreen = Vector2.Zero;
|
||||
private List<IPhysBody> _hoverBodies = new();
|
||||
|
||||
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager)
|
||||
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
|
||||
: base(nameof(PhysicsOverlay))
|
||||
{
|
||||
_componentManager = compMan;
|
||||
_eyeManager = eyeMan;
|
||||
_inputManager = inputManager;
|
||||
_physicsManager = physicsManager;
|
||||
|
||||
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
@@ -158,23 +162,20 @@ namespace Robust.Client.Debugging
|
||||
_hoverStartScreen = mouseScreenPos;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysicsComponent>())
|
||||
{
|
||||
var physBody = (IPhysBody) boundingBox;
|
||||
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
|
||||
foreach (var physBody in _physicsManager.GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Entity.Transform;
|
||||
|
||||
// if not on the same map, continue
|
||||
if (transform.MapID != _eyeManager.CurrentMap || !transform.IsMapTransform)
|
||||
continue;
|
||||
|
||||
var worldBox = physBody.WorldAABB;
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
// if not on screen, or too small, continue
|
||||
if (!worldBox.Intersects(in viewport) || worldBox.IsEmpty())
|
||||
continue;
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
|
||||
foreach (var shape in physBody.PhysicsShapes)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@@ -31,6 +31,7 @@ using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Interfaces.Timers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -50,7 +51,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IClientConsole _console = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
@@ -67,6 +68,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
|
||||
[Dependency] private readonly IAuthManager _authManager = default!;
|
||||
|
||||
private CommandLineArgs? _commandLineArgs;
|
||||
private bool _disableAssemblyLoadContext;
|
||||
@@ -164,9 +166,10 @@ namespace Robust.Client
|
||||
|
||||
_userInterfaceManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
@@ -185,6 +188,8 @@ namespace Robust.Client
|
||||
_client.PlayerNameOverride = _commandLineArgs.Username;
|
||||
}
|
||||
|
||||
_authManager.LoadFromEnv();
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
||||
|
||||
@@ -55,15 +55,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Register<AppearanceComponent>();
|
||||
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
|
||||
|
||||
|
||||
Register<AppearanceTestComponent>();
|
||||
Register<SnapGridComponent>();
|
||||
|
||||
Register<ClientUserInterfaceComponent>();
|
||||
RegisterReference<ClientUserInterfaceComponent, SharedUserInterfaceComponent>();
|
||||
|
||||
RegisterIgnore("IgnorePause");
|
||||
|
||||
Register<AnimationPlayerComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
|
||||
@@ -48,11 +48,21 @@ namespace Robust.Client.GameObjects.Components.Animations
|
||||
return;
|
||||
}
|
||||
|
||||
List<string>? toRemove = null;
|
||||
// TODO: Get rid of this ToArray() allocation.
|
||||
foreach (var (key, playback) in _playingAnimations.ToArray())
|
||||
{
|
||||
var keep = UpdatePlayback(Owner, playback, frameTime);
|
||||
if (!keep)
|
||||
{
|
||||
toRemove ??= new List<string>();
|
||||
toRemove.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_playingAnimations.Remove(key);
|
||||
AnimationCompleted?.Invoke(key);
|
||||
|
||||
@@ -54,17 +54,17 @@ namespace Robust.Client.GameObjects
|
||||
return (T) data[key];
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(Enum key, [MaybeNullWhen(false)] out T data)
|
||||
public override bool TryGetData<T>(Enum key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(string key, [MaybeNullWhen(false)] out T data)
|
||||
public override bool TryGetData<T>(string key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
internal bool TryGetData<T>(object key, [MaybeNullWhen(false)] out T data)
|
||||
internal bool TryGetData<T>(object key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
if (this.data.TryGetValue(key, out var dat))
|
||||
{
|
||||
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
return true;
|
||||
}
|
||||
|
||||
data = default;
|
||||
data = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static IDirectionalTextureProvider TextureForConfig(ObjectSerializer serializer, IResourceCache resourceCache)
|
||||
private static IRsiStateLike TextureForConfig(ObjectSerializer serializer, IResourceCache resourceCache)
|
||||
{
|
||||
DebugTools.Assert(serializer.Reading);
|
||||
|
||||
if (serializer.TryGetCacheData<IDirectionalTextureProvider>(SerializationCache, out var dirTex))
|
||||
if (serializer.TryGetCacheData<IRsiStateLike>(SerializationCache, out var dirTex))
|
||||
{
|
||||
return dirTex;
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public static IDirectionalTextureProvider? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
if (!prototype.Components.TryGetValue("Icon", out var mapping))
|
||||
{
|
||||
|
||||
12
Robust.Client/GameObjects/Components/IgnorePauseComponent.cs
Normal file
12
Robust.Client/GameObjects/Components/IgnorePauseComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
|
||||
namespace Robust.Client.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedIgnorePauseComponent))]
|
||||
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -991,6 +991,9 @@ namespace Robust.Client.GameObjects
|
||||
var mRotation = Matrix3.CreateRotation(angle);
|
||||
Matrix3.Multiply(ref mRotation, ref mOffset, out var transform);
|
||||
|
||||
// Only apply scale if needed.
|
||||
if(!Scale.EqualsApprox(Vector2.One)) transform.Multiply(Matrix3.CreateScale(Scale));
|
||||
|
||||
transform.Multiply(worldTransform);
|
||||
|
||||
RenderInternal(drawingHandle, worldRotation, overrideDirection, transform);
|
||||
@@ -1043,7 +1046,7 @@ namespace Robust.Client.GameObjects
|
||||
var rsi = layer.RSI ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
||||
{
|
||||
state = GetFallbackState();
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var layerSpecificDir = layer.EffectiveDirection(state, worldRotation, overrideDirection);
|
||||
@@ -1276,7 +1279,7 @@ namespace Robust.Client.GameObjects
|
||||
var rsi = layer.RSI ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
||||
{
|
||||
state = GetFallbackState();
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
if (!state.IsAnimated)
|
||||
@@ -1404,7 +1407,7 @@ namespace Robust.Client.GameObjects
|
||||
var rsi = layer.RSI ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
||||
{
|
||||
state = GetFallbackState();
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
if (state.IsAnimated)
|
||||
@@ -1415,9 +1418,9 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private RSI.State GetFallbackState()
|
||||
internal static RSI.State GetFallbackState(IResourceCache cache)
|
||||
{
|
||||
var rsi = resourceCache.GetResource<RSIResource>("/Textures/error.rsi").RSI;
|
||||
var rsi = cache.GetResource<RSIResource>("/Textures/error.rsi").RSI;
|
||||
return rsi["error"];
|
||||
}
|
||||
|
||||
@@ -1735,14 +1738,14 @@ namespace Robust.Client.GameObjects
|
||||
var rsi = ActualRsi;
|
||||
if (rsi == null)
|
||||
{
|
||||
state = _parent.GetFallbackState();
|
||||
state = GetFallbackState(_parent.resourceCache);
|
||||
Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!rsi.TryGetState(stateId, out state))
|
||||
{
|
||||
state = _parent.GetFallbackState();
|
||||
state = GetFallbackState(_parent.resourceCache);
|
||||
Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI. Trace:\n{1}", stateId,
|
||||
Environment.StackTrace);
|
||||
}
|
||||
@@ -1793,7 +1796,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public IDirectionalTextureProvider? Icon
|
||||
public IRsiStateLike? Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -1803,13 +1806,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var texture = layer.Texture;
|
||||
|
||||
if (!layer.State.IsValid) return null;
|
||||
if (!layer.State.IsValid) return texture;
|
||||
|
||||
// Pull texture from RSI state instead.
|
||||
var rsi = layer.RSI ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
||||
{
|
||||
state = GetFallbackState();
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -1867,21 +1870,21 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
}
|
||||
|
||||
public static IDirectionalTextureProvider? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);
|
||||
if (icon != null) return icon;
|
||||
|
||||
if (!prototype.Components.TryGetValue("Sprite", out var spriteNode))
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
{
|
||||
return resourceCache.GetFallback<TextureResource>().Texture;
|
||||
return GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity() {Prototype = prototype};
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
dummy.Delete();
|
||||
|
||||
return spriteComponent?.Icon ?? resourceCache.GetFallback<TextureResource>().Texture;
|
||||
return spriteComponent.Icon ?? GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
#region DummyIconEntity
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
foreach (var animationPlayerComponent in EntityManager.ComponentManager.EntityQuery<AnimationPlayerComponent>())
|
||||
foreach (var animationPlayerComponent in EntityManager.ComponentManager.EntityQuery<AnimationPlayerComponent>(true))
|
||||
{
|
||||
animationPlayerComponent.Update(frameTime);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var appearanceTestComponent in EntityManager.ComponentManager.EntityQuery<AppearanceTestComponent>())
|
||||
foreach (var appearanceTestComponent in EntityManager.ComponentManager.EntityQuery<AppearanceTestComponent>(true))
|
||||
{
|
||||
appearanceTestComponent.OnUpdate(frameTime);
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
|
||||
private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev)
|
||||
{
|
||||
var gridId = ev.Coordinates.GetGridId(_entityManager);
|
||||
|
||||
if (!_mapManager.GridExists(gridId))
|
||||
var mapId = ev.Coordinates.GetMapId(_entityManager);
|
||||
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
{
|
||||
Logger.Error(
|
||||
$"Server tried to play sound on grid {gridId}, which does not exist. Ignoring.");
|
||||
$"Server tried to play sound on map {mapId}, which does not exist. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,13 +111,13 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
if (stream.TrackingCoordinates != null)
|
||||
{
|
||||
var coords = stream.TrackingCoordinates.Value;
|
||||
if (_mapManager.GridExists(coords.GetGridId(_entityManager)))
|
||||
if (_mapManager.MapExists(coords.GetMapId(_entityManager)))
|
||||
{
|
||||
mapPos = stream.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Grid no longer exists, delete stream.
|
||||
// Map no longer exists, delete stream.
|
||||
StreamDone(stream);
|
||||
continue;
|
||||
}
|
||||
@@ -356,4 +356,76 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
|
||||
event Action PlaybackDone;
|
||||
}
|
||||
|
||||
public static class AudioSystemExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this IEntity entity,
|
||||
string filename,
|
||||
AudioParams? audioParams,
|
||||
AudioSystem? audioSystem = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream following an entity.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this IEntity entity,
|
||||
AudioStream stream,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(stream, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this EntityCoordinates coordinates,
|
||||
string filename,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream at a static position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this EntityCoordinates coordinates,
|
||||
AudioStream stream,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(stream, coordinates, audioParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
@@ -57,47 +57,54 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public void CreateEffect(EffectSystemMessage message)
|
||||
{
|
||||
// The source of effects is either local actions during FirstTimePredicted, or the network at LastServerTick
|
||||
// When replaying predicted input, don't spam effects.
|
||||
if(gameTiming.InPrediction && !gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (message.AttachedEntityUid != null && message.Coordinates != default)
|
||||
{
|
||||
Logger.Warning("Set both an AttachedEntityUid and EntityCoordinates on an EffectSystemMessage for sprite {0} which is not supported!", message.EffectSprite);
|
||||
}
|
||||
|
||||
var gameTime = gameTiming.CurTime;
|
||||
if (gameTime > message.DeathTime) //Did we already die in transit? That's pretty troubling isn't it
|
||||
if (message.LifeTime <= TimeSpan.Zero)
|
||||
{
|
||||
Logger.Warning("Effect using sprite {0} died in transit to the client", message.EffectSprite);
|
||||
Logger.Warning("Effect using sprite {0} had zero lifetime.", message.EffectSprite);
|
||||
return;
|
||||
}
|
||||
|
||||
//Create effect from creation message
|
||||
var effect = new Effect(message, resourceCache, _mapManager, _entityManager);
|
||||
effect.Deathtime = gameTiming.CurTime + message.LifeTime;
|
||||
if (effect.AttachedEntityUid != null)
|
||||
{
|
||||
effect.AttachedEntity = _entityManager.GetEntity(effect.AttachedEntityUid.Value);
|
||||
}
|
||||
|
||||
//Age the effect through a single update to the previous update tick of the effect system
|
||||
//effect.Update((float)((lasttimeprocessed - effect.Age).TotalSeconds));
|
||||
|
||||
_Effects.Add(effect);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var curTime = gameTiming.CurTime;
|
||||
for (int i = 0; i < _Effects.Count; i++)
|
||||
{
|
||||
var effect = _Effects[i];
|
||||
|
||||
//Update variables of the effect via its deltas
|
||||
effect.Update(frameTime);
|
||||
|
||||
//These effects have died
|
||||
if (effect.Age > effect.Deathtime)
|
||||
// Effects are purely visual, so they don't need to be ran through prediction.
|
||||
// once CurTime ever passes DeathTime (clients render the top at IsFirstTimePredicted, where this happens) just remove them.
|
||||
if (curTime > effect.Deathtime)
|
||||
{
|
||||
//Remove from the effects list and decrement the iterator
|
||||
_Effects.Remove(effect);
|
||||
i--;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update variables of the effect via its deltas
|
||||
effect.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,14 +212,9 @@ namespace Robust.Client.GameObjects
|
||||
public bool Shaded = true;
|
||||
|
||||
/// <summary>
|
||||
/// Effect's age -- from 0f
|
||||
/// CurTime after which the effect will "die"
|
||||
/// </summary>
|
||||
public TimeSpan Age = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Time after which the effect will "die"
|
||||
/// </summary>
|
||||
public TimeSpan Deathtime = TimeSpan.FromSeconds(1);
|
||||
public TimeSpan Deathtime;
|
||||
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
@@ -245,8 +247,6 @@ namespace Robust.Client.GameObjects
|
||||
RadialAcceleration = effectcreation.RadialAcceleration;
|
||||
TangentialVelocity = effectcreation.TangentialVelocity;
|
||||
TangentialAcceleration = effectcreation.TangentialAcceleration;
|
||||
Age = effectcreation.Born;
|
||||
Deathtime = effectcreation.DeathTime;
|
||||
Rotation = effectcreation.Rotation;
|
||||
RotationRate = effectcreation.RotationRate;
|
||||
Size = effectcreation.Size;
|
||||
@@ -260,10 +260,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
Age += TimeSpan.FromSeconds(frameTime);
|
||||
if (Age >= Deathtime)
|
||||
return;
|
||||
|
||||
Velocity += Acceleration * frameTime;
|
||||
RadialVelocity += RadialAcceleration * frameTime;
|
||||
TangentialVelocity += TangentialAcceleration * frameTime;
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var eyeComponent in EntityManager.ComponentManager.EntityQuery<EyeComponent>())
|
||||
foreach (var eyeComponent in EntityManager.ComponentManager.EntityQuery<EyeComponent>(true))
|
||||
{
|
||||
eyeComponent.UpdateEyePosition();
|
||||
}
|
||||
|
||||
@@ -128,7 +128,8 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
|
||||
if (!entity.TryGetComponent(out InputComponent? inputComp))
|
||||
{
|
||||
Logger.DebugS("input.context", $"AttachedEnt has no InputComponent: entId={entity.Uid}, entProto={entity.Prototype}");
|
||||
Logger.DebugS("input.context", $"AttachedEnt has no InputComponent: entId={entity.Uid}, entProto={entity.Prototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,7 +139,8 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("input.context", $"Unknown context: entId={entity.Uid}, entProto={entity.Prototype}, context={inputComp.ContextName}");
|
||||
Logger.ErrorS("input.context", $"Unknown context: entId={entity.Uid}, entProto={entity.Prototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Client.Interfaces;
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
@@ -41,7 +42,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
@@ -57,7 +58,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
public bool Predicting { get; private set; }
|
||||
|
||||
public int PredictSize { get; private set; }
|
||||
public int PredictTickBias { get; private set; }
|
||||
public float PredictLagBias { get; private set; }
|
||||
|
||||
public int StateBufferMergeThreshold { get; private set; }
|
||||
|
||||
@@ -82,14 +84,16 @@ namespace Robust.Client.GameStates
|
||||
_config.OnValueChanged(CVars.NetInterpRatio, i => _processor.InterpRatio = i, true);
|
||||
_config.OnValueChanged(CVars.NetLogging, b => _processor.Logging = b, true);
|
||||
_config.OnValueChanged(CVars.NetPredict, b => Predicting = b, true);
|
||||
_config.OnValueChanged(CVars.NetPredictSize, i => PredictSize = i, true);
|
||||
_config.OnValueChanged(CVars.NetPredictTickBias, i => PredictTickBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetPredictLagBias, i => PredictLagBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetStateBufMergeThreshold, i => StateBufferMergeThreshold = i, true);
|
||||
|
||||
_processor.Interpolation = _config.GetCVar(CVars.NetInterp);
|
||||
_processor.InterpRatio = _config.GetCVar(CVars.NetInterpRatio);
|
||||
_processor.Logging = _config.GetCVar(CVars.NetLogging);
|
||||
Predicting = _config.GetCVar(CVars.NetPredict);
|
||||
PredictSize = _config.GetCVar(CVars.NetPredictSize);
|
||||
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
|
||||
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -174,7 +178,7 @@ namespace Robust.Client.GameStates
|
||||
var i = 0;
|
||||
for (; i < applyCount; i++)
|
||||
{
|
||||
_timing.CurTick = _lastProcessedTick + 1;
|
||||
_timing.LastRealTick = _timing.CurTick = _lastProcessedTick + 1;
|
||||
|
||||
// TODO: We could theoretically communicate with the GameStateProcessor better here.
|
||||
// Since game states are sliding windows, it is possible that we need less than applyCount applies here.
|
||||
@@ -256,9 +260,9 @@ namespace Robust.Client.GameStates
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f; // seconds.
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictSize;
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
@@ -377,6 +381,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
|
||||
262
Robust.Client/GameStates/NetEntityOverlay.cs
Normal file
262
Robust.Client/GameStates/NetEntityOverlay.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
/// <summary>
|
||||
/// A network entity report that lists all entities as they are updated through game states.
|
||||
/// https://developer.valvesoftware.com/wiki/Networking_Entities#cl_entityreport
|
||||
/// </summary>
|
||||
class NetEntityOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private const int TrafficHistorySize = 64; // Size of the traffic history bar in game ticks.
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
private readonly Font _font;
|
||||
private readonly int _lineHeight;
|
||||
private readonly List<NetEntity> _netEnts = new();
|
||||
|
||||
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_lineHeight = _font.GetLineHeight(1);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
}
|
||||
|
||||
private void HandleGameStateApplied(GameStateAppliedArgs args)
|
||||
{
|
||||
if(_gameTiming.InPrediction) // we only care about real server states.
|
||||
return;
|
||||
|
||||
// Shift traffic history down one
|
||||
for (var i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var traffic = _netEnts[i].Traffic;
|
||||
for (int j = 1; j < TrafficHistorySize; j++)
|
||||
{
|
||||
traffic[j - 1] = traffic[j];
|
||||
}
|
||||
|
||||
traffic[^1] = 0;
|
||||
}
|
||||
|
||||
var gameState = args.AppliedState;
|
||||
|
||||
if(gameState.EntityStates is not null)
|
||||
{
|
||||
// Loop over every entity that gets updated this state and record the traffic
|
||||
foreach (var entityState in gameState.EntityStates)
|
||||
{
|
||||
var newEnt = true;
|
||||
for(var i=0;i<_netEnts.Count;i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
if (netEnt.Id != entityState.Uid)
|
||||
continue;
|
||||
|
||||
//TODO: calculate size of state and record it here.
|
||||
netEnt.Traffic[^1] = 1;
|
||||
netEnt.LastUpdate = gameState.ToSequence;
|
||||
newEnt = false;
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
break;
|
||||
}
|
||||
|
||||
if (!newEnt)
|
||||
continue;
|
||||
|
||||
var newNetEnt = new NetEntity(entityState.Uid);
|
||||
newNetEnt.Traffic[^1] = 1;
|
||||
newNetEnt.LastUpdate = gameState.ToSequence;
|
||||
_netEnts.Add(newNetEnt);
|
||||
}
|
||||
}
|
||||
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize*2, pvsSize*2));
|
||||
|
||||
int timeout = _gameTiming.TickRate * 3;
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
if(_entityManager.EntityExists(netEnt.Id))
|
||||
{
|
||||
//TODO: Whoever is working on PVS remake, change the InPVS detection.
|
||||
var position = _entityManager.GetEntity(netEnt.Id).Transform.MapPosition;
|
||||
netEnt.InPVS = !pvsEnabled || (pvsBox.Contains(position.Position) && position.MapId == pvsCenter.MapId);
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
continue;
|
||||
}
|
||||
|
||||
netEnt.Exists = false;
|
||||
if (netEnt.LastUpdate.Value + timeout < _gameTiming.LastRealTick.Value)
|
||||
{
|
||||
_netEnts.RemoveAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
if (!_netManager.IsConnected)
|
||||
return;
|
||||
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
var screenHandle = (DrawingHandleScreen)handle;
|
||||
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
var xPos = 100;
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {_entityManager.GetEntity(netEnt.Id).Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTrafficBox(DrawingHandleScreen handle, ref NetEntity netEntity, int x, int y)
|
||||
{
|
||||
handle.DrawRect(UIBox2.FromDimensions(x+1, y, TrafficHistorySize + 1, _lineHeight), new Color(32, 32, 32, 128));
|
||||
handle.DrawRect(UIBox2.FromDimensions(x, y, TrafficHistorySize + 2, _lineHeight), Color.Gray.WithAlpha(0.15f), false);
|
||||
|
||||
var traffic = netEntity.Traffic;
|
||||
|
||||
//TODO: Local peak size, actually scale the peaks
|
||||
for (int i = 0; i < TrafficHistorySize; i++)
|
||||
{
|
||||
if(traffic[i] == 0)
|
||||
continue;
|
||||
|
||||
var xPos = x + 1 + i;
|
||||
var yPosA = y + 1;
|
||||
var yPosB = yPosA + _lineHeight - 1;
|
||||
handle.DrawLine(new Vector2(xPos, yPosA), new Vector2(xPos, yPosB), Color.Green);
|
||||
}
|
||||
}
|
||||
|
||||
private Color CalcTextColor(ref NetEntity ent)
|
||||
{
|
||||
if(!ent.Exists)
|
||||
return Color.Gray; // Entity is deleted, will be removed from list soon.
|
||||
|
||||
if(!ent.InPVS)
|
||||
return Color.Red; // Entity still exists outside PVS, but not updated anymore.
|
||||
|
||||
if(_gameTiming.LastRealTick < ent.LastUpdate + _gameTiming.TickRate)
|
||||
return Color.Blue; //Entity in PVS generating ongoing traffic.
|
||||
|
||||
return Color.Green; // Entity in PVS, but not updated recently.
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
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 chr in str)
|
||||
{
|
||||
var advance = font.DrawChar(handle, chr, baseLine, 1, textColor);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private struct NetEntity
|
||||
{
|
||||
public GameTick LastUpdate;
|
||||
public readonly EntityUid Id;
|
||||
public readonly int[] Traffic;
|
||||
public bool Exists;
|
||||
public bool InPVS;
|
||||
|
||||
public NetEntity(EntityUid id)
|
||||
{
|
||||
LastUpdate = GameTick.Zero;
|
||||
Id = id;
|
||||
Traffic = new int[TrafficHistorySize];
|
||||
Exists = true;
|
||||
InPVS = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class NetEntityReportCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_entityreport";
|
||||
public string Help => "net_entityreport <0|1>";
|
||||
public string Description => "Toggles the net entity report panel.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!byte.TryParse(args[0], out var iValue))
|
||||
{
|
||||
shell.WriteError("Invalid argument: Needs to be 0 or 1.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bValue = iValue > 0;
|
||||
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
|
||||
{
|
||||
overlayMan.AddOverlay(new NetEntityOverlay());
|
||||
shell.WriteLine("Enabled network entity report overlay.");
|
||||
}
|
||||
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
|
||||
{
|
||||
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
|
||||
shell.WriteLine("Disabled network entity report overlay.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -172,18 +172,18 @@ namespace Robust.Client.GameStates
|
||||
public string Help => "net_graph <0|1>";
|
||||
public string Description => "Toggles the net statistics pannel.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!byte.TryParse(args[0], out var iValue))
|
||||
{
|
||||
console.AddLine("Invalid argument: Needs to be 0 or 1.");
|
||||
return false;
|
||||
shell.WriteLine("Invalid argument: Needs to be 0 or 1.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bValue = iValue > 0;
|
||||
@@ -192,15 +192,13 @@ namespace Robust.Client.GameStates
|
||||
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
|
||||
{
|
||||
overlayMan.AddOverlay(new NetGraphOverlay());
|
||||
console.AddLine("Enabled network overlay.");
|
||||
shell.WriteLine("Enabled network overlay.");
|
||||
}
|
||||
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
|
||||
{
|
||||
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
|
||||
console.AddLine("Disabled network overlay.");
|
||||
shell.WriteLine("Disabled network overlay.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Overlays;
|
||||
using Robust.Client.Graphics.Shaders;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
@@ -34,7 +34,7 @@ namespace Robust.Client.GameStates
|
||||
handle.UseShader(_shader);
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysicsComponent>())
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysicsComponent>(true))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = ((IComponent)boundingBox).Owner.Transform;
|
||||
@@ -73,18 +73,18 @@ namespace Robust.Client.GameStates
|
||||
public string Help => "net_draw_interp <0|1>";
|
||||
public string Description => "Toggles the debug drawing of the network interpolation.";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
|
||||
return false;
|
||||
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!byte.TryParse(args[0], out var iValue))
|
||||
{
|
||||
console.AddLine("Invalid argument: Needs to be 0 or 1.");
|
||||
return false;
|
||||
shell.WriteLine("Invalid argument: Needs to be 0 or 1.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bValue = iValue > 0;
|
||||
@@ -93,15 +93,13 @@ namespace Robust.Client.GameStates
|
||||
if (bValue && !overlayMan.HasOverlay(nameof(NetInterpOverlay)))
|
||||
{
|
||||
overlayMan.AddOverlay(new NetInterpOverlay());
|
||||
console.AddLine("Enabled network interp overlay.");
|
||||
shell.WriteLine("Enabled network interp overlay.");
|
||||
}
|
||||
else if (overlayMan.HasOverlay(nameof(NetInterpOverlay)))
|
||||
{
|
||||
overlayMan.RemoveOverlay(nameof(NetInterpOverlay));
|
||||
console.AddLine("Disabled network interp overlay.");
|
||||
shell.WriteLine("Disabled network interp overlay.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
while (readSamples < totalSamples)
|
||||
{
|
||||
var read = vorbis.ReadSamples(buffer, readSamples * channels, (int)totalSamples - readSamples);
|
||||
var read = vorbis.ReadSamples(buffer, readSamples * channels, buffer.Length - readSamples);
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private const string UniProjViewMatrices = "projectionViewMatrices";
|
||||
private const string UniUniformConstants = "uniformConstants";
|
||||
|
||||
private static readonly Color AmbientLightColor = Color.Black;
|
||||
|
||||
private const int BindingIndexProjView = 0;
|
||||
private const int BindingIndexUniformConstants = 1;
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// OpenGL ES capabilities.
|
||||
CheckGLCap(ref _hasGLKhrDebug, "khr_debug", (3, 2), "GL_KHR_debug");
|
||||
if (CompareVersion(3, 2, major, minor))
|
||||
if (!CompareVersion(3, 2, major, minor))
|
||||
{
|
||||
// We're ES <3.2, KHR_debug is extension and needs KHR suffixes.
|
||||
_isGLKhrDebugESExtension = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Buffers;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -30,7 +31,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Horizontal width, in pixels, of the shadow maps used to render FOV.
|
||||
// I figured this was more accuracy sensitive than lights so resolution is significantly higher.
|
||||
private const int FovMapSize = 2048;
|
||||
private const int MaxLightsPerScene = 128;
|
||||
|
||||
// The maximum possible amount of lights in the light list.
|
||||
// In the average case, the only cost of increasing this value is memory.
|
||||
// If you are ever in a situation where this value needs to be increased, however, it will also implicitly cost some CPU time to sort the additional lights.
|
||||
private const int LightsToRenderListSize = 2048;
|
||||
|
||||
private ClydeShaderInstance _fovDebugShaderInstance = default!;
|
||||
|
||||
@@ -80,16 +85,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// For depth calculation of lighting shadows.
|
||||
private RenderTexture _shadowRenderTarget = default!;
|
||||
// Used because otherwise a MaxLightsPerScene change callback getting hit on startup causes interesting issues (read: bugs)
|
||||
private bool _shadowRenderTargetCanInitializeSafely = false;
|
||||
|
||||
// Proxies to textures of the above render targets.
|
||||
private ClydeTexture FovTexture => _fovRenderTarget.Texture;
|
||||
private ClydeTexture ShadowTexture => _shadowRenderTarget.Texture;
|
||||
|
||||
private readonly (PointLightComponent light, Vector2 pos)[] _lightsToRenderList
|
||||
= new (PointLightComponent light, Vector2 pos)[MaxLightsPerScene];
|
||||
private (PointLightComponent light, Vector2 pos, float distanceSquared)[] _lightsToRenderList = new (PointLightComponent light, Vector2 pos, float distanceSquared)[LightsToRenderListSize];
|
||||
|
||||
private unsafe void InitLighting()
|
||||
{
|
||||
// Other...
|
||||
LoadLightingShaders();
|
||||
|
||||
{
|
||||
@@ -160,10 +167,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
// Shadow FBO.
|
||||
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, MaxLightsPerScene),
|
||||
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
|
||||
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
|
||||
nameof(_shadowRenderTarget));
|
||||
_shadowRenderTargetCanInitializeSafely = true;
|
||||
MaxLightsPerSceneChanged(_maxLightsPerScene);
|
||||
}
|
||||
|
||||
private void LoadLightingShaders()
|
||||
@@ -334,6 +339,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
DrawFov(viewport, eye);
|
||||
|
||||
if (!_lightManager.DrawLighting)
|
||||
{
|
||||
BindRenderTargetFull(viewport.RenderTarget);
|
||||
GL.Viewport(0, 0, viewport.Size.X, viewport.Size.Y);
|
||||
CheckGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
using (DebugGroup("Draw shadow depth"))
|
||||
{
|
||||
PrepareDepthDraw(RtToLoaded(_shadowRenderTarget));
|
||||
@@ -344,7 +357,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var (light, lightPos) = lights[i];
|
||||
var (light, lightPos, _) = lights[i];
|
||||
|
||||
DrawOcclusionDepth(lightPos, ShadowMapSize, light.Radius, i);
|
||||
}
|
||||
@@ -355,7 +368,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
|
||||
CheckGlError();
|
||||
GLClearColor(Color.FromSrgb(AmbientLightColor));
|
||||
GLClearColor(_lightManager.AmbientLightColor);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
CheckGlError();
|
||||
|
||||
@@ -382,7 +395,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var (component, lightPos) = lights[i];
|
||||
var (component, lightPos, _) = lights[i];
|
||||
|
||||
var transform = component.Owner.Transform;
|
||||
|
||||
@@ -473,25 +486,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_lightingReady = true;
|
||||
}
|
||||
|
||||
private ((PointLightComponent light, Vector2 pos)[] lights, int count, Box2 expandedBounds)
|
||||
private ((PointLightComponent light, Vector2 pos, float distanceSquared)[] lights, int count, Box2 expandedBounds)
|
||||
GetLightsToRender(MapId map, in Box2 worldBounds)
|
||||
{
|
||||
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
|
||||
// As they could still affect the shadows of (large) light sources.
|
||||
// We expand the world bounds so that it encompasses the center of every light source.
|
||||
// This should make it so no culled occluder can make a difference.
|
||||
// (if the occluder is in the current lights at all, it's still not between the light and the world bounds).
|
||||
var expandedBounds = worldBounds;
|
||||
|
||||
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
var lightTree = renderingTreeSystem.GetLightTreeForMap(map);
|
||||
|
||||
var state = (this, expandedBounds, count: 0);
|
||||
var state = (this, worldBounds, count: 0);
|
||||
|
||||
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 expandedBounds, int count) state, in PointLightComponent light) =>
|
||||
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
|
||||
{
|
||||
var transform = light.Owner.Transform;
|
||||
|
||||
if (state.count >= LightsToRenderListSize)
|
||||
{
|
||||
// There are too many lights to fit in the static memory.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
return true;
|
||||
@@ -501,26 +513,45 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var circle = new Circle(lightPos, light.Radius);
|
||||
|
||||
if (!circle.Intersects(state.expandedBounds))
|
||||
// If the light doesn't touch anywhere the camera can see, it doesn't matter.
|
||||
if (!circle.Intersects(state.worldBounds))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
state.clyde._lightsToRenderList[state.count] = (light, lightPos);
|
||||
state.count += 1;
|
||||
|
||||
state.expandedBounds = state.expandedBounds.ExtendToContain(lightPos);
|
||||
|
||||
if (state.count == MaxLightsPerScene)
|
||||
{
|
||||
// TODO: Allow more than MaxLightsPerScene lights.
|
||||
return false;
|
||||
}
|
||||
float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared;
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
return true;
|
||||
}, expandedBounds);
|
||||
}, worldBounds);
|
||||
|
||||
return (_lightsToRenderList, state.count, state.expandedBounds);
|
||||
if (state.count > _maxLightsPerScene)
|
||||
{
|
||||
// There are too many lights to fit in the scene.
|
||||
// This check must occur before occluder expansion, or else bad things happen.
|
||||
// Sort lights by distance.
|
||||
Array.Sort(_lightsToRenderList, 0, state.count, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
|
||||
{
|
||||
return x.distanceSquared.CompareTo(y.distanceSquared);
|
||||
}));
|
||||
// Then effectively delete the furthest lights.
|
||||
state.count = _maxLightsPerScene;
|
||||
}
|
||||
|
||||
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
|
||||
// As they could still affect the shadows of (large) light sources.
|
||||
// We expand the world bounds so that it encompasses the center of every light source.
|
||||
// This should make it so no culled occluder can make a difference.
|
||||
// (if the occluder is in the current lights at all, it's still not between the light and the world bounds).
|
||||
var expandedBounds = worldBounds;
|
||||
|
||||
for (var i = 0; i < state.count; i++)
|
||||
{
|
||||
var (_, lightPos, _) = _lightsToRenderList[i];
|
||||
expandedBounds = expandedBounds.ExtendToContain(lightPos);
|
||||
}
|
||||
|
||||
return (_lightsToRenderList, state.count, expandedBounds);
|
||||
}
|
||||
|
||||
private void BlurOntoWalls(Viewport viewport, IEye eye)
|
||||
@@ -959,6 +990,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
protected override void MaxLightsPerSceneChanged(int newValue)
|
||||
{
|
||||
_maxLightsPerScene = newValue;
|
||||
|
||||
// This guard is in place because otherwise the shadow FBO is initialized before GL is initialized.
|
||||
if (!_shadowRenderTargetCanInitializeSafely)
|
||||
return;
|
||||
|
||||
if (_shadowRenderTarget != null)
|
||||
{
|
||||
DeleteRenderTexture(_shadowRenderTarget.Handle);
|
||||
}
|
||||
// Shadow FBO.
|
||||
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, _maxLightsPerScene),
|
||||
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
|
||||
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
|
||||
nameof(_shadowRenderTarget));
|
||||
}
|
||||
|
||||
protected override void SoftShadowsChanged(bool newValue)
|
||||
{
|
||||
_enableSoftShadows = newValue;
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGlError();
|
||||
|
||||
// Check on original format is NOT a bug, this is so srgb emulation works
|
||||
textureObject = GenTexture(texture, size, format.ColorFormat == RTCF.Rgba8Srgb, name == null ? null : $"{name}-color");
|
||||
textureObject = GenTexture(texture, size, format.ColorFormat == RTCF.Rgba8Srgb, name == null ? null : $"{name}-color", TexturePixelType.RenderTarget);
|
||||
}
|
||||
|
||||
// Depth/stencil buffers.
|
||||
|
||||
@@ -800,7 +800,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return;
|
||||
}
|
||||
|
||||
GL.CreateBuffers(1, out uint pbo);
|
||||
GL.GenBuffers(1, out uint pbo);
|
||||
CheckGlError();
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
|
||||
CheckGlError();
|
||||
|
||||
@@ -11,6 +11,9 @@ using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
|
||||
using PIF = OpenToolkit.Graphics.OpenGL4.PixelInternalFormat;
|
||||
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
|
||||
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -20,8 +23,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private ClydeTexture _stockTextureBlack = default!;
|
||||
private ClydeTexture _stockTextureTransparent = default!;
|
||||
|
||||
private readonly Dictionary<ClydeHandle, LoadedTexture> _loadedTextures =
|
||||
new();
|
||||
private readonly Dictionary<ClydeHandle, LoadedTexture> _loadedTextures = new();
|
||||
|
||||
private readonly ConcurrentQueue<ClydeHandle> _textureDisposeQueue = new();
|
||||
|
||||
@@ -69,85 +71,129 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Flip image because OpenGL reads images upside down.
|
||||
var copy = FlipClone(image);
|
||||
|
||||
var texture = new GLHandle((uint) GL.GenTexture());
|
||||
CheckGlError();
|
||||
GL.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
||||
CheckGlError();
|
||||
ApplySampleParameters(actualParams.SampleParameters);
|
||||
|
||||
PixelInternalFormat internalFormat;
|
||||
PixelFormat pixelDataFormat;
|
||||
PixelType pixelDataType;
|
||||
bool isActuallySrgb = false;
|
||||
|
||||
if (pixelType == typeof(Rgba32))
|
||||
{
|
||||
// Note that if _hasGLSrgb is off, we import an sRGB texture as non-sRGB.
|
||||
// Shaders are expected to compensate for this
|
||||
internalFormat = (actualParams.Srgb && _hasGLSrgb) ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8;
|
||||
isActuallySrgb = actualParams.Srgb;
|
||||
pixelDataFormat = PixelFormat.Rgba;
|
||||
pixelDataType = PixelType.UnsignedByte;
|
||||
}
|
||||
else if (pixelType == typeof(A8))
|
||||
{
|
||||
if (image.Width % 4 != 0 || image.Height % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("Alpha8 images must have multiple of 4 sizes.");
|
||||
}
|
||||
|
||||
internalFormat = PixelInternalFormat.R8;
|
||||
pixelDataFormat = PixelFormat.Red;
|
||||
pixelDataType = PixelType.UnsignedByte;
|
||||
|
||||
unsafe
|
||||
{
|
||||
// TODO: Does it make sense to default to 1 for RGB parameters?
|
||||
// It might make more sense to pass some options to change swizzling.
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.One);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.One);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.One);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.Red);
|
||||
}
|
||||
}
|
||||
else if (pixelType == typeof(L8) && !actualParams.Srgb)
|
||||
{
|
||||
// Can only use R8 for L8 if sRGB is OFF.
|
||||
// Because OpenGL doesn't provide sRGB single/dual channel image formats.
|
||||
// Vulkan when?
|
||||
if (copy.Width % 4 != 0 || copy.Height % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("L8 non-sRGB images must have multiple of 4 sizes.");
|
||||
}
|
||||
|
||||
internalFormat = PixelInternalFormat.R8;
|
||||
pixelDataFormat = PixelFormat.Red;
|
||||
pixelDataType = PixelType.UnsignedByte;
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.Red);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.Red);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.Red);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.One);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"Unable to handle pixel type '{pixelType.Name}'");
|
||||
}
|
||||
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var span = copy.GetPixelSpan();
|
||||
fixed (T* ptr = span)
|
||||
{
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, internalFormat, copy.Width, copy.Height, 0,
|
||||
pixelDataFormat, pixelDataType, (IntPtr) ptr);
|
||||
CheckGlError();
|
||||
// Still bound.
|
||||
DoTexUpload(copy.Width, copy.Height, actualParams.Srgb, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
var pressureEst = EstPixelSize(internalFormat) * copy.Width * copy.Height;
|
||||
return texture;
|
||||
}
|
||||
|
||||
return GenTexture(texture, (copy.Width, copy.Height), isActuallySrgb, name, pressureEst);
|
||||
public unsafe OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
if (!_hasGLTextureSwizzle)
|
||||
{
|
||||
// Actually create RGBA32 texture if missing texture swizzle.
|
||||
// This is fine (TexturePixelType that's stored) because all other APIs do the same.
|
||||
if (typeof(T) == typeof(A8) || typeof(T) == typeof(L8))
|
||||
{
|
||||
return CreateBlankTexture<Rgba32>(size, name, loadParams);
|
||||
}
|
||||
}
|
||||
|
||||
var texture = CreateBaseTextureInternal<T>(
|
||||
size.X, size.Y,
|
||||
actualParams,
|
||||
name);
|
||||
|
||||
// Texture still bound, run glTexImage2D with null data param to specify bounds.
|
||||
DoTexUpload<T>(size.X, size.Y, actualParams.Srgb, null);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private unsafe void DoTexUpload<T>(int width, int height, bool srgb, T* ptr) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (sizeof(T) < 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
var (pif, pf, pt) = PixelEnums<T>(srgb);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, pif, width, height, 0, pf, pt, (IntPtr) ptr);
|
||||
CheckGlError();
|
||||
|
||||
if (sizeof(T) < 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private ClydeTexture CreateBaseTextureInternal<T>(
|
||||
int width, int height,
|
||||
in TextureLoadParameters loadParams,
|
||||
string? name = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var texture = new GLHandle((uint) GL.GenTexture());
|
||||
CheckGlError();
|
||||
GL.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
||||
CheckGlError();
|
||||
ApplySampleParameters(loadParams.SampleParameters);
|
||||
|
||||
var (pif, _, _) = PixelEnums<T>(loadParams.Srgb);
|
||||
var pixelType = typeof(T);
|
||||
var texPixType = GetTexturePixelType<T>();
|
||||
var isActuallySrgb = false;
|
||||
|
||||
if (pixelType == typeof(Rgba32))
|
||||
{
|
||||
isActuallySrgb = loadParams.Srgb;
|
||||
}
|
||||
else if (pixelType == typeof(A8))
|
||||
{
|
||||
DebugTools.Assert(_hasGLTextureSwizzle);
|
||||
|
||||
// TODO: Does it make sense to default to 1 for RGB parameters?
|
||||
// It might make more sense to pass some options to change swizzling.
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.One);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.One);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.One);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.Red);
|
||||
CheckGlError();
|
||||
}
|
||||
else if (pixelType == typeof(L8) && !loadParams.Srgb)
|
||||
{
|
||||
DebugTools.Assert(_hasGLTextureSwizzle);
|
||||
|
||||
// Can only use R8 for L8 if sRGB is OFF.
|
||||
// Because OpenGL doesn't provide sRGB single/dual channel image formats.
|
||||
// Vulkan when?
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.Red);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.Red);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.Red);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.One);
|
||||
CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Unable to handle pixel type '{pixelType.Name}'");
|
||||
}
|
||||
|
||||
var pressureEst = EstPixelSize(pif) * width * height;
|
||||
|
||||
return GenTexture(texture, (width, height), isActuallySrgb, name, texPixType, pressureEst);
|
||||
}
|
||||
|
||||
private void ApplySampleParameters(TextureSampleParameters? sampleParameters)
|
||||
@@ -201,10 +247,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
private ClydeTexture GenTexture(GLHandle glHandle, Vector2i size, bool srgb, string? name, long memoryPressure=0)
|
||||
private (PIF pif, PF pf, PT pt) PixelEnums<T>(bool srgb)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
// Note that if _hasGLSrgb is off, we import an sRGB texture as non-sRGB.
|
||||
// Shaders are expected to compensate for this
|
||||
Rgba32 => (srgb && _hasGLSrgb ? PIF.Srgb8Alpha8 : PIF.Rgba8, PF.Rgba, PT.UnsignedByte),
|
||||
A8 or L8 => (PIF.R8, PF.Red, PT.UnsignedByte),
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
}
|
||||
|
||||
private ClydeTexture GenTexture(
|
||||
GLHandle glHandle,
|
||||
Vector2i size,
|
||||
bool srgb,
|
||||
string? name,
|
||||
TexturePixelType pixType,
|
||||
long memoryPressure = 0)
|
||||
{
|
||||
if (name != null)
|
||||
{
|
||||
@@ -222,7 +288,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Height = height,
|
||||
IsSrgb = srgb,
|
||||
Name = name,
|
||||
MemoryPressure = memoryPressure
|
||||
MemoryPressure = memoryPressure,
|
||||
TexturePixelType = pixType
|
||||
// TextureInstance = new WeakReference<ClydeTexture>(instance)
|
||||
};
|
||||
|
||||
@@ -246,6 +313,97 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//GC.RemoveMemoryPressure(loadedTexture.MemoryPressure);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImage<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Image<T> srcImage,
|
||||
in UIBox2i srcBox)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (!_hasGLTextureSwizzle)
|
||||
{
|
||||
if (typeof(T) == typeof(A8))
|
||||
{
|
||||
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(L8))
|
||||
{
|
||||
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
|
||||
}
|
||||
}
|
||||
|
||||
var loaded = _loadedTextures[texture.TextureId];
|
||||
|
||||
var pixType = GetTexturePixelType<T>();
|
||||
|
||||
if (pixType != loaded.TexturePixelType)
|
||||
{
|
||||
if (loaded.TexturePixelType == TexturePixelType.RenderTarget)
|
||||
throw new InvalidOperationException("Cannot modify texture for render target directly.");
|
||||
|
||||
throw new InvalidOperationException("Mismatching pixel type for texture.");
|
||||
}
|
||||
|
||||
if (loaded.Width < dstTl.X + srcBox.Width || loaded.Height < dstTl.Y + srcBox.Height)
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Destination rectangle out of bounds.");
|
||||
|
||||
if (srcBox.Left < 0 || srcBox.Top < 0 || srcBox.Right > srcImage.Width || srcBox.Bottom > srcImage.Height)
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
// sRGB doesn't matter since that only changes the internalFormat, which we don't need here.
|
||||
var (_, pf, pt) = PixelEnums<T>(srgb: false);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var size = srcBox.Width * srcBox.Height;
|
||||
|
||||
var copyBuffer = size < 16 * 16 ? stackalloc T[size] : new T[size];
|
||||
|
||||
for (var y = 0; y < srcBox.Height; y++)
|
||||
for (var x = 0; x < srcBox.Width; x++)
|
||||
{
|
||||
copyBuffer[(srcBox.Height - y - 1) * srcBox.Width + x] = srcImage[x + srcBox.Left, srcBox.Top + y];
|
||||
}
|
||||
|
||||
fixed (T* aPtr = copyBuffer)
|
||||
{
|
||||
var dstY = loaded.Height - dstTl.Y - srcBox.Height;
|
||||
GL.TexSubImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
0,
|
||||
dstTl.X, dstY,
|
||||
srcBox.Width, srcBox.Height,
|
||||
pf, pt,
|
||||
(IntPtr) aPtr);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private static TexturePixelType GetTexturePixelType<T>() where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
Rgba32 => TexturePixelType.Rgba32,
|
||||
L8 => TexturePixelType.L8,
|
||||
A8 => TexturePixelType.A8,
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
}
|
||||
|
||||
private void LoadStockTextures()
|
||||
{
|
||||
var white = new Image<Rgba32>(1, 1);
|
||||
@@ -350,10 +508,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsSrgb;
|
||||
public string? Name;
|
||||
public long MemoryPressure;
|
||||
public TexturePixelType TexturePixelType;
|
||||
|
||||
public Vector2i Size => (Width, Height);
|
||||
// public WeakReference<ClydeTexture> TextureInstance;
|
||||
}
|
||||
|
||||
private enum TexturePixelType : byte
|
||||
{
|
||||
RenderTarget = 0,
|
||||
Rgba32,
|
||||
A8,
|
||||
L8,
|
||||
}
|
||||
|
||||
private void FlushTextureDispose()
|
||||
{
|
||||
while (_textureDisposeQueue.TryDequeue(out var handle))
|
||||
@@ -369,6 +537,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal ClydeHandle TextureId { get; }
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
{
|
||||
_clyde.SetSubImage(this, topLeft, sourceImage, sourceRegion);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
||||
@@ -63,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private GLShaderProgram? _currentProgram;
|
||||
|
||||
private int _lightmapDivider = 2;
|
||||
private int _maxLightsPerScene = 128;
|
||||
private bool _enableSoftShadows = true;
|
||||
|
||||
private bool _checkGLErrors;
|
||||
@@ -133,6 +134,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
base.ReadConfig();
|
||||
_lightmapDivider = _configurationManager.GetCVar(CVars.DisplayLightMapDivider);
|
||||
_maxLightsPerScene = _configurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
|
||||
_enableSoftShadows = _configurationManager.GetCVar(CVars.DisplaySoftShadows);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public string GetKeyName(Keyboard.Key key) => string.Empty;
|
||||
public string GetKeyNameScanCode(int scanCode) => string.Empty;
|
||||
public int GetKeyScanCode(Keyboard.Key key) => default;
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
// Nada.
|
||||
@@ -77,8 +78,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public override event Action<WindowResizedEventArgs> OnWindowResized
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Render()
|
||||
@@ -111,6 +112,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new DummyTexture((image.Width, image.Height));
|
||||
}
|
||||
|
||||
public OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return new DummyTexture(size);
|
||||
}
|
||||
|
||||
public IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null)
|
||||
{
|
||||
@@ -174,7 +184,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return DummyAudioSource.Instance;
|
||||
}
|
||||
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
@@ -296,6 +306,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public DummyTexture(Vector2i size) : base(size)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
{
|
||||
// Just do nothing on mutate.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyShaderInstance : ShaderInstance
|
||||
@@ -434,7 +449,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
public IRenderTexture RenderTarget { get; } = new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
public IRenderTexture RenderTarget { get; } =
|
||||
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
|
||||
public IEye? Eye { get; set; }
|
||||
public Vector2i Size { get; }
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
|
||||
// xy: A, zw: B
|
||||
varying highp vec4 fragPos;
|
||||
// x: actual angle, y: horizontal (1) / vertical (-1)
|
||||
varying highp vec2 fragAngle;
|
||||
// x: Angle being queried, y: Angle of closest point of line (is of 90-degree angle to line angle), z: Distance at y
|
||||
varying highp vec3 fragControl;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Stuff that needs to be inferred to avoid interpolation issues.
|
||||
highp vec2 rayNormal = vec2(cos(fragAngle.x), -sin(fragAngle.x));
|
||||
|
||||
// Depth calculation accounting for interpolation.
|
||||
highp float dist;
|
||||
|
||||
if (fragAngle.y > 0.0) {
|
||||
// Line is horizontal
|
||||
dist = abs(fragPos.y / rayNormal.y);
|
||||
} else {
|
||||
// Line is vertical
|
||||
dist = abs(fragPos.x / rayNormal.x);
|
||||
}
|
||||
// Thanks to Radrark for finding this for me. There's also a useful diagram, but this is text, so:
|
||||
// r = p / cos(theta - phi)
|
||||
// r: Distance to line *given angle theta*
|
||||
// p: Distance to closest point of line
|
||||
// theta: Angle being queried
|
||||
// phi: Angle of closest point of line - inherently on 90-degree angle to line angle
|
||||
highp float dist = abs(fragControl.z / cos(fragControl.x - fragControl.y));
|
||||
|
||||
// Main body.
|
||||
#ifdef HAS_DFDX
|
||||
|
||||
@@ -11,10 +11,8 @@ attribute vec4 aPos;
|
||||
// x: deflection(0=A/1=B) y: height
|
||||
attribute vec2 subVertex;
|
||||
|
||||
// xy: A, zw: B
|
||||
varying vec4 fragPos;
|
||||
// x: actual angle, y: horizontal (1) / vertical (-1)
|
||||
varying vec2 fragAngle;
|
||||
// x: actual angle, y: line angle + 90 degrees, z: Distance at y
|
||||
varying vec3 fragControl;
|
||||
|
||||
// Note: This is *not* the standard projectionMatrix!
|
||||
uniform vec2 shadowLightCentre;
|
||||
@@ -70,8 +68,19 @@ void main()
|
||||
}
|
||||
}
|
||||
|
||||
fragPos = vec4(pA, pB);
|
||||
fragAngle = vec2(mix(xA, xB, subVertex.x), abs(pA.x - pB.x) - abs(pA.y - pB.y));
|
||||
float targetAngle = mix(xA, xB, subVertex.x);
|
||||
|
||||
// Calculate the necessary control data for the fragment shader.
|
||||
vec2 lineNormal = pB - pA; // hypothetical: <- would have negative X, zero Y
|
||||
lineNormal /= length(lineNormal);
|
||||
fragControl = vec3(
|
||||
// Angle
|
||||
targetAngle,
|
||||
// Angle Out
|
||||
atan(lineNormal.x, lineNormal.y),
|
||||
// Distance @ Angle Out
|
||||
dot(vec2(lineNormal.y, -lineNormal.x), pA)
|
||||
);
|
||||
|
||||
// Depth divide MUST be implemented here no matter what,
|
||||
// because GLES SL 1.00 doesn't have gl_FragDepth.
|
||||
@@ -79,5 +88,5 @@ void main()
|
||||
// and we don't really need to have correction
|
||||
float zbufferDepth = 1.0 - (1.0 / (length(mix(pA, pB, subVertex.x)) + DEPTH_ZBUFFER_PREDIV_BIAS));
|
||||
|
||||
gl_Position = vec4(mix(xA, xB, subVertex.x) / PI, mix(1.0, -1.0, subVertex.y), zbufferDepth, 1.0);
|
||||
gl_Position = vec4(targetAngle / PI, mix(1.0, -1.0, subVertex.y), zbufferDepth, 1.0);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Robust.Client.Graphics
|
||||
_configurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
|
||||
return true;
|
||||
@@ -76,6 +77,10 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void MaxLightsPerSceneChanged(int newValue)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void SoftShadowsChanged(bool newValue)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
@@ -17,7 +18,11 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
internal sealed class FontManager : IFontManagerInternal
|
||||
{
|
||||
private const int SheetWidth = 256;
|
||||
private const int SheetHeight = 256;
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
private uint BaseFontDPI;
|
||||
|
||||
@@ -51,8 +56,7 @@ namespace Robust.Client.Graphics
|
||||
return instance;
|
||||
}
|
||||
|
||||
var glyphMap = _generateGlyphMap(fontFaceHandle.Face);
|
||||
instance = new FontInstanceHandle(this, size, glyphMap, fontFaceHandle);
|
||||
instance = new FontInstanceHandle(this, size, fontFaceHandle);
|
||||
|
||||
_loadedInstances.Add((fontFaceHandle, size), instance);
|
||||
return instance;
|
||||
@@ -67,135 +71,128 @@ namespace Robust.Client.Graphics
|
||||
var descent = -ftFace.Size.Metrics.Descender.ToInt32();
|
||||
var lineHeight = ftFace.Size.Metrics.Height.ToInt32();
|
||||
|
||||
var (atlas, metricsMap) = _generateAtlas(instance, scale);
|
||||
var data = new ScaledFontData(ascent, descent, ascent + descent, lineHeight);
|
||||
|
||||
return new ScaledFontData(metricsMap, ascent, descent, ascent + descent, lineHeight, atlas);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private (FontTextureAtlas, Dictionary<uint, CharMetrics> metricsMap)
|
||||
_generateAtlas(FontInstanceHandle instance, float scale)
|
||||
private void CacheGlyph(FontInstanceHandle instance, ScaledFontData scaled, float scale, uint glyph)
|
||||
{
|
||||
// TODO: This could use a better box packing algorithm.
|
||||
// Right now we treat each glyph bitmap as having the max size among all glyphs.
|
||||
// So we can divide the atlas into equal-size rectangles.
|
||||
// This wastes a lot of space though because there's a lot of tiny glyphs.
|
||||
// Check if already cached.
|
||||
if (scaled.AtlasData.ContainsKey(glyph))
|
||||
return;
|
||||
|
||||
var face = instance.FaceHandle.Face;
|
||||
face.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var maxGlyphSize = Vector2i.Zero;
|
||||
var count = 0;
|
||||
var glyphMetrics = face.Glyph.Metrics;
|
||||
var metrics = new CharMetrics(glyphMetrics.HorizontalBearingX.ToInt32(),
|
||||
glyphMetrics.HorizontalBearingY.ToInt32(),
|
||||
glyphMetrics.HorizontalAdvance.ToInt32(),
|
||||
glyphMetrics.Width.ToInt32(),
|
||||
glyphMetrics.Height.ToInt32());
|
||||
|
||||
var metricsMap = new Dictionary<uint, CharMetrics>();
|
||||
|
||||
foreach (var glyph in instance.GlyphMap.Values)
|
||||
using var bitmap = face.Glyph.Bitmap;
|
||||
if (bitmap.Pitch < 0)
|
||||
{
|
||||
if (metricsMap.ContainsKey(glyph))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var glyphMetrics = face.Glyph.Metrics;
|
||||
var metrics = new CharMetrics(glyphMetrics.HorizontalBearingX.ToInt32(),
|
||||
glyphMetrics.HorizontalBearingY.ToInt32(),
|
||||
glyphMetrics.HorizontalAdvance.ToInt32(),
|
||||
glyphMetrics.Width.ToInt32(),
|
||||
glyphMetrics.Height.ToInt32());
|
||||
metricsMap.Add(glyph, metrics);
|
||||
|
||||
maxGlyphSize = Vector2i.ComponentMax(maxGlyphSize,
|
||||
new Vector2i(face.Glyph.Bitmap.Width, face.Glyph.Bitmap.Rows));
|
||||
|
||||
count += 1;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Make atlas.
|
||||
// This is the same algorithm used for RSIs. Tries to keep width and height as close as possible,
|
||||
// but preferring to increase width if necessary.
|
||||
var atlasEntriesHorizontal = (int) Math.Ceiling(Math.Sqrt(count));
|
||||
var atlasEntriesVertical =
|
||||
(int) Math.Ceiling(count / (float) atlasEntriesHorizontal);
|
||||
var atlasDimX =
|
||||
(int) Math.Ceiling(atlasEntriesHorizontal * maxGlyphSize.X / 4f) * 4;
|
||||
var atlasDimY =
|
||||
(int) Math.Ceiling(atlasEntriesVertical * maxGlyphSize.Y / 4f) * 4;
|
||||
|
||||
using (var atlas = new Image<A8>(atlasDimX, atlasDimY))
|
||||
if (bitmap.Pitch != 0)
|
||||
{
|
||||
var atlasRegions = new Dictionary<uint, UIBox2>();
|
||||
count = 0;
|
||||
foreach (var glyph in metricsMap.Keys)
|
||||
Image<A8> img;
|
||||
switch (bitmap.PixelMode)
|
||||
{
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var bitmap = face.Glyph.Bitmap;
|
||||
if (bitmap.Pitch == 0)
|
||||
case PixelMode.Mono:
|
||||
{
|
||||
count += 1;
|
||||
continue;
|
||||
img = MonoBitMapToImage(bitmap);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bitmap.Pitch < 0)
|
||||
case PixelMode.Gray:
|
||||
{
|
||||
ReadOnlySpan<A8> span;
|
||||
unsafe
|
||||
{
|
||||
span = new ReadOnlySpan<A8>((void*) bitmap.Buffer, bitmap.Pitch * bitmap.Rows);
|
||||
}
|
||||
|
||||
img = new Image<A8>(bitmap.Width, bitmap.Rows);
|
||||
|
||||
span.Blit(
|
||||
bitmap.Pitch,
|
||||
UIBox2i.FromDimensions(0, 0, bitmap.Pitch, bitmap.Rows),
|
||||
img,
|
||||
(0, 0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PixelMode.Gray2:
|
||||
case PixelMode.Gray4:
|
||||
case PixelMode.Lcd:
|
||||
case PixelMode.VerticalLcd:
|
||||
case PixelMode.Bgra:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var column = count % atlasEntriesHorizontal;
|
||||
var row = count / atlasEntriesVertical;
|
||||
var offsetX = column * maxGlyphSize.X;
|
||||
var offsetY = row * maxGlyphSize.Y;
|
||||
count += 1;
|
||||
atlasRegions.Add(glyph, UIBox2i.FromDimensions(offsetX, offsetY, bitmap.Width, bitmap.Rows));
|
||||
|
||||
switch (bitmap.PixelMode)
|
||||
{
|
||||
case PixelMode.Mono:
|
||||
{
|
||||
using (var bitmapImage = MonoBitMapToImage(bitmap))
|
||||
{
|
||||
bitmapImage.Blit(new UIBox2i(0, 0, bitmapImage.Width, bitmapImage.Height), atlas,
|
||||
(offsetX, offsetY));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PixelMode.Gray:
|
||||
{
|
||||
ReadOnlySpan<A8> span;
|
||||
unsafe
|
||||
{
|
||||
span = new ReadOnlySpan<A8>((void*) bitmap.Buffer, bitmap.Pitch * bitmap.Rows);
|
||||
}
|
||||
|
||||
span.Blit(bitmap.Pitch, UIBox2i.FromDimensions(0, 0, bitmap.Pitch, bitmap.Rows), atlas,
|
||||
(offsetX, offsetY));
|
||||
break;
|
||||
}
|
||||
|
||||
case PixelMode.Gray2:
|
||||
case PixelMode.Gray4:
|
||||
case PixelMode.Lcd:
|
||||
case PixelMode.VerticalLcd:
|
||||
case PixelMode.Bgra:
|
||||
throw new NotImplementedException();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var atlasDictionary = new Dictionary<uint, AtlasTexture>();
|
||||
var texture = Texture.LoadFromImage(atlas, $"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}");
|
||||
OwnedTexture sheet;
|
||||
if (scaled.AtlasTextures.Count == 0)
|
||||
sheet = GenSheet();
|
||||
else
|
||||
sheet = scaled.AtlasTextures[^1];
|
||||
|
||||
foreach (var (glyph, region) in atlasRegions)
|
||||
var (sheetW, sheetH) = sheet.Size;
|
||||
|
||||
if (sheetW - scaled.CurSheetX < img.Width)
|
||||
{
|
||||
atlasDictionary.Add(glyph, new AtlasTexture(texture, region));
|
||||
scaled.CurSheetX = 0;
|
||||
scaled.CurSheetY = scaled.CurSheetMaxY;
|
||||
}
|
||||
|
||||
return (new FontTextureAtlas(texture, atlasDictionary), metricsMap);
|
||||
if (sheetH - scaled.CurSheetY < img.Height)
|
||||
{
|
||||
// Make new sheet.
|
||||
scaled.CurSheetY = 0;
|
||||
scaled.CurSheetX = 0;
|
||||
scaled.CurSheetMaxY = 0;
|
||||
|
||||
sheet = GenSheet();
|
||||
}
|
||||
|
||||
sheet.SetSubImage((scaled.CurSheetX, scaled.CurSheetY), img);
|
||||
|
||||
var atlasTexture = new AtlasTexture(
|
||||
sheet,
|
||||
UIBox2.FromDimensions(
|
||||
scaled.CurSheetX,
|
||||
scaled.CurSheetY,
|
||||
bitmap.Width,
|
||||
bitmap.Rows));
|
||||
|
||||
scaled.AtlasData.Add(glyph, atlasTexture);
|
||||
|
||||
scaled.CurSheetMaxY = Math.Max(scaled.CurSheetMaxY, scaled.CurSheetY + bitmap.Rows);
|
||||
scaled.CurSheetX += bitmap.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
scaled.AtlasData.Add(glyph, null);
|
||||
}
|
||||
|
||||
scaled.MetricsMap.Add(glyph, metrics);
|
||||
|
||||
OwnedTexture GenSheet()
|
||||
{
|
||||
var sheet = _clyde.CreateBlankTexture<A8>((SheetWidth, SheetHeight),
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
scaled.AtlasTextures.Add(sheet);
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,46 +223,6 @@ namespace Robust.Client.Graphics
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
private Dictionary<char, uint> _generateGlyphMap(Face face)
|
||||
{
|
||||
var map = new Dictionary<char, uint>();
|
||||
|
||||
// TODO: Render more than extended ASCII, Cyrillic and Greek. somehow.
|
||||
// Does it make sense to just render every glyph in the font?
|
||||
|
||||
// Render all the extended ASCII characters.
|
||||
// Yeah I know "extended ASCII" isn't a real thing get off my back.
|
||||
for (var i = 32u; i <= 255; i++)
|
||||
{
|
||||
_addGlyph(i, face, map);
|
||||
}
|
||||
|
||||
// Render basic cyrillic.
|
||||
for (var i = 0x0410u; i <= 0x044F; i++)
|
||||
{
|
||||
_addGlyph(i, face, map);
|
||||
}
|
||||
|
||||
// Render greek.
|
||||
for (var i = 0x03B1u; i <= 0x03C9; i++)
|
||||
{
|
||||
_addGlyph(i, face, map);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void _addGlyph(uint codePoint, Face face, Dictionary<char, uint> map)
|
||||
{
|
||||
var glyphIndex = face.GetCharIndex(codePoint);
|
||||
if (glyphIndex == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map.Add((char) codePoint, glyphIndex);
|
||||
}
|
||||
|
||||
private class FontFaceHandle : IFontFaceHandle
|
||||
{
|
||||
public Face Face { get; }
|
||||
@@ -282,78 +239,84 @@ namespace Robust.Client.Graphics
|
||||
public FontFaceHandle FaceHandle { get; }
|
||||
public int Size { get; }
|
||||
private readonly Dictionary<float, ScaledFontData> _scaledData = new();
|
||||
public readonly IReadOnlyDictionary<char, uint> GlyphMap;
|
||||
private readonly FontManager _fontManager;
|
||||
public readonly Dictionary<Rune, uint> GlyphMap;
|
||||
|
||||
public FontInstanceHandle(FontManager fontManager, int size, IReadOnlyDictionary<char, uint> glyphMap,
|
||||
FontFaceHandle faceHandle)
|
||||
public FontInstanceHandle(FontManager fontManager, int size, FontFaceHandle faceHandle)
|
||||
{
|
||||
GlyphMap = new Dictionary<Rune, uint>();
|
||||
_fontManager = fontManager;
|
||||
Size = size;
|
||||
GlyphMap = glyphMap;
|
||||
FaceHandle = faceHandle;
|
||||
}
|
||||
|
||||
public Texture? GetCharTexture(char chr, float scale)
|
||||
public Texture? GetCharTexture(Rune codePoint, float scale)
|
||||
{
|
||||
var glyph = _getGlyph(chr);
|
||||
var glyph = GetGlyph(codePoint);
|
||||
if (glyph == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var scaled = _getScaleDatum(scale);
|
||||
scaled.Atlas.AtlasData.TryGetValue(glyph, out var texture);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
_fontManager.CacheGlyph(this, scaled, scale, glyph);
|
||||
|
||||
scaled.AtlasData.TryGetValue(glyph, out var texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public CharMetrics? GetCharMetrics(char chr, float scale)
|
||||
public CharMetrics? GetCharMetrics(Rune codePoint, float scale)
|
||||
{
|
||||
var glyph = _getGlyph(chr);
|
||||
var glyph = GetGlyph(codePoint);
|
||||
if (glyph == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
_fontManager.CacheGlyph(this, scaled, scale, glyph);
|
||||
|
||||
return scaled.MetricsMap[glyph];
|
||||
}
|
||||
|
||||
public int GetAscent(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.Ascent;
|
||||
}
|
||||
|
||||
public int GetDescent(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.Descent;
|
||||
}
|
||||
|
||||
public int GetHeight(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.Height;
|
||||
}
|
||||
|
||||
public int GetLineHeight(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.LineHeight;
|
||||
}
|
||||
|
||||
private uint _getGlyph(char chr)
|
||||
private uint GetGlyph(Rune chr)
|
||||
{
|
||||
if (GlyphMap.TryGetValue(chr, out var glyph))
|
||||
{
|
||||
return glyph;
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Check FreeType to see if it exists.
|
||||
var index = FaceHandle.Face.GetCharIndex((uint) chr.Value);
|
||||
|
||||
GlyphMap.Add(chr, index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private ScaledFontData _getScaleDatum(float scale)
|
||||
private ScaledFontData GetScaleDatum(float scale)
|
||||
{
|
||||
if (_scaledData.TryGetValue(scale, out var datum))
|
||||
{
|
||||
@@ -368,37 +331,25 @@ namespace Robust.Client.Graphics
|
||||
|
||||
private class ScaledFontData
|
||||
{
|
||||
public ScaledFontData(IReadOnlyDictionary<uint, CharMetrics> metricsMap, int ascent, int descent,
|
||||
int height, int lineHeight, FontTextureAtlas atlas)
|
||||
public ScaledFontData(int ascent, int descent, int height, int lineHeight)
|
||||
{
|
||||
MetricsMap = metricsMap;
|
||||
Ascent = ascent;
|
||||
Descent = descent;
|
||||
Height = height;
|
||||
LineHeight = lineHeight;
|
||||
Atlas = atlas;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<uint, CharMetrics> MetricsMap { get; }
|
||||
public int Ascent { get; }
|
||||
public int Descent { get; }
|
||||
public int Height { get; }
|
||||
public int LineHeight { get; }
|
||||
public FontTextureAtlas Atlas { get; }
|
||||
}
|
||||
public readonly List<OwnedTexture> AtlasTextures = new();
|
||||
public readonly Dictionary<uint, AtlasTexture?> AtlasData = new();
|
||||
public readonly Dictionary<uint, CharMetrics> MetricsMap = new();
|
||||
public readonly int Ascent;
|
||||
public readonly int Descent;
|
||||
public readonly int Height;
|
||||
public readonly int LineHeight;
|
||||
|
||||
private class FontTextureAtlas
|
||||
{
|
||||
public FontTextureAtlas(Texture mainTexture, Dictionary<uint, AtlasTexture> atlasData)
|
||||
{
|
||||
MainTexture = mainTexture;
|
||||
AtlasData = atlasData;
|
||||
}
|
||||
|
||||
public Texture MainTexture { get; }
|
||||
|
||||
// Maps glyph index to atlas.
|
||||
public Dictionary<uint, AtlasTexture> AtlasData { get; }
|
||||
public int CurSheetX;
|
||||
public int CurSheetY;
|
||||
public int CurSheetMaxY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Robust.Client/Graphics/IRsiStateLike.cs
Normal file
12
Robust.Client/Graphics/IRsiStateLike.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public interface IRsiStateLike : IDirectionalTextureProvider
|
||||
{
|
||||
RSI.State.DirectionType Directions { get; }
|
||||
bool IsAnimated { get; }
|
||||
int AnimationFrameCount { get; }
|
||||
|
||||
float GetDelay(int frame);
|
||||
Texture GetFrame(RSI.State.Direction dir, int frame);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.Interfaces.Graphics.Lighting;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Lighting
|
||||
{
|
||||
@@ -7,5 +8,8 @@ namespace Robust.Client.Graphics.Lighting
|
||||
public bool Enabled { get; set; } = true;
|
||||
public bool DrawShadows { get; set; } = true;
|
||||
public bool DrawHardFov { get; set; } = true;
|
||||
public bool DrawLighting { get; set; } = true;
|
||||
public bool LockConsoleAccess { get; set; } = false;
|
||||
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -12,21 +14,42 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a sub area of the texture with new data.
|
||||
/// </summary>
|
||||
/// <param name="topLeft">The top left corner of the area to modify.</param>
|
||||
/// <param name="sourceImage">The image from which to copy pixel data.</param>
|
||||
/// <param name="sourceRegion">The rectangle inside <paramref name="sourceImage"/> from which to copy.</param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of pixels being used.
|
||||
/// This must match the type used when creating the texture.
|
||||
/// </typeparam>
|
||||
public abstract void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
where T : unmanaged, IPixel<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a sub area of the texture with new data.
|
||||
/// </summary>
|
||||
/// <param name="topLeft">The top left corner of the area to modify.</param>
|
||||
/// <param name="sourceImage">The image to paste onto the texture.</param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of pixels being used.
|
||||
/// This must match the type used when creating the texture.
|
||||
/// </typeparam>
|
||||
public void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
SetSubImage(topLeft, sourceImage, UIBox2i.FromDimensions(0, 0, sourceImage.Width, sourceImage.Height));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[Obsolete("Use Dispose() instead")]
|
||||
public void Delete()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~OwnedTexture()
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Robust.Client.Graphics
|
||||
/// RSIs are folded into a single set of animation timings when loaded.
|
||||
/// This is to simplify animation playback code in-engine.
|
||||
/// </remarks>
|
||||
public sealed class State : IDirectionalTextureProvider
|
||||
public sealed class State : IRsiStateLike
|
||||
{
|
||||
// List of delays for the frame to reach the next frame.
|
||||
private readonly float[] Delays;
|
||||
@@ -80,6 +80,8 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public bool IsAnimated => DelayCount > 1;
|
||||
|
||||
int IRsiStateLike.AnimationFrameCount => DelayCount;
|
||||
|
||||
public Texture GetFrame(Direction direction, int frame)
|
||||
{
|
||||
return Icons[(int) direction][frame];
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name","directions"] //'delays' is marked as optional in the spec
|
||||
"required": ["name"] //'delays' is marked as optional in the spec
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Graphics
|
||||
/// Contains a texture used for drawing things.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public abstract class Texture : IDirectionalTextureProvider
|
||||
public abstract class Texture : IRsiStateLike
|
||||
{
|
||||
/// <summary>
|
||||
/// The width of the texture, in pixels.
|
||||
@@ -85,6 +85,26 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
RSI.State.DirectionType IRsiStateLike.Directions => RSI.State.DirectionType.Dir1;
|
||||
bool IRsiStateLike.IsAnimated => false;
|
||||
int IRsiStateLike.AnimationFrameCount => 0;
|
||||
|
||||
float IRsiStateLike.GetDelay(int frame)
|
||||
{
|
||||
if (frame != 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Texture IRsiStateLike.GetFrame(RSI.State.Direction dir, int frame)
|
||||
{
|
||||
if (frame != 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -7,9 +7,9 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Client.Interfaces.UserInterface;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
@@ -134,7 +134,8 @@ namespace Robust.Client.Input
|
||||
Priority = p.Priority,
|
||||
Type = p.BindingType,
|
||||
CanFocus = p.CanFocus,
|
||||
CanRepeat = p.CanRepeat
|
||||
CanRepeat = p.CanRepeat,
|
||||
AllowSubCombs = p.AllowSubCombs
|
||||
}).ToArray();
|
||||
|
||||
var leaveEmpty = _modifiedKeyFunctions
|
||||
@@ -203,6 +204,7 @@ namespace Robust.Client.Input
|
||||
|
||||
var bindsDown = new List<KeyBinding>();
|
||||
var hasCanFocus = false;
|
||||
var hasAllowSubCombs = false;
|
||||
|
||||
// bindings are ordered with larger combos before single key bindings so combos have priority.
|
||||
foreach (var binding in _bindings)
|
||||
@@ -221,12 +223,22 @@ namespace Robust.Client.Input
|
||||
matchedCombo = binding.PackedKeyCombo;
|
||||
|
||||
bindsDown.Add(binding);
|
||||
|
||||
hasCanFocus |= binding.CanFocus;
|
||||
hasAllowSubCombs |= binding.AllowSubCombs;
|
||||
|
||||
}
|
||||
else if (PackedIsSubPattern(matchedCombo, binding.PackedKeyCombo))
|
||||
{
|
||||
// kill any lower level matches
|
||||
UpBind(binding);
|
||||
if (hasAllowSubCombs)
|
||||
{
|
||||
bindsDown.Add(binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
// kill any lower level matches
|
||||
UpBind(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,8 +390,8 @@ namespace Robust.Client.Input
|
||||
{
|
||||
for (var i = 0; i < 32; i += 8)
|
||||
{
|
||||
var key = (Key) (subPackedCombo.Packed >> i);
|
||||
if (!PackedContainsKey(packedCombo, key))
|
||||
var key = (Key) ((subPackedCombo.Packed >> i) & 0b_1111_1111);
|
||||
if (key != Key.Unknown && !PackedContainsKey(packedCombo, key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -453,7 +465,7 @@ namespace Robust.Client.Input
|
||||
public IKeyBinding RegisterBinding(BoundKeyFunction function, KeyBindingType bindingType,
|
||||
Key baseKey, Key? mod1, Key? mod2, Key? mod3)
|
||||
{
|
||||
var binding = new KeyBinding(this, function, bindingType, baseKey, false, false,
|
||||
var binding = new KeyBinding(this, function, bindingType, baseKey, false, false, false,
|
||||
0, mod1 ?? Key.Unknown, mod2 ?? Key.Unknown, mod3 ?? Key.Unknown);
|
||||
|
||||
RegisterBinding(binding);
|
||||
@@ -464,7 +476,7 @@ namespace Robust.Client.Input
|
||||
public IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified = true)
|
||||
{
|
||||
var binding = new KeyBinding(this, reg.Function, reg.Type, reg.BaseKey, reg.CanFocus, reg.CanRepeat,
|
||||
reg.Priority, reg.Mod1, reg.Mod2, reg.Mod3);
|
||||
reg.AllowSubCombs, reg.Priority, reg.Mod1, reg.Mod2, reg.Mod3);
|
||||
|
||||
RegisterBinding(binding, markModified);
|
||||
|
||||
@@ -619,12 +631,18 @@ namespace Robust.Client.Input
|
||||
[ViewVariables]
|
||||
public bool CanRepeat { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Bound Key Combination allows Sub Combinations of it to trigger.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool AllowSubCombs { get; internal set; }
|
||||
|
||||
[ViewVariables] public int Priority { get; internal set; }
|
||||
|
||||
public KeyBinding(InputManager inputManager, BoundKeyFunction function,
|
||||
KeyBindingType bindingType,
|
||||
Key baseKey,
|
||||
bool canFocus, bool canRepeat, int priority, Key mod1 = Key.Unknown,
|
||||
bool canFocus, bool canRepeat, bool allowSubCombs, int priority, Key mod1 = Key.Unknown,
|
||||
Key mod2 = Key.Unknown,
|
||||
Key mod3 = Key.Unknown)
|
||||
{
|
||||
@@ -632,6 +650,7 @@ namespace Robust.Client.Input
|
||||
BindingType = bindingType;
|
||||
CanFocus = canFocus;
|
||||
CanRepeat = canRepeat;
|
||||
AllowSubCombs = allowSubCombs;
|
||||
Priority = priority;
|
||||
_inputManager = inputManager;
|
||||
|
||||
@@ -793,34 +812,34 @@ namespace Robust.Client.Input
|
||||
public string Description => "Binds an input key to an input command.";
|
||||
public string Help => "bind <KeyName> <BindMode> <InputCommand>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 3)
|
||||
{
|
||||
console.AddLine("Too few arguments.");
|
||||
return false;
|
||||
shell.WriteLine("Too few arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 3)
|
||||
{
|
||||
console.AddLine("Too many arguments.");
|
||||
return false;
|
||||
shell.WriteLine("Too many arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
var keyName = args[0];
|
||||
|
||||
if (!Enum.TryParse(typeof(Key), keyName, true, out var keyIdObj))
|
||||
{
|
||||
console.AddLine($"Key '{keyName}' is unrecognized.");
|
||||
return false;
|
||||
shell.WriteLine($"Key '{keyName}' is unrecognized.");
|
||||
return;
|
||||
}
|
||||
|
||||
var keyId = (Key) keyIdObj!;
|
||||
|
||||
if (!Enum.TryParse(typeof(KeyBindingType), args[1], true, out var keyModeObj))
|
||||
{
|
||||
console.AddLine($"BindMode '{args[1]}' is unrecognized.");
|
||||
return false;
|
||||
shell.WriteLine($"BindMode '{args[1]}' is unrecognized.");
|
||||
return;
|
||||
}
|
||||
|
||||
var keyMode = (KeyBindingType) keyModeObj!;
|
||||
@@ -837,8 +856,6 @@ namespace Robust.Client.Input
|
||||
};
|
||||
|
||||
inputMan.RegisterBinding(registration);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,12 +866,10 @@ namespace Robust.Client.Input
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IInputManager>()
|
||||
.SaveToUserData();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.Interfaces.Console
|
||||
{
|
||||
public interface IConsoleCommand : ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes the command
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the command should also be forwarded to the server. True to allow forwarding, false to block.</returns>
|
||||
bool Execute(IDebugConsole console, params string[] args);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Interfaces.Console
|
||||
{
|
||||
public interface IDebugConsole
|
||||
{
|
||||
IReadOnlyDictionary<string, IConsoleCommand> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Write a line with a specific color to the console window.
|
||||
/// </summary>
|
||||
void AddLine(string text, Color color);
|
||||
|
||||
void AddLine(string text);
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
|
||||
void Clear();
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,29 @@ namespace Robust.Client.Interfaces.Graphics
|
||||
Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
|
||||
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a blank texture of the specified parameters.
|
||||
/// This texture can later be modified using <see cref="OwnedTexture.SetSubImage{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="size">The size of the new texture, in pixels.</param>
|
||||
/// <param name="name">A name for the texture that can show up in debugging tools like renderdoc.</param>
|
||||
/// <param name="loadParams">
|
||||
/// Load parameters for the texture describing stuff such as sample mode.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of pixels to "store" in the texture.
|
||||
/// This is the same type you should pass to <see cref="OwnedTexture.SetSubImage{T}"/>,
|
||||
/// and also determines how the texture is stored internally.
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// An owned, mutable texture object.
|
||||
/// </returns>
|
||||
OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>;
|
||||
|
||||
IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Robust.Client.Interfaces.Graphics
|
||||
@@ -22,8 +23,13 @@ namespace Robust.Client.Interfaces.Graphics
|
||||
|
||||
internal interface IFontInstanceHandle
|
||||
{
|
||||
Texture? GetCharTexture(char chr, float scale);
|
||||
CharMetrics? GetCharMetrics(char chr, float scale);
|
||||
|
||||
|
||||
Texture? GetCharTexture(Rune codePoint, float scale);
|
||||
Texture? GetCharTexture(char chr, float scale) => GetCharTexture((Rune) chr, scale);
|
||||
CharMetrics? GetCharMetrics(Rune codePoint, float scale);
|
||||
CharMetrics? GetCharMetrics(char chr, float scale) => GetCharMetrics((Rune) chr, scale);
|
||||
|
||||
int GetAscent(float scale);
|
||||
int GetDescent(float scale);
|
||||
int GetHeight(float scale);
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Interfaces.Graphics.Lighting
|
||||
{
|
||||
public interface ILightManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables/disables the entire light manager.
|
||||
/// </summary>
|
||||
bool Enabled { get; set; }
|
||||
/// <summary>
|
||||
/// Enables/disables shadows, but lights are still functional.
|
||||
/// </summary>
|
||||
bool DrawShadows { get; set; }
|
||||
/// <summary>
|
||||
/// Enables/disables hard FOV.
|
||||
/// </summary>
|
||||
bool DrawHardFov { get; set; }
|
||||
/// <summary>
|
||||
/// Enables/disables everything to do with the lighting buffer, without interfering with hard FOV.
|
||||
/// </summary>
|
||||
bool DrawLighting { get; set; }
|
||||
/// <summary>
|
||||
/// This is useful to prevent players messing with lighting setup when they shouldn't.
|
||||
/// </summary>
|
||||
bool LockConsoleAccess { get; set; }
|
||||
/// <summary>
|
||||
/// Ambient light. This is in linear-light, i.e. when providing a fixed colour, you must use Color.FromSrgb(Color.Black)!
|
||||
/// </summary>
|
||||
Color AmbientLightColor { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Robust.Client.Interfaces.Input
|
||||
|
||||
bool CanFocus { get; }
|
||||
bool CanRepeat { get; }
|
||||
bool AllowSubCombs { get; }
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -16,8 +16,9 @@ namespace Robust.Client.Interfaces.Input
|
||||
public int Priority;
|
||||
public bool CanFocus;
|
||||
public bool CanRepeat;
|
||||
public bool AllowSubCombs;
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(ref Function, "function", default);
|
||||
serializer.DataField(ref Type, "type", KeyBindingType.State);
|
||||
@@ -28,6 +29,7 @@ namespace Robust.Client.Interfaces.Input
|
||||
serializer.DataField(ref Priority, "priority", 0);
|
||||
serializer.DataField(ref CanFocus, "canFocus", false);
|
||||
serializer.DataField(ref CanRepeat, "canRepeat", false);
|
||||
serializer.DataField(ref AllowSubCombs, "allowSubCombs", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,22 @@ namespace Robust.Client.Interfaces.UserInterface
|
||||
/// </summary>
|
||||
Stylesheet? Stylesheet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A control can have "keyboard focus" separate from ControlFocused, obtained when calling
|
||||
/// Control.GrabKeyboardFocus. Corresponding events in Control are KeyboardFocusEntered/Exited
|
||||
/// </summary>
|
||||
Control? KeyboardFocused { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A control gets "ControlFocused" when a mouse button (or any KeyBinding which has CanFocus = true) is
|
||||
/// pressed down on the control. While it is focused, it will receive mouse hover events and the corresponding
|
||||
/// keyup event if it still has focus when that occurs (it will NOT receive the keyup if focus has
|
||||
/// been taken by another control). Focus is removed when a different control takes focus
|
||||
/// (such as by pressing a different mouse button down over a different control) or when the keyup event
|
||||
/// happens. When focus is lost on a control, it always fires Control.ControlFocusExited.
|
||||
/// </summary>
|
||||
Control? ControlFocused { get; }
|
||||
|
||||
LayoutContainer StateRoot { get; }
|
||||
|
||||
LayoutContainer WindowRoot { get; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -12,9 +12,9 @@ namespace Robust.Client.Log
|
||||
/// </summary>
|
||||
class DebugConsoleLogHandler : ILogHandler
|
||||
{
|
||||
readonly IDebugConsole Console;
|
||||
readonly IClientConsoleHost Console;
|
||||
|
||||
public DebugConsoleLogHandler(IDebugConsole console)
|
||||
public DebugConsoleLogHandler(IClientConsoleHost console)
|
||||
{
|
||||
Console = console;
|
||||
}
|
||||
|
||||
@@ -98,9 +98,9 @@ namespace Robust.Client.Placement
|
||||
public bool Eraser { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture we use to show from our placement manager to represent the entity to place
|
||||
/// The texture we use to show from our placement manager to represent the entity to place
|
||||
/// </summary>
|
||||
public IDirectionalTextureProvider? CurrentBaseSprite { get; set; }
|
||||
public List<IDirectionalTextureProvider>? CurrentTextures { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which of the placement orientations we are trying to place with
|
||||
@@ -311,7 +311,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
PlacementChanged?.Invoke(this, EventArgs.Empty);
|
||||
Hijack = null;
|
||||
CurrentBaseSprite = null;
|
||||
CurrentTextures = null;
|
||||
CurrentPrototype = null;
|
||||
CurrentPermission = null;
|
||||
CurrentMode = null;
|
||||
@@ -555,7 +555,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
var prototype = _prototypeManager.Index<EntityPrototype>(templateName);
|
||||
|
||||
CurrentBaseSprite = SpriteComponent.GetPrototypeIcon(prototype, ResourceCache);
|
||||
CurrentTextures = SpriteComponent.GetPrototypeTextures(prototype, ResourceCache).ToList();
|
||||
CurrentPrototype = prototype;
|
||||
|
||||
IsActive = true;
|
||||
@@ -563,8 +563,9 @@ namespace Robust.Client.Placement
|
||||
|
||||
private void PreparePlacementTile()
|
||||
{
|
||||
CurrentBaseSprite = ResourceCache
|
||||
.GetResource<TextureResource>(new ResourcePath("/Textures/UserInterface/tilebuildoverlay.png")).Texture;
|
||||
CurrentTextures = new List<IDirectionalTextureProvider>
|
||||
{ResourceCache
|
||||
.GetResource<TextureResource>(new ResourcePath("/Textures/UserInterface/tilebuildoverlay.png")).Texture};
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.ClientEye;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
@@ -28,9 +29,9 @@ namespace Robust.Client.Placement
|
||||
public EntityCoordinates MouseCoords { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture resource to draw to represent the entity we are tryign to spawn
|
||||
/// Texture resources to draw to represent the entity we are trying to spawn
|
||||
/// </summary>
|
||||
public Texture? SpriteToDraw { get; set; }
|
||||
public List<Texture>? TexturesToDraw { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Color set to the ghost entity when it has a valid spawn position
|
||||
@@ -85,12 +86,15 @@ namespace Robust.Client.Placement
|
||||
|
||||
public virtual void Render(DrawingHandleWorld handle)
|
||||
{
|
||||
if (SpriteToDraw == null)
|
||||
if (TexturesToDraw == null)
|
||||
{
|
||||
SetSprite();
|
||||
DebugTools.AssertNotNull(SpriteToDraw);
|
||||
DebugTools.AssertNotNull(TexturesToDraw);
|
||||
}
|
||||
|
||||
if (TexturesToDraw == null || TexturesToDraw.Count == 0)
|
||||
return;
|
||||
|
||||
IEnumerable<EntityCoordinates> locationcollection;
|
||||
switch (pManager.PlacementType)
|
||||
{
|
||||
@@ -108,13 +112,17 @@ namespace Robust.Client.Placement
|
||||
break;
|
||||
}
|
||||
|
||||
var size = SpriteToDraw!.Size;
|
||||
var size = TexturesToDraw[0].Size;
|
||||
foreach (var coordinate in locationcollection)
|
||||
{
|
||||
var worldPos = coordinate.ToMapPos(pManager.EntityManager);
|
||||
var pos = worldPos - (size/(float)EyeManager.PixelsPerMeter) / 2f;
|
||||
var color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
|
||||
handle.DrawTexture(SpriteToDraw, pos, color);
|
||||
|
||||
foreach (var texture in TexturesToDraw)
|
||||
{
|
||||
handle.DrawTexture(texture, pos, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +196,10 @@ namespace Robust.Client.Placement
|
||||
|
||||
public void SetSprite()
|
||||
{
|
||||
SpriteToDraw = pManager.CurrentBaseSprite!.TextureFor(pManager.Direction);
|
||||
if (pManager.CurrentTextures == null)
|
||||
return;
|
||||
|
||||
TexturesToDraw = pManager.CurrentTextures.Select(o => o.TextureFor(pManager.Direction)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,12 +13,15 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IResourceCache cache, ResourcePath path)
|
||||
{
|
||||
if (!cache.ContentFileExists(path))
|
||||
if (!cache.TryContentFileRead(path, out var stream))
|
||||
{
|
||||
throw new FileNotFoundException("Content file does not exist for font");
|
||||
}
|
||||
|
||||
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(cache.ContentFileRead(path));
|
||||
using (stream)
|
||||
{
|
||||
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public VectorFont MakeDefault()
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
@@ -83,7 +82,12 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
// Load image from disk.
|
||||
var texPath = path / (stateObject.StateId + ".png");
|
||||
var image = Image.Load<Rgba32>(cache.ContentFileRead(texPath));
|
||||
var stream = cache.ContentFileRead(texPath);
|
||||
Image<Rgba32> image;
|
||||
using (stream)
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
var sheetSize = new Vector2i(image.Width, image.Height);
|
||||
|
||||
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
|
||||
@@ -341,15 +345,25 @@ namespace Robust.Client.ResourceManagement
|
||||
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
|
||||
{
|
||||
var stateName = stateObject["name"]!.ToObject<string>()!;
|
||||
var dirValue = stateObject["directions"]!.ToObject<int>();
|
||||
RSI.State.DirectionType directions;
|
||||
int dirValue;
|
||||
|
||||
var directions = dirValue switch
|
||||
if (stateObject.TryGetValue("directions", out var dirJToken))
|
||||
{
|
||||
1 => RSI.State.DirectionType.Dir1,
|
||||
4 => RSI.State.DirectionType.Dir4,
|
||||
8 => RSI.State.DirectionType.Dir8,
|
||||
_ => throw new RSILoadException($"Invalid direction: {dirValue}")
|
||||
};
|
||||
dirValue= dirJToken.ToObject<int>();
|
||||
directions = dirValue switch
|
||||
{
|
||||
1 => RSI.State.DirectionType.Dir1,
|
||||
4 => RSI.State.DirectionType.Dir4,
|
||||
8 => RSI.State.DirectionType.Dir8,
|
||||
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
dirValue = 1;
|
||||
directions = RSI.State.DirectionType.Dir1;
|
||||
}
|
||||
|
||||
// We can ignore selectors and flags for now,
|
||||
// because they're not used yet!
|
||||
|
||||
@@ -26,20 +26,23 @@ namespace Robust.Client.ResourceManagement
|
||||
throw new FileNotFoundException("Content file does not exist for texture");
|
||||
}
|
||||
|
||||
// Primarily for tracking down iCCP sRGB errors in the image files.
|
||||
Logger.DebugS("res.tex", $"Loading texture {path}.");
|
||||
|
||||
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
|
||||
|
||||
var manager = IoCManager.Resolve<IClyde>();
|
||||
|
||||
using var image = Image.Load<Rgba32>(stream);
|
||||
|
||||
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
|
||||
|
||||
if (cache is IResourceCacheInternal cacheInternal)
|
||||
using (stream)
|
||||
{
|
||||
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
|
||||
// Primarily for tracking down iCCP sRGB errors in the image files.
|
||||
Logger.DebugS("res.tex", $"Loading texture {path}.");
|
||||
|
||||
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
|
||||
|
||||
var manager = IoCManager.Resolve<IClyde>();
|
||||
|
||||
using var image = Image.Load<Rgba32>(stream);
|
||||
|
||||
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
|
||||
|
||||
if (cache is IResourceCacheInternal cacheInternal)
|
||||
{
|
||||
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,21 +51,25 @@ namespace Robust.Client.ResourceManagement
|
||||
var metaPath = path.WithName(path.Filename + ".yml");
|
||||
if (cache.TryContentFileRead(metaPath, out var stream))
|
||||
{
|
||||
YamlDocument yamlData;
|
||||
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
|
||||
using (stream)
|
||||
{
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
if (yamlStream.Documents.Count == 0)
|
||||
YamlDocument yamlData;
|
||||
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
|
||||
{
|
||||
return null;
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
if (yamlStream.Documents.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
yamlData = yamlStream.Documents[0];
|
||||
}
|
||||
|
||||
yamlData = yamlStream.Documents[0];
|
||||
return TextureLoadParameters.FromYaml((YamlMappingNode) yamlData.RootNode);
|
||||
}
|
||||
|
||||
return TextureLoadParameters.FromYaml((YamlMappingNode)yamlData.RootNode);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="nfluidsynth" Version="0.3.0" />
|
||||
<PackageReference Include="nfluidsynth" Version="0.3.1" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.3.1" Condition="'$(Configuration)' == 'Debug'" />
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs>? OnKeyBindDown;
|
||||
public event Action<GUIBoundKeyEventArgs>? OnKeyBindUp;
|
||||
|
||||
protected internal virtual void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
@@ -41,6 +42,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
protected internal virtual void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
OnKeyBindUp?.Invoke(args);
|
||||
}
|
||||
|
||||
protected internal virtual void MouseMove(GUIMouseMoveEventArgs args)
|
||||
|
||||
@@ -521,7 +521,7 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
DebugTools.Assert(!Disposed, "Control has been disposed.");
|
||||
|
||||
foreach (var child in Children.ToList())
|
||||
foreach (var child in Children.ToArray())
|
||||
{
|
||||
RemoveChild(child);
|
||||
}
|
||||
@@ -757,14 +757,32 @@ namespace Robust.Client.UserInterface
|
||||
/// <summary>
|
||||
/// Called when this control receives keyboard focus.
|
||||
/// </summary>
|
||||
protected internal virtual void FocusEntered()
|
||||
protected internal virtual void KeyboardFocusEntered()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this control loses keyboard focus.
|
||||
/// Called when this control loses keyboard focus (corresponds to UserInterfaceManager.KeyboardFocused).
|
||||
/// </summary>
|
||||
protected internal virtual void FocusExited()
|
||||
protected internal virtual void KeyboardFocusExited()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a control loses control focus for any reason. See <see cref="IUserInterfaceManager.ControlFocused"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Controls which have some sort of drag / drop behavior should usually implement this method (typically by cancelling the drag drop).
|
||||
/// Otherwise, if a user clicks down LMB over one control to initiate a drag, then clicks RMB down
|
||||
/// over a different control while still holding down LMB, the control being dragged will now lose focus
|
||||
/// and will no longer receive the keyup for the LMB, thus won't cancel the drag.
|
||||
/// This should also be considered for controls which have any special KeyBindUp behavior - consider
|
||||
/// what would happen if the control lost focus and never received the KeyBindUp.
|
||||
///
|
||||
/// There is no corresponding ControlFocusEntered - if a control wants to handle that situation they should simply
|
||||
/// handle KeyBindDown as that's the only way a control would gain focus.
|
||||
/// </remarks>
|
||||
protected internal virtual void ControlFocusExited()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
62
Robust.Client/UserInterface/Controls/AnimatedTextureRect.cs
Normal file
62
Robust.Client/UserInterface/Controls/AnimatedTextureRect.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A more complex control wrapping <see cref="TextureRect"/> that can do RSI directions and animations.
|
||||
/// </summary>
|
||||
public sealed class AnimatedTextureRect : Control
|
||||
{
|
||||
private IRsiStateLike? _state;
|
||||
private int _curFrame;
|
||||
private float _curFrameTime;
|
||||
|
||||
/// <summary>
|
||||
/// Internal TextureRect used to do actual drawing of the texture.
|
||||
/// You can use this property to change shaders or styling or such.
|
||||
/// </summary>
|
||||
public TextureRect DisplayRect { get; }
|
||||
|
||||
public RSI.State.Direction RsiDirection { get; } = RSI.State.Direction.South;
|
||||
|
||||
public AnimatedTextureRect()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
DisplayRect = new TextureRect();
|
||||
AddChild(DisplayRect);
|
||||
}
|
||||
|
||||
public void SetFromSpriteSpecifier(SpriteSpecifier specifier)
|
||||
{
|
||||
_curFrame = 0;
|
||||
_state = specifier.RsiStateLike();
|
||||
_curFrameTime = _state.GetDelay(0);
|
||||
DisplayRect.Texture = _state.GetFrame(RsiDirection, 0);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (!VisibleInTree || _state == null || !_state.IsAnimated)
|
||||
return;
|
||||
|
||||
var oldFrame = _curFrame;
|
||||
|
||||
_curFrameTime -= args.DeltaSeconds;
|
||||
while (_curFrameTime < _state.GetDelay(_curFrame))
|
||||
{
|
||||
_curFrame = (_curFrame + 1) % _state.AnimationFrameCount;
|
||||
_curFrameTime += _state.GetDelay(_curFrame);
|
||||
}
|
||||
|
||||
if (_curFrame != oldFrame)
|
||||
{
|
||||
DisplayRect.Texture = _state.GetFrame(RsiDirection, _curFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,8 +446,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var (vSepActual, hSepActual) = (Vector2i) (Separations * UIScale);
|
||||
var hSep = _limitDimension == Dimension.Column ? hSepActual : vSepActual;
|
||||
var vSep = _limitDimension == Dimension.Column ? vSepActual : hSepActual;
|
||||
var width = _limitDimension == Dimension.Column ? Width : Height;
|
||||
var height = _limitDimension == Dimension.Column ? Height : Width;
|
||||
var width = _limitDimension == Dimension.Column ? PixelWidth : PixelHeight;
|
||||
var height = _limitDimension == Dimension.Column ? PixelHeight : PixelWidth;
|
||||
|
||||
var stretchMaxX = width - hSep * (cols - 1);
|
||||
var stretchMaxY = height - vSep * (rows - 1);
|
||||
|
||||
@@ -433,7 +433,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (item.Region == null)
|
||||
continue;
|
||||
|
||||
if (!item.Region.Value.Contains(args.RelativePosition))
|
||||
if (!item.Region.Value.Contains(args.RelativePixelPosition))
|
||||
continue;
|
||||
|
||||
if (item.Selectable && !item.Disabled)
|
||||
|
||||
@@ -589,18 +589,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return index;
|
||||
}
|
||||
|
||||
protected internal override void FocusEntered()
|
||||
protected internal override void KeyboardFocusEntered()
|
||||
{
|
||||
base.FocusEntered();
|
||||
base.KeyboardFocusEntered();
|
||||
|
||||
// Reset this so the cursor is always visible immediately after gaining focus..
|
||||
_resetCursorBlink();
|
||||
OnFocusEnter?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
protected internal override void FocusExited()
|
||||
protected internal override void KeyboardFocusExited()
|
||||
{
|
||||
base.FocusExited();
|
||||
base.KeyboardFocusExited();
|
||||
OnFocusExit?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -20,7 +19,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
{
|
||||
var contentBox = _getStyleBox()?.GetContentBox(PixelSizeBox) ?? SizeBox;
|
||||
var contentBox = _getStyleBox()?.GetContentBox(PixelSizeBox) ?? PixelSizeBox;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
base.Draw(handle);
|
||||
|
||||
var bg = _getBackground();
|
||||
bg?.Draw(handle, SizeBox);
|
||||
bg?.Draw(handle, PixelSizeBox);
|
||||
|
||||
var fg = _getForeground();
|
||||
if (fg == null)
|
||||
@@ -69,10 +69,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
var minSize = fg.MinimumSize;
|
||||
var size = Width * GetAsRatio() - minSize.X;
|
||||
var size = PixelWidth * GetAsRatio() - minSize.X;
|
||||
if (size > 0)
|
||||
{
|
||||
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, Height));
|
||||
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, PixelHeight));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
264
Robust.Client/UserInterface/Controls/RadioOptions.cs
Normal file
264
Robust.Client/UserInterface/Controls/RadioOptions.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public enum RadioOptionsLayout { Horizontal, Vertical }
|
||||
|
||||
public class RadioOptions<T> : Control
|
||||
{
|
||||
private int internalIdCount = 0;
|
||||
|
||||
private readonly List<RadioOptionButtonData<T>> _buttonDataList = new();
|
||||
//private readonly Dictionary<int, int> _idMap = new();
|
||||
private ButtonGroup _buttonGroup = new();
|
||||
private Container _container;
|
||||
|
||||
public string ButtonStyle = string.Empty;
|
||||
public string FirstButtonStyle = string.Empty;
|
||||
public string LastButtonStyle = string.Empty;
|
||||
|
||||
public int ItemCount => _buttonDataList.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever you select a button.
|
||||
///
|
||||
/// Note: You should add optionButtons.Select(args.Id); if you want to actually select the button.
|
||||
/// </summary>
|
||||
public event Action<RadioOptionItemSelectedEventArgs<T>>? OnItemSelected;
|
||||
|
||||
public RadioOptions(RadioOptionsLayout layout)
|
||||
{
|
||||
switch (layout)
|
||||
{
|
||||
case RadioOptionsLayout.Vertical:
|
||||
_container = new VBoxContainer();
|
||||
break;
|
||||
case RadioOptionsLayout.Horizontal:
|
||||
default:
|
||||
_container = new HBoxContainer();
|
||||
break;
|
||||
}
|
||||
|
||||
this.AddChild(_container);
|
||||
}
|
||||
|
||||
public int AddItem(string label, T value, Action<RadioOptionItemSelectedEventArgs<T>>? itemSelectedAction = null)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Text = label,
|
||||
Group = _buttonGroup
|
||||
};
|
||||
|
||||
button.OnPressed += ButtonOnPressed;
|
||||
|
||||
var data = new RadioOptionButtonData<T>(label, value, button)
|
||||
{
|
||||
Id = internalIdCount++
|
||||
};
|
||||
|
||||
if (itemSelectedAction != null)
|
||||
{
|
||||
data.OnItemSelected += itemSelectedAction;
|
||||
}
|
||||
|
||||
_buttonDataList.Add(data);
|
||||
_container.AddChild(button);
|
||||
UpdateFirstAndLastButtonStyle();
|
||||
|
||||
if (_buttonDataList.Count == 1)
|
||||
{
|
||||
Select(data.Id);
|
||||
}
|
||||
return data.Id;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is triggered when the button is pressed via the UI
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
private void ButtonOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Button == obj.Button);
|
||||
if (buttonData != null)
|
||||
{
|
||||
InvokeItemSelected(new RadioOptionItemSelectedEventArgs<T>(buttonData.Id, this));
|
||||
return;
|
||||
}
|
||||
// Not reachable.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var buttonDatum in _buttonDataList)
|
||||
{
|
||||
buttonDatum.Button.OnPressed -= ButtonOnPressed;
|
||||
}
|
||||
_buttonDataList.Clear();
|
||||
_container.Children.Clear();
|
||||
SelectedId = 0;
|
||||
}
|
||||
|
||||
public object? GetItemMetadata(int idx)
|
||||
{
|
||||
return _buttonDataList.FirstOrDefault(bd => bd.Id == idx)?.Metadata;
|
||||
}
|
||||
|
||||
public int SelectedId { get; private set; }
|
||||
public RadioOptionButtonData<T> SelectedButtonData => _buttonDataList.First(bd => bd.Id == SelectedId);
|
||||
public Button SelectedButton => SelectedButtonData.Button;
|
||||
public string SelectedText => SelectedButtonData.Text;
|
||||
public T SelectedValue => SelectedButtonData.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Always will return true if itemId is not found.
|
||||
/// </summary>
|
||||
/// <param name="idx"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsItemDisabled(int idx)
|
||||
{
|
||||
return _buttonDataList.FirstOrDefault(bd => bd.Id == idx)?.Disabled ?? true;
|
||||
}
|
||||
|
||||
public void RemoveItem(int idx)
|
||||
{
|
||||
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data!= null)
|
||||
{
|
||||
data.Button.OnPressed -= ButtonOnPressed;
|
||||
_container.RemoveChild(data.Button);
|
||||
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (buttonData != null)
|
||||
_buttonDataList.Remove(buttonData);
|
||||
|
||||
UpdateFirstAndLastButtonStyle();
|
||||
}
|
||||
}
|
||||
|
||||
public void Select(int idx)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
SelectedId = data.Id;
|
||||
data.Button.Pressed = true;
|
||||
return;
|
||||
}
|
||||
// Not found.
|
||||
}
|
||||
|
||||
public void SelectByValue(T value)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => EqualityComparer<T>.Default.Equals(bd.Value, value));
|
||||
if (data != null)
|
||||
{
|
||||
Select(data.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void InvokeItemSelected(RadioOptionItemSelectedEventArgs<T> args)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == args.Id);
|
||||
if (buttonData == null) return;
|
||||
|
||||
if (buttonData.HasOnItemSelectedEvent)
|
||||
buttonData.InvokeItemSelected(args);
|
||||
else
|
||||
OnItemSelected?.Invoke(args);
|
||||
}
|
||||
|
||||
public void UpdateFirstAndLastButtonStyle()
|
||||
{
|
||||
for (int i = 0; i < _buttonDataList.Count; i++)
|
||||
{
|
||||
var buttonData = _buttonDataList[i];
|
||||
if (buttonData.Button == null) continue;
|
||||
|
||||
buttonData.Button.StyleClasses.Remove(ButtonStyle);
|
||||
buttonData.Button.StyleClasses.Remove(LastButtonStyle);
|
||||
buttonData.Button.StyleClasses.Remove(FirstButtonStyle);
|
||||
|
||||
if (i == 0)
|
||||
buttonData.Button.StyleClasses.Add(FirstButtonStyle);
|
||||
else if (i == _buttonDataList.Count - 1)
|
||||
buttonData.Button.StyleClasses.Add(LastButtonStyle);
|
||||
else
|
||||
buttonData.Button.StyleClasses.Add(ButtonStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItemDisabled(int idx, bool disabled)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
data.Disabled = disabled;
|
||||
data.Button.Disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItemMetadata(int idx, object metadata)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (buttonData != null)
|
||||
buttonData.Metadata = metadata;
|
||||
}
|
||||
|
||||
public void SetItemText(int idx, string text)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
data.Text = text;
|
||||
data.Button.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class RadioOptionItemSelectedEventArgs<T> : EventArgs
|
||||
{
|
||||
public RadioOptions<T> Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the item that has been selected.
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
public RadioOptionItemSelectedEventArgs(int id, RadioOptions<T> button)
|
||||
{
|
||||
Id = id;
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RadioOptionButtonData<T>
|
||||
{
|
||||
public int Id;
|
||||
public string Text;
|
||||
public T Value;
|
||||
public bool Disabled;
|
||||
public object? Metadata;
|
||||
|
||||
public Button Button;
|
||||
|
||||
public RadioOptionButtonData(string text, T value, Button button)
|
||||
{
|
||||
Text = text;
|
||||
this.Button = button;
|
||||
Value = value;
|
||||
}
|
||||
public event Action<RadioOptionItemSelectedEventArgs<T>>? OnItemSelected;
|
||||
public bool HasOnItemSelectedEvent => OnItemSelected != null;
|
||||
public void InvokeItemSelected(RadioOptionItemSelectedEventArgs<T> args)
|
||||
{
|
||||
OnItemSelected?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetValueWithoutEvent(float newValue)
|
||||
{
|
||||
newValue = ClampValue(newValue);
|
||||
_value = newValue;
|
||||
}
|
||||
|
||||
private void _ensureValueClamped()
|
||||
{
|
||||
var newValue = ClampValue(_value);
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
var oldHeight = _entry.Height;
|
||||
var oldWidth = _entry.Width;
|
||||
_entry.Update(font, MaxWidth ?? Width, UIScale);
|
||||
_entry.Update(font, (MaxWidth ?? Width) * UIScale, UIScale);
|
||||
if (oldHeight != _entry.Height || MaxWidth != null && _entry.Width != oldWidth)
|
||||
{
|
||||
MinimumSizeChanged();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user