mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f04bcd62cc | ||
|
|
9567a79a03 | ||
|
|
8c16ef9ac4 | ||
|
|
4d80492e9e | ||
|
|
32bca7cfd4 | ||
|
|
008babebc6 | ||
|
|
c65c4ba57e | ||
|
|
eb5b838e61 | ||
|
|
6b43036c9d | ||
|
|
f23a55793d | ||
|
|
46143d2589 | ||
|
|
ba7d1452c1 | ||
|
|
1c1343466e | ||
|
|
0d534e8bcd | ||
|
|
c83c6f9592 | ||
|
|
c794bd84bf | ||
|
|
d1d43f834b | ||
|
|
9505cb68df | ||
|
|
9763f5fdf4 | ||
|
|
80a963ec05 | ||
|
|
3a670ec25e | ||
|
|
e45950a557 | ||
|
|
6f1427ef3c | ||
|
|
9a7d1a39c1 | ||
|
|
9be903ee56 | ||
|
|
72f9f9c343 | ||
|
|
3ad760a99e | ||
|
|
e2f3722ce9 | ||
|
|
b4beca6562 | ||
|
|
ea02260230 | ||
|
|
3b243e487d | ||
|
|
f40ccb7558 | ||
|
|
f467a7027b | ||
|
|
c9d7d442d9 | ||
|
|
342626ad9b | ||
|
|
1c3ea968e4 | ||
|
|
f0ed3537ee | ||
|
|
74e7e61a98 | ||
|
|
fb9b0ae89b | ||
|
|
dbe297b1fc | ||
|
|
b84917e8e4 | ||
|
|
abb3f65fe4 | ||
|
|
41ec2dc131 | ||
|
|
e714dcc83c | ||
|
|
46291af1be | ||
|
|
ad929c9955 | ||
|
|
c86cb0b795 | ||
|
|
8d03feb84f | ||
|
|
0fa21ee2d2 | ||
|
|
9be0f032e8 | ||
|
|
afffb33446 | ||
|
|
19a87fb67a | ||
|
|
2fda62a274 | ||
|
|
5218bf70b0 | ||
|
|
4f95c07ab3 | ||
|
|
786acae47a | ||
|
|
f81e30a031 | ||
|
|
f5c1d870f9 | ||
|
|
4949b34c88 | ||
|
|
0f60ad9018 | ||
|
|
f7287b181d | ||
|
|
45b7500d93 | ||
|
|
dbe6f65880 | ||
|
|
4faef1bfd3 | ||
|
|
48d70a09c6 | ||
|
|
f682fb9cc7 | ||
|
|
814e5bcf17 | ||
|
|
dbc4e80e61 | ||
|
|
5eb5ddd96e | ||
|
|
405ed378c0 | ||
|
|
be9db264dd | ||
|
|
2f73f6190d | ||
|
|
f3dfa1f666 | ||
|
|
b0d17e9527 | ||
|
|
4c81e68bf1 | ||
|
|
4490751001 | ||
|
|
bc8d2c154c | ||
|
|
3c83f8e62a | ||
|
|
c36919d76a | ||
|
|
70a853cdd5 | ||
|
|
fd3eb092cc | ||
|
|
c740026014 | ||
|
|
44f9262d1a | ||
|
|
df2160b151 | ||
|
|
5c7b1e6823 | ||
|
|
eaf7a6ba0f | ||
|
|
9ab4286592 | ||
|
|
3f02ef3730 | ||
|
|
2f17cbb1dc | ||
|
|
c2657812f5 | ||
|
|
f17f077849 | ||
|
|
306deddbd2 | ||
|
|
cdd8df743a | ||
|
|
e7ac5ad047 | ||
|
|
0e621a26be | ||
|
|
bbcc7cfe1f | ||
|
|
1208c25dcd | ||
|
|
38c227b692 | ||
|
|
4e73d72753 | ||
|
|
b1e1a0cd88 |
@@ -58,7 +58,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
167
RELEASE-NOTES.md
167
RELEASE-NOTES.md
@@ -54,6 +54,171 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 237.1.2
|
||||
|
||||
|
||||
## 237.1.1
|
||||
|
||||
|
||||
## 237.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* csi's auto import-system can now handle generic types.
|
||||
* csi's reflection helpers (like `fld()`) handle private members up the inheritance chain.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UniqueIndexHkm<,>` and, by extension, entity data storage memory leaking.
|
||||
* Fix bugs related to UIScale on `OSWindow`s.
|
||||
|
||||
|
||||
## 237.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IClydeWindow.Size` is now settable, allowing window sizes to be changed after creation.
|
||||
|
||||
### New features
|
||||
|
||||
* The game server's `/update` endpoint now supports passing more information on why an update is available.
|
||||
* This information is accessible via `IWatchdogApi.RestartRequested`.
|
||||
* Information can be specified by passing a JSON object with a `Reason` code and `Message` field.
|
||||
* Added an "Erase" button to the tile spawn menu.
|
||||
* Added `OSWindow.Create()`, which allows OS windows to be created & initialised without immediately opening/showing them.
|
||||
|
||||
### Other
|
||||
|
||||
* Made `WatchdogApi` and some members of `IWatchdogApi` private. These symbols should never have been accessed by content.
|
||||
|
||||
|
||||
## 236.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `RequiredMemberAttribute` and `SetsRequiredMembersAttribute` have been added to the sandbox whitelist. I.e., you can now use the `required` keyword in client/shared code.
|
||||
* Added `SwitchExpressionException` to sandbox. This type gets used if you have a `switch` expression with no default case.
|
||||
* Added `LineEdit.SelectAllOnFocus`.
|
||||
* `GameTitle`, `WindowIconSet` and `SplashLogo` are exposed in `IGameController`. These will return said information set in game options or whatever is set in `manifest.yml`.
|
||||
* `BoundUserInterface` inheritors now have access to `PlayerManager`.
|
||||
* Added `MuteSounds` bool to `BaseButton`.
|
||||
* The engine has a new future-proof HWID system.
|
||||
* The auth server now manages HWIDs. This avoids HWID impersonation attacks.
|
||||
* The auth server can return multiple HWIDs. They are accessible in `NetUserData.ModernHWIds`.
|
||||
* The auth server also returns a trust score factor, accessible as `NetUserData.Trust`.
|
||||
* HWID can be disabled client side (`ROBUST_AUTH_ALLOW_HWID` env var) or server side (`net.hwid` cvar).
|
||||
* The old HWID system is still in place. It is intended that content switches to placing new bans against the new HWIDs.
|
||||
* Old HWIDs no longer work if the connection is not authenticated.
|
||||
* `launchauth` command now recognizes `SS14_LAUNCHER_APPDATA_NAME`.
|
||||
* Added new overload to `EntityLookupSystem.GetEntitiesIntersecting`.
|
||||
* Added `Control.RemoveChild(int childIndex)`.
|
||||
* `build.entities_category_filter` allows filtering the entity spawn panel to a specific category.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `SpriteView` offset calculations when scaled.
|
||||
|
||||
### Other
|
||||
|
||||
* Sprite flicks are applied immediately when started.
|
||||
* More warning fixes.
|
||||
* If the server gets shut down before finishing startup, the reason is now logged properly.
|
||||
|
||||
|
||||
## 236.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Revert IsTouching only being set to true if the contact were laready touching in clientside physics prediction.
|
||||
* Don't touch IsTouching if both bodies are asleep for clientside physics contacts. This change and the one above should fix a lot of clientside contact issues, particularly around repeated incorrect clientside contact events.
|
||||
|
||||
### New features
|
||||
|
||||
* Added an analyzer to detect duplicate Dependency fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities.
|
||||
* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players.
|
||||
* Hot reload XAML files on rename to fix them potentially not being reloaded with Visual Studio.
|
||||
* Fix TabContainer click detection for non-1.0 UI scales.
|
||||
|
||||
### Other
|
||||
|
||||
* Obsolete some static localization methods.
|
||||
* Tried to improve PVS tolerance to exceptions occurring.
|
||||
|
||||
|
||||
## 235.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks.
|
||||
* noSpawn is no longer obsolete and is now removed in lieu of the EntityCategory HideSpawnMenu.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* physics.maxlinvelocity is now a replicated cvar.
|
||||
* Fix DistanceJoint debug drawing in physics not using the local anchors.
|
||||
* Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter.
|
||||
* Fixed equality checks for `MarkupNode` not properly handling attributes.
|
||||
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
|
||||
* Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round.
|
||||
* Fixed the "to" and "take" toolshed commands not working as intended.
|
||||
* Rich text controls within an `OutputPanel` control will now become invisible when they are out of view.
|
||||
|
||||
### Other
|
||||
|
||||
* Improve precision for Quaternion2D constructor from angles.
|
||||
|
||||
|
||||
## 234.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* SharedAudioSystem now has PlayLocal which only runs audio locally on the client.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix AudioParams not being passed through on PlayGlobal methods.
|
||||
|
||||
|
||||
## 234.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove a lot of obsoleted code that has been obsoleted for a while.
|
||||
|
||||
### New features
|
||||
|
||||
* Add another GetLocalEntitiesIntersecting override.
|
||||
|
||||
### Other
|
||||
|
||||
* Mark large replays as requiring Server GC.
|
||||
* Obsolete some IResourceCache proxies.
|
||||
|
||||
|
||||
## 233.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem.
|
||||
* `MarkupNode` is now `IEquatable<MarkupNode>`. It already supported equality checks, now it implements the interface.
|
||||
* Added `Entity<T>` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`.
|
||||
* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed equality checks for `MarkupNode` not properly handling attributes.
|
||||
* Fixed toolshed commands failing to generate error messages when working with array types
|
||||
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
|
||||
|
||||
### Other
|
||||
|
||||
* If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception.
|
||||
|
||||
|
||||
## 233.0.2
|
||||
|
||||
### Bugfixes
|
||||
@@ -83,7 +248,7 @@ END TEMPLATE-->
|
||||
|
||||
### Internal
|
||||
|
||||
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
|
||||
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
|
||||
* Engine version script now supports dashes.
|
||||
|
||||
|
||||
|
||||
@@ -18,3 +18,9 @@
|
||||
description: entity-category-desc-hide
|
||||
hideSpawnMenu: true
|
||||
inheritable: false
|
||||
|
||||
# Entity prototypes added by the fork. With CVar you can hide all entities without this category
|
||||
- type: entityCategory
|
||||
id: ForkFiltered
|
||||
name: entity-category-name-fork
|
||||
description: entity-category-desc-fork
|
||||
@@ -4,9 +4,16 @@ entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-replace-button-text = Replace
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## TileSpawnWindow
|
||||
|
||||
tile-spawn-window-title = Place Tiles
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
|
||||
## Common Used
|
||||
|
||||
window-erase-button-text = Erase Mode
|
||||
|
||||
@@ -7,3 +7,6 @@ entity-category-desc-spawner = Entity prototypes that spawn other entities.
|
||||
|
||||
entity-category-name-hide = Hidden
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus
|
||||
|
||||
entity-category-name-fork = Fork Filtered
|
||||
entity-category-desc-fork = Entity prototypes added by the fork. With CVar you can hide all entities without this category
|
||||
@@ -219,9 +219,9 @@ command-description-MulVecCommand =
|
||||
command-description-DivVecCommand =
|
||||
Divides every element in the input by a scalar (single value).
|
||||
command-description-rng-to =
|
||||
Returns a number from its input to its argument (i.e. n..m inclusive)
|
||||
Returns a number between the input (inclusive) and the argument (exclusive).
|
||||
command-description-rng-from =
|
||||
Returns a number to its input from its argument (i.e. m..n inclusive)
|
||||
Returns a number between the argument (inclusive) and the input (exclusive))
|
||||
command-description-rng-prob =
|
||||
Returns a boolean based on the input probability/chance (from 0 to 1)
|
||||
command-description-sum =
|
||||
|
||||
63
Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs
Normal file
63
Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DuplicateDependencyAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DuplicateDependencyAnalyzer))]
|
||||
public sealed class DuplicateDependencyAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DuplicateDependencyAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.IoC.DependencyAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
public sealed class Foo
|
||||
{
|
||||
[Dependency]
|
||||
private object? Field;
|
||||
|
||||
[Dependency]
|
||||
private object? Field2;
|
||||
|
||||
[Dependency]
|
||||
private string? DifferentField;
|
||||
|
||||
private string? NonDependency1;
|
||||
private string? NonDependency2;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(9,21): warning RA0032: Another [Dependency] field of type 'object?' already exists in this type as field 'Field'
|
||||
VerifyCS.Diagnostic().WithSpan(9, 21, 9, 27).WithArguments("object?", "Field"));
|
||||
}
|
||||
}
|
||||
126
Robust.Analyzers/DuplicateDependencyAnalyzer.cs
Normal file
126
Robust.Analyzers/DuplicateDependencyAnalyzer.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Analyzer that detects duplicate <c>[Dependency]</c> fields inside a single type.
|
||||
/// </summary>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class DuplicateDependencyAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdDuplicateDependency,
|
||||
"Duplicate dependency field",
|
||||
"Another [Dependency] field of type '{0}' already exists in this type with field '{1}'",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterCompilationStartAction(compilationContext =>
|
||||
{
|
||||
var dependencyAttributeType = compilationContext.Compilation.GetTypeByMetadataName(DependencyAttributeType);
|
||||
if (dependencyAttributeType == null)
|
||||
return;
|
||||
|
||||
compilationContext.RegisterSymbolStartAction(symbolContext =>
|
||||
{
|
||||
var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
|
||||
// Only deal with non-static classes, doesn't make sense to have dependencies in anything else.
|
||||
if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsStatic)
|
||||
return;
|
||||
|
||||
var state = new AnalyzerState(dependencyAttributeType);
|
||||
symbolContext.RegisterSyntaxNodeAction(state.AnalyzeField, SyntaxKind.FieldDeclaration);
|
||||
symbolContext.RegisterSymbolEndAction(state.End);
|
||||
},
|
||||
SymbolKind.NamedType);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class AnalyzerState(INamedTypeSymbol dependencyAttributeType)
|
||||
{
|
||||
private readonly Dictionary<ITypeSymbol, List<IFieldSymbol>> _dependencyFields = new(SymbolEqualityComparer.Default);
|
||||
|
||||
public void AnalyzeField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
var field = (FieldDeclarationSyntax)context.Node;
|
||||
if (field.AttributeLists.Count == 0)
|
||||
return;
|
||||
|
||||
if (context.ContainingSymbol is not IFieldSymbol fieldSymbol)
|
||||
return;
|
||||
|
||||
// Can't have [Dependency]s for non-reference types.
|
||||
if (!fieldSymbol.Type.IsReferenceType)
|
||||
return;
|
||||
|
||||
if (!IsDependency(context.ContainingSymbol))
|
||||
return;
|
||||
|
||||
lock (_dependencyFields)
|
||||
{
|
||||
if (!_dependencyFields.TryGetValue(fieldSymbol.Type, out var dependencyFields))
|
||||
{
|
||||
dependencyFields = [];
|
||||
_dependencyFields.Add(fieldSymbol.Type, dependencyFields);
|
||||
}
|
||||
|
||||
dependencyFields.Add(fieldSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDependency(ISymbol symbol)
|
||||
{
|
||||
foreach (var attributeData in symbol.GetAttributes())
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, dependencyAttributeType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void End(SymbolAnalysisContext context)
|
||||
{
|
||||
lock (_dependencyFields)
|
||||
{
|
||||
foreach (var pair in _dependencyFields)
|
||||
{
|
||||
var fieldType = pair.Key;
|
||||
var fields = pair.Value;
|
||||
if (fields.Count <= 1)
|
||||
continue;
|
||||
|
||||
// Sort so we can have deterministic order to skip reporting for a single field.
|
||||
// Whichever sorts first doesn't get reported.
|
||||
fields.Sort(static (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
// Start at index 1 to skip first field.
|
||||
var firstField = fields[0];
|
||||
for (var i = 1; i < fields.Count; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, field.Locations[0], fieldType.ToDisplayString(), firstField.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Robust.Benchmarks/Physics/BoxStackBenchmark.cs
Normal file
96
Robust.Benchmarks/Physics/BoxStackBenchmark.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
[MediumRunJob]
|
||||
public class PhysicsBoxStackBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BoxStack()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PolygonShape shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
shape = new PolygonShape();
|
||||
shape.SetAsBox(0.5f, 0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
}
|
||||
92
Robust.Benchmarks/Physics/CircleStackBenchmark.cs
Normal file
92
Robust.Benchmarks/Physics/CircleStackBenchmark.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsCircleStackBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CircleStack()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PhysShapeCircle shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
shape = new PhysShapeCircle(0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
// TODO: Need to detect shape and work out if we need to use fixedrotation
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
}
|
||||
91
Robust.Benchmarks/Physics/PyramidBenchmark.cs
Normal file
91
Robust.Benchmarks/Physics/PyramidBenchmark.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsPyramidBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 300; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Pyramid()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
const byte count = 20;
|
||||
|
||||
// Setup ground
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
|
||||
// Setup boxes
|
||||
float a = 0.5f;
|
||||
PolygonShape shape = new();
|
||||
shape.SetAsBox(a, a);
|
||||
|
||||
var x = new Vector2(-7.0f, 0.75f);
|
||||
Vector2 y;
|
||||
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
|
||||
Vector2 deltaY = new Vector2(1.125f, 0.0f);
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
y = x;
|
||||
|
||||
for (var j = i; j < count; ++j)
|
||||
{
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
|
||||
y += deltaY;
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
|
||||
x += deltaX;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
Robust.Benchmarks/Physics/TumblerBenchmark.cs
Normal file
105
Robust.Benchmarks/Physics/TumblerBenchmark.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsTumblerBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 800; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
var shape = new PolygonShape();
|
||||
shape.SetAsBox(0.125f, 0.125f);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Tumbler()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var joints = entManager.System<SharedJointSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
// Due to lookup changes fixtureless bodies are invalid, so
|
||||
var cShape = new PhysShapeCircle(1f);
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
|
||||
|
||||
var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var body = entManager.AddComponent<PhysicsComponent>(bodyUid);
|
||||
|
||||
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
|
||||
physics.SetSleepingAllowed(bodyUid, body, false);
|
||||
physics.SetFixedRotation(bodyUid, false, body: body);
|
||||
|
||||
|
||||
// TODO: Box2D just deref, bleh shape structs someday
|
||||
var shape1 = new PolygonShape();
|
||||
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
|
||||
|
||||
var shape2 = new PolygonShape();
|
||||
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
|
||||
|
||||
var shape3 = new PolygonShape();
|
||||
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
|
||||
|
||||
var shape4 = new PolygonShape();
|
||||
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
physics.WakeBody(bodyUid, body: body);
|
||||
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
|
||||
revolute.LocalAnchorA = new Vector2(0f, 10f);
|
||||
revolute.LocalAnchorB = new Vector2(0f, 0f);
|
||||
revolute.ReferenceAngle = 0f;
|
||||
revolute.MotorSpeed = 0.05f * MathF.PI;
|
||||
revolute.MaxMotorTorque = 100000000f;
|
||||
revolute.EnableMotor = true;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -453,6 +453,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
|
||||
SoundSpecifier? sound,
|
||||
EntityUid source,
|
||||
EntityUid? soundInitiator,
|
||||
AudioParams? audioParams = null
|
||||
)
|
||||
{
|
||||
return PlayPredicted(sound, source, soundInitiator, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
|
||||
@@ -33,12 +33,6 @@ public interface IMidiRenderer : IDisposable
|
||||
/// </summary>
|
||||
bool LoopMidi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This increases all note on velocities to 127.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
bool VolumeBoost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The midi program (instrument) the renderer is using.
|
||||
/// </summary>
|
||||
|
||||
@@ -205,14 +205,6 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
public bool VolumeBoost
|
||||
{
|
||||
get => VelocityOverride == 127;
|
||||
set => VelocityOverride = value ? 127 : null;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? TrackingEntity { get; set; } = null;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.HWId;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Placement;
|
||||
@@ -158,6 +159,7 @@ namespace Robust.Client
|
||||
|
||||
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
|
||||
deps.Register<MarkupTagManager>();
|
||||
deps.Register<IHWId, BasicHWId>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var wantName = args.Length > 0 ? args[0] : null;
|
||||
|
||||
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir(_gameController))!;
|
||||
var dbPath = Path.Combine(basePath, "launcher", "settings.db");
|
||||
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
|
||||
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
|
||||
|
||||
#if USE_SYSTEM_SQLITE
|
||||
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
|
||||
|
||||
@@ -14,15 +14,6 @@ namespace Robust.Client.Credits
|
||||
/// </summary>
|
||||
public static class CreditsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of open source software used in the engine and their license.
|
||||
/// </summary>
|
||||
[Obsolete("Use overload that takes in an explicit resource manager instead.")]
|
||||
public static IEnumerable<LicenseEntry> GetLicenses()
|
||||
{
|
||||
return GetLicenses(IoCManager.Resolve<IResourceManager>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of open source software used in the engine and their license.
|
||||
/// </summary>
|
||||
|
||||
@@ -544,7 +544,7 @@ namespace Robust.Client.Debugging
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint:
|
||||
worldHandle.DrawLine(xf1, xf2, JointColor);
|
||||
worldHandle.DrawLine(p1, p2, JointColor);
|
||||
break;
|
||||
case PrismaticJoint prisma:
|
||||
var pA = Transform.Mul(xfa, joint.LocalAnchorA);
|
||||
|
||||
@@ -112,14 +112,28 @@ namespace Robust.Client
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
public string GameTitle()
|
||||
{
|
||||
return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox";
|
||||
}
|
||||
|
||||
public string WindowIconSet()
|
||||
{
|
||||
return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? "";
|
||||
}
|
||||
|
||||
public string SplashLogo()
|
||||
{
|
||||
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
|
||||
}
|
||||
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
DebugTools.AssertNotNull(_resourceManifest);
|
||||
|
||||
_clyde.InitializePostWindowing();
|
||||
_audio.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(
|
||||
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
|
||||
_clyde.SetWindowTitle(GameTitle());
|
||||
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
@@ -368,7 +382,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
@@ -399,10 +413,8 @@ namespace Robust.Client
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
_configurationManager.OverrideConVars(new[]
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name,
|
||||
options.WindowIconSet?.ToString() ?? _resourceManifest.WindowIconSet ?? ""),
|
||||
(CVars.DisplaySplashLogo.Name,
|
||||
options.SplashLogo?.ToString() ?? _resourceManifest.SplashLogo ?? "")
|
||||
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
|
||||
(CVars.DisplaySplashLogo.Name, SplashLogo())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -99,15 +99,6 @@ namespace Robust.Client.GameObjects
|
||||
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start playing an animation.
|
||||
/// </summary>
|
||||
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
|
||||
public void Play(AnimationPlayerComponent component, Animation animation, string key)
|
||||
{
|
||||
Play(new Entity<AnimationPlayerComponent>(component.Owner, component), animation, key);
|
||||
}
|
||||
|
||||
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
|
||||
{
|
||||
AddComponent(ent);
|
||||
@@ -152,6 +143,14 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var track in animation.AnimationTracks)
|
||||
{
|
||||
if (track is not AnimationTrackSpriteFlick)
|
||||
continue;
|
||||
|
||||
track.AdvancePlayback(ent.Owner, 0, 0, 0f);
|
||||
}
|
||||
|
||||
ent.Comp.PlayingAnimations.Add(key, playback);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private readonly PointLightSystem _lightSys = default!;
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
|
||||
|
||||
@@ -34,9 +34,6 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
int GetApplicableStateCount();
|
||||
|
||||
[Obsolete("use GetApplicableStateCount()")]
|
||||
int CurrentBufferSize => GetApplicableStateCount();
|
||||
|
||||
/// <summary>
|
||||
/// Total number of game states currently in the state buffer.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
|
||||
|
||||
@@ -109,6 +109,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (!reg.IsVisible) // Only send this for open windows
|
||||
return;
|
||||
|
||||
var loaded = RtToLoaded(reg.RenderTarget);
|
||||
loaded.Size = reg.FramebufferSize;
|
||||
|
||||
|
||||
@@ -343,6 +343,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (isMain)
|
||||
_mainWindow = reg;
|
||||
|
||||
reg.IsVisible = parameters.Visible;
|
||||
|
||||
_windows.Add(reg);
|
||||
_windowHandles.Add(reg.Handle);
|
||||
|
||||
@@ -444,6 +446,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowing!.CursorSet(_mainWindow!, cursor);
|
||||
}
|
||||
|
||||
private void SetWindowSize(WindowReg reg, Vector2i size)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
|
||||
_windowing!.WindowSetSize(reg, size);
|
||||
}
|
||||
|
||||
private void SetWindowVisible(WindowReg reg, bool visible)
|
||||
{
|
||||
@@ -533,7 +541,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.DoDestroyWindow(Reg);
|
||||
}
|
||||
|
||||
public Vector2i Size => Reg.FramebufferSize;
|
||||
public Vector2i Size
|
||||
{
|
||||
get => Reg.FramebufferSize;
|
||||
set => _clyde.SetWindowSize(Reg, value);
|
||||
}
|
||||
|
||||
public IRenderTarget RenderTarget => Reg.RenderTarget;
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
|
||||
{
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
@@ -517,7 +517,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderTarget = renderTarget;
|
||||
}
|
||||
|
||||
public Vector2i Size { get; } = default;
|
||||
public Vector2i Size { get; set; } = default;
|
||||
public bool IsDisposed { get; private set; }
|
||||
public WindowId Id { get; set; }
|
||||
public IRenderTarget RenderTarget { get; }
|
||||
|
||||
@@ -85,6 +85,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
WinThreadWinSetMonitor(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetSize cmd:
|
||||
WinThreadWinSetSize(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetVisible cmd:
|
||||
WinThreadWinSetVisible(cmd);
|
||||
break;
|
||||
@@ -234,6 +238,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetSize(
|
||||
nint Window,
|
||||
int W, int H
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetVisible(
|
||||
nint Window,
|
||||
bool Visible
|
||||
|
||||
@@ -84,6 +84,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
);
|
||||
}
|
||||
|
||||
public void WindowSetSize(WindowReg window, Vector2i size)
|
||||
{
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
SendCmd(new CmdWinSetSize((nint) reg.GlfwWindow, size.X, size.Y));
|
||||
}
|
||||
|
||||
public void WindowSetVisible(WindowReg window, bool visible)
|
||||
{
|
||||
var reg = (GlfwWindowReg) window;
|
||||
@@ -92,6 +99,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SendCmd(new CmdWinSetVisible((nint) reg.GlfwWindow, visible));
|
||||
}
|
||||
|
||||
private void WinThreadWinSetSize(CmdWinSetSize cmd)
|
||||
{
|
||||
var win = (Window*) cmd.Window;
|
||||
|
||||
GLFW.SetWindowSize(win, cmd.W, cmd.H);
|
||||
}
|
||||
|
||||
private void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
||||
{
|
||||
var win = (Window*) cmd.Window;
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
void WindowDestroy(WindowReg reg);
|
||||
void WindowSetTitle(WindowReg window, string title);
|
||||
void WindowSetMonitor(WindowReg window, IClydeMonitor monitor);
|
||||
void WindowSetSize(WindowReg window, Vector2i size);
|
||||
void WindowSetVisible(WindowReg window, bool visible);
|
||||
void WindowRequestAttention(WindowReg window);
|
||||
void WindowSwapBuffers(WindowReg window);
|
||||
|
||||
@@ -93,6 +93,10 @@ internal partial class Clyde
|
||||
WinThreadWinRequestAttention(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetSize cmd:
|
||||
WinThreadWinSetSize(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetVisible cmd:
|
||||
WinThreadWinSetVisible(cmd);
|
||||
break;
|
||||
@@ -246,6 +250,11 @@ internal partial class Clyde
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetSize(
|
||||
nint Window,
|
||||
int W, int H
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetVisible(
|
||||
nint Window,
|
||||
bool Visible
|
||||
|
||||
@@ -336,11 +336,22 @@ internal partial class Clyde
|
||||
_sawmill.Warning("WindowSetMonitor not implemented on SDL2");
|
||||
}
|
||||
|
||||
public void WindowSetSize(WindowReg window, Vector2i size)
|
||||
{
|
||||
SendCmd(new CmdWinSetSize(WinPtr(window), size.X, size.Y));
|
||||
}
|
||||
|
||||
public void WindowSetVisible(WindowReg window, bool visible)
|
||||
{
|
||||
window.IsVisible = visible;
|
||||
SendCmd(new CmdWinSetVisible(WinPtr(window), visible));
|
||||
}
|
||||
|
||||
private static void WinThreadWinSetSize(CmdWinSetSize cmd)
|
||||
{
|
||||
SDL_SetWindowSize(cmd.Window, cmd.W, cmd.H);
|
||||
}
|
||||
|
||||
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
||||
{
|
||||
if (cmd.Visible)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.Graphics
|
||||
WindowId Id { get; }
|
||||
IRenderTarget RenderTarget { get; }
|
||||
string Title { get; set; }
|
||||
Vector2i Size { get; }
|
||||
Vector2i Size { get; set; }
|
||||
bool IsFocused { get; }
|
||||
bool IsMinimized { get; }
|
||||
bool IsVisible { get; set; }
|
||||
|
||||
86
Robust.Client/HWId/BasicHWId.cs
Normal file
86
Robust.Client/HWId/BasicHWId.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Win32;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.HWId;
|
||||
|
||||
internal sealed class BasicHWId : IHWId
|
||||
{
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
|
||||
public const int LengthHwid = 32;
|
||||
|
||||
public byte[] GetLegacy()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
return GetWindowsHWid("Hwid");
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public byte[] GetModern()
|
||||
{
|
||||
byte[] raw;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
raw = GetWindowsHWid("Hwid2");
|
||||
else
|
||||
raw = GetFileHWid();
|
||||
|
||||
return [0, ..raw];
|
||||
}
|
||||
|
||||
private static byte[] GetWindowsHWid(string keyName)
|
||||
{
|
||||
const string keyPath = @"HKEY_CURRENT_USER\SOFTWARE\Space Wizards\Robust";
|
||||
|
||||
var regKey = Registry.GetValue(keyPath, keyName, null);
|
||||
if (regKey is byte[] { Length: LengthHwid } bytes)
|
||||
return bytes;
|
||||
|
||||
var newId = new byte[LengthHwid];
|
||||
RandomNumberGenerator.Fill(newId);
|
||||
Registry.SetValue(
|
||||
keyPath,
|
||||
keyName,
|
||||
newId,
|
||||
RegistryValueKind.Binary);
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
private byte[] GetFileHWid()
|
||||
{
|
||||
var path = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
var hwidPath = Path.Combine(path, ".hwid");
|
||||
|
||||
var value = ReadHWidFile(hwidPath);
|
||||
if (value != null)
|
||||
return value;
|
||||
|
||||
value = RandomNumberGenerator.GetBytes(LengthHwid);
|
||||
File.WriteAllBytes(hwidPath, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static byte[]? ReadHWidFile(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = File.ReadAllBytes(path);
|
||||
if (value.Length == LengthHwid)
|
||||
return value;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// First time the file won't exist.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -26,5 +26,20 @@ public interface IGameController
|
||||
/// This exists to give content module more control over tick updating.
|
||||
/// </summary>
|
||||
event Action<FrameEventArgs>? TickUpdateOverride;
|
||||
|
||||
/// <summary>
|
||||
/// Get the games Title, if Options.DefaultWindowTitle or if defaultWindowTitle is not set in the manifest.yml, it will default to RobustToolbox.
|
||||
/// </summary>
|
||||
string GameTitle();
|
||||
|
||||
/// <summary>
|
||||
/// Get the games Window Icon set, if Options.WindowIconSet or if windowIconSet is not set in the manifest.yml, it will default to an empty string.
|
||||
/// </summary>
|
||||
string WindowIconSet();
|
||||
|
||||
/// <summary>
|
||||
/// Get the games Splash Logo, if Options.SplashLogo or if splashLogo is not set in the manifest.yml, it will default to an empty string.
|
||||
/// </summary>
|
||||
string SplashLogo();
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,6 @@ public sealed partial class PhysicsSystem
|
||||
|
||||
if (activeA == false && activeB == false)
|
||||
{
|
||||
contact.IsTouching = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -261,7 +261,6 @@ namespace Robust.Client.Player
|
||||
// This is a new userid, so we create a new session.
|
||||
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
|
||||
var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.SetPing(state.Ping);
|
||||
SetStatus(newSession, state.Status);
|
||||
SetAttachedEntity(newSession, controlled, out _, true);
|
||||
dirty = true;
|
||||
@@ -271,7 +270,6 @@ namespace Robust.Client.Player
|
||||
// Check if the data is actually different
|
||||
if (session.Name == state.Name
|
||||
&& session.Status == state.Status
|
||||
&& session.Ping == state.Ping
|
||||
&& session.AttachedEntity == controlled)
|
||||
{
|
||||
continue;
|
||||
@@ -280,7 +278,6 @@ namespace Robust.Client.Player
|
||||
dirty = true;
|
||||
var local = (ICommonSessionInternal)session;
|
||||
local.SetName(state.Name);
|
||||
local.SetPing(state.Ping);
|
||||
SetStatus(local, state.Status);
|
||||
SetAttachedEntity(local, controlled, out _, true);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed partial class ReplayLoadManager
|
||||
// Scratch data used by UpdateEntityStates.
|
||||
// Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on
|
||||
// first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop.
|
||||
private class UpdateScratchData
|
||||
private sealed class UpdateScratchData
|
||||
{
|
||||
public Dictionary<ushort, ComponentChange> Changes;
|
||||
public EntityState lastChange;
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed partial class ReplayLoadManager
|
||||
var uncompressedSize = BitConverter.ToInt32(intBuf);
|
||||
|
||||
var decompressedStream = new MemoryStream(uncompressedSize);
|
||||
decompressStream.CopyTo(decompressedStream, uncompressedSize);
|
||||
decompressStream.CopyTo(decompressedStream);
|
||||
decompressedStream.Position = 0;
|
||||
DebugTools.Assert(uncompressedSize == decompressedStream.Length);
|
||||
|
||||
|
||||
@@ -48,7 +48,10 @@ public interface IResourceCache : IResourceManager
|
||||
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
|
||||
event Action<RsiLoadedEventArgs> OnRsiLoaded;
|
||||
|
||||
[Obsolete("Fetch these through IoC directly instead")]
|
||||
IClyde Clyde { get; }
|
||||
|
||||
[Obsolete("Fetch these through IoC directly instead")]
|
||||
IFontManager FontManager { get; }
|
||||
}
|
||||
|
||||
|
||||
@@ -762,7 +762,23 @@ namespace Robust.Client.UserInterface
|
||||
throw new InvalidOperationException("The provided control is not a direct child of this control.");
|
||||
}
|
||||
|
||||
_orderedChildren.Remove(child);
|
||||
var childIndex = _orderedChildren.IndexOf(child);
|
||||
RemoveChild(childIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the child at a specific index from this control.
|
||||
/// </summary>
|
||||
/// <param name="childIndex">The index of the child to remove.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown if the provided child index is out of range
|
||||
/// </exception>
|
||||
public void RemoveChild(int childIndex)
|
||||
{
|
||||
DebugTools.Assert(!Disposed, "Control has been disposed.");
|
||||
|
||||
var child = _orderedChildren[childIndex];
|
||||
_orderedChildren.RemoveAt(childIndex);
|
||||
|
||||
child.Parent = null;
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ using Robust.Client.Placement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -19,9 +21,9 @@ namespace Robust.Client.UserInterface.Controllers.Implementations;
|
||||
|
||||
public sealed class EntitySpawningUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly IResourceCache _resources = default!;
|
||||
|
||||
private EntitySpawnWindow? _window;
|
||||
private readonly List<EntityPrototype> _shownEntities = new();
|
||||
@@ -193,6 +195,9 @@ public sealed class EntitySpawningUIController : UIController
|
||||
_window.SelectedButton = null;
|
||||
searchStr = searchStr?.ToLowerInvariant();
|
||||
|
||||
var categoryFilter = _cfg.GetCVar(CVars.EntitiesCategoryFilter);
|
||||
_prototypes.TryIndex<EntityCategoryPrototype>(categoryFilter, out var filter);
|
||||
|
||||
foreach (var prototype in _prototypes.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (prototype.Abstract)
|
||||
@@ -205,6 +210,11 @@ public sealed class EntitySpawningUIController : UIController
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filter is not null && !prototype.Categories.Contains(filter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchStr != null && !DoesEntityMatchSearch(prototype, searchStr))
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class TileSpawningUIController : UIController
|
||||
|
||||
private readonly List<ITileDefinition> _shownTiles = new();
|
||||
private bool _clearingTileSelections;
|
||||
private bool _eraseTile;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -35,6 +36,37 @@ public sealed class TileSpawningUIController : UIController
|
||||
_placement.PlacementChanged += ClearTileSelection;
|
||||
}
|
||||
|
||||
private void StartTilePlacement(int tileType)
|
||||
{
|
||||
var newObjInfo = new PlacementInformation
|
||||
{
|
||||
PlacementOption = "AlignTileAny",
|
||||
TileType = tileType,
|
||||
Range = 400,
|
||||
IsTile = true
|
||||
};
|
||||
|
||||
_placement.BeginPlacing(newObjInfo);
|
||||
}
|
||||
|
||||
private void OnTileEraseToggled(ButtonToggledEventArgs args)
|
||||
{
|
||||
if (_window == null || _window.Disposed)
|
||||
return;
|
||||
|
||||
_placement.Clear();
|
||||
|
||||
if (args.Pressed)
|
||||
{
|
||||
_eraseTile = true;
|
||||
StartTilePlacement(0);
|
||||
}
|
||||
else
|
||||
_eraseTile = false;
|
||||
|
||||
args.Button.Pressed = args.Pressed;
|
||||
}
|
||||
|
||||
public void ToggleWindow()
|
||||
{
|
||||
EnsureWindow();
|
||||
@@ -60,6 +92,8 @@ public sealed class TileSpawningUIController : UIController
|
||||
_window.SearchBar.OnTextChanged += OnTileSearchChanged;
|
||||
_window.TileList.OnItemSelected += OnTileItemSelected;
|
||||
_window.TileList.OnItemDeselected += OnTileItemDeselected;
|
||||
_window.EraseButton.Pressed = _eraseTile;
|
||||
_window.EraseButton.OnToggled += OnTileEraseToggled;
|
||||
BuildTileList();
|
||||
}
|
||||
|
||||
@@ -76,6 +110,7 @@ public sealed class TileSpawningUIController : UIController
|
||||
_clearingTileSelections = true;
|
||||
_window.TileList.ClearSelected();
|
||||
_clearingTileSelections = false;
|
||||
_window.EraseButton.Pressed = false;
|
||||
}
|
||||
|
||||
private void OnTileClearPressed(ButtonEventArgs args)
|
||||
@@ -102,16 +137,7 @@ public sealed class TileSpawningUIController : UIController
|
||||
private void OnTileItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
var definition = _shownTiles[args.ItemIndex];
|
||||
|
||||
var newObjInfo = new PlacementInformation
|
||||
{
|
||||
PlacementOption = "AlignTileAny",
|
||||
TileType = definition.TileId,
|
||||
Range = 400,
|
||||
IsTile = true
|
||||
};
|
||||
|
||||
_placement.BeginPlacing(newObjInfo);
|
||||
StartTilePlacement(definition.TileId);
|
||||
}
|
||||
|
||||
private void OnTileItemDeselected(ItemList.ItemListDeselectedEventArgs args)
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private bool _enableAllKeybinds;
|
||||
private ButtonGroup? _group;
|
||||
private bool _toggleMode;
|
||||
private bool _muteSounds;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the group this button belongs to.
|
||||
@@ -135,7 +136,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (Pressed != value)
|
||||
return;
|
||||
|
||||
UserInterfaceManager.ClickSound();
|
||||
if (!MuteSounds)
|
||||
UserInterfaceManager.ClickSound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,6 +201,16 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If <c>true</c>, this button will not emit sounds when the mouse is pressed or hovered over.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool MuteSounds
|
||||
{
|
||||
get => _muteSounds;
|
||||
set => _muteSounds = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the button is pushed down by the mouse.
|
||||
/// </summary>
|
||||
@@ -298,7 +310,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
else
|
||||
{
|
||||
UserInterfaceManager.ClickSound();
|
||||
if (!MuteSounds)
|
||||
UserInterfaceManager.ClickSound();
|
||||
}
|
||||
|
||||
OnPressed?.Invoke(buttonEventArgs);
|
||||
@@ -353,7 +366,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.MouseEntered();
|
||||
|
||||
if (!Disabled)
|
||||
if (!Disabled && !MuteSounds)
|
||||
{
|
||||
UserInterfaceManager.HoverSound();
|
||||
}
|
||||
|
||||
@@ -53,6 +53,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private TimeSpan? _lastClickTime;
|
||||
private Vector2? _lastClickPosition;
|
||||
|
||||
// Keep track of the frame on which we got focus, so we can implement SelectAllOnFocus properly.
|
||||
// Otherwise, there's no way to keep track of whether the KeyDown is the one that focused the text box,
|
||||
// to avoid text selection stomping on the behavior.
|
||||
// This isn't a great way to do it.
|
||||
// A better fix would be to annotate all input events with some unique sequence ID,
|
||||
// and expose the input event that focused the control in KeyboardFocusEntered.
|
||||
// But that sounds like a refactor I'm not doing today.
|
||||
private uint _focusedOnFrame;
|
||||
|
||||
private bool IsPlaceHolderVisible => !(HidePlaceHolderOnFocus && HasKeyboardFocus()) && string.IsNullOrEmpty(_text) && _placeHolder != null;
|
||||
|
||||
public event Action<LineEditEventArgs>? OnTextChanged;
|
||||
@@ -190,6 +199,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public bool IgnoreNext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, all the text in the LineEdit will be automatically selected whenever it is focused.
|
||||
/// </summary>
|
||||
public bool SelectAllOnFocus { get; set; }
|
||||
|
||||
private (int start, int length)? _imeData;
|
||||
|
||||
|
||||
@@ -709,7 +723,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
else
|
||||
else if (!(SelectAllOnFocus && _focusedOnFrame == _timing.CurFrame))
|
||||
{
|
||||
_lastClickTime = _timing.RealTime;
|
||||
_lastClickPosition = args.PointerLocation.Position;
|
||||
@@ -868,6 +882,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
_clyde.TextInputStart();
|
||||
}
|
||||
|
||||
_focusedOnFrame = _timing.CurFrame;
|
||||
if (SelectAllOnFocus)
|
||||
{
|
||||
CursorPosition = _text.Length;
|
||||
SelectionStart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override void KeyboardFocusExited()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -33,9 +32,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public IClydeWindow? Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the window is currently open.
|
||||
/// Whether the window is created and currently open.
|
||||
/// </summary>
|
||||
public bool IsOpen => ClydeWindow != null;
|
||||
public bool IsOpen => ClydeWindow?.IsVisible ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// The title of the window.
|
||||
@@ -97,12 +96,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the window to the user.
|
||||
/// Create the window if not already created.
|
||||
/// This window is not visible by default, call <see cref="Show"/> to display it.
|
||||
/// </summary>
|
||||
public void Show()
|
||||
public IClydeWindow Create()
|
||||
{
|
||||
if (IsOpen)
|
||||
return;
|
||||
if (ClydeWindow != null)
|
||||
return ClydeWindow;
|
||||
|
||||
var parameters = new WindowCreateParameters();
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
parameters.Styles = WindowStyles;
|
||||
parameters.Owner = Owner;
|
||||
parameters.StartupLocation = StartupLocation;
|
||||
parameters.Visible = false;
|
||||
|
||||
ClydeWindow = _clyde.CreateWindow(parameters);
|
||||
ClydeWindow.RequestClosed += OnWindowRequestClosed;
|
||||
@@ -136,6 +137,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_root = UserInterfaceManager.CreateWindowRoot(ClydeWindow);
|
||||
_root.AddChild(this);
|
||||
|
||||
// Resize the window by our UIScale
|
||||
ClydeWindow.Size = new((int)(ClydeWindow.Size.X * UIScale), (int)(ClydeWindow.Size.Y * UIScale));
|
||||
return ClydeWindow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the window to the user, creating it if necessary
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
ClydeWindow = Create();
|
||||
ClydeWindow.IsVisible = true;
|
||||
|
||||
Shown();
|
||||
}
|
||||
|
||||
@@ -179,7 +193,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void OnWindowResized(WindowResizedEventArgs obj)
|
||||
{
|
||||
SetSize = obj.NewSize;
|
||||
SetSize = obj.NewSize / UIScale;
|
||||
}
|
||||
|
||||
private void RealClosed()
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var style = _getStyleBox();
|
||||
var font = _getFont();
|
||||
var lineSeparation = font.GetLineSeparation(UIScale);
|
||||
style?.Draw(handle, PixelSizeBox, UIScale);
|
||||
var contentBox = _getContentBox();
|
||||
|
||||
@@ -141,18 +142,26 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (entryOffset + entry.Height < 0)
|
||||
{
|
||||
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
|
||||
// Controls within the entry are the children of this control, which means they are drawn separately
|
||||
// after this Draw call, so we have to mark them as invisible to prevent them from being drawn.
|
||||
//
|
||||
// An alternative option is to ensure that the control position updating logic in entry.Draw is always
|
||||
// run, and then setting RectClipContent = true to use scissor box testing to handle the controls
|
||||
// visibility
|
||||
entry.HideControls();
|
||||
entryOffset += entry.Height + lineSeparation;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entryOffset > contentBox.Height)
|
||||
{
|
||||
break;
|
||||
entry.HideControls();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.Draw(_tagManager, handle, font, contentBox, entryOffset, context, UIScale);
|
||||
|
||||
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
|
||||
entryOffset += entry.Height + lineSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var offset = SpriteOffset
|
||||
? Vector2.Zero
|
||||
: - (-_eyeRotation).RotateVec(sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
: - (-_eyeRotation).RotateVec(sprite.Offset * _scale) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
|
||||
var position = PixelSize / 2 + offset * stretch * UIScale;
|
||||
var scale = Scale * UIScale * stretch;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
@@ -21,6 +22,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private int _currentTab;
|
||||
private bool _tabsVisible = true;
|
||||
// The right-most coordinate of each tab header
|
||||
private List<float> _tabRight = new();
|
||||
|
||||
public int CurrentTab
|
||||
{
|
||||
@@ -157,11 +160,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var headerOffset = 0f;
|
||||
|
||||
_tabRight.Clear();
|
||||
|
||||
// Then, draw the tabs.
|
||||
for (var i = 0; i < ChildCount; i++)
|
||||
{
|
||||
if (!GetTabVisible(i))
|
||||
{
|
||||
_tabRight.Add(headerOffset);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -214,6 +220,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
headerOffset += boxAdvance;
|
||||
// Remember the right-most point of this tab, for testing clicked areas
|
||||
_tabRight.Add(headerOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,46 +291,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
args.Handle();
|
||||
|
||||
var relX = args.RelativePixelPosition.X;
|
||||
|
||||
var font = _getFont();
|
||||
var boxActive = _getTabBoxActive();
|
||||
var boxInactive = _getTabBoxInactive();
|
||||
|
||||
var headerOffset = 0f;
|
||||
|
||||
float tabLeft = 0;
|
||||
for (var i = 0; i < ChildCount; i++)
|
||||
{
|
||||
if (!GetTabVisible(i))
|
||||
if (relX > tabLeft && relX <= _tabRight[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var title = GetActualTabTitle(i);
|
||||
|
||||
var titleLength = 0;
|
||||
// Get string length.
|
||||
foreach (var rune in title.EnumerateRunes())
|
||||
{
|
||||
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
titleLength += metrics.Advance;
|
||||
}
|
||||
|
||||
var active = _currentTab == i;
|
||||
var box = active ? boxActive : boxInactive;
|
||||
var boxAdvance = titleLength + (box?.MinimumSize.X ?? 0);
|
||||
|
||||
if (headerOffset < relX && headerOffset + boxAdvance > relX)
|
||||
{
|
||||
// Got em.
|
||||
CurrentTab = i;
|
||||
return;
|
||||
}
|
||||
|
||||
headerOffset += boxAdvance;
|
||||
// Next tab starts here
|
||||
tabLeft = _tabRight[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</ScrollContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="ReplaceButton" Access="Public" ToggleMode="True" Text="{Loc entity-spawn-window-replace-button-text}"/>
|
||||
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc entity-spawn-window-erase-button-text}"/>
|
||||
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc window-erase-button-text}"/>
|
||||
<OptionButton Name="OverrideMenu" Access="Public" HorizontalExpand="True" ToolTip="{Loc entity-spawn-window-override-menu-tooltip}" />
|
||||
</BoxContainer>
|
||||
<Label Name="RotationLabel" Access="Public"/>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
[Obsolete("Use DefaultWindow instead")]
|
||||
[Virtual]
|
||||
public class SS14Window : DefaultWindow
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<TileSpawnWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
Title="Place Tiles"
|
||||
Title="{Loc tile-spawn-window-title}"
|
||||
SetSize="300 300"
|
||||
MinSize="300 200">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
@@ -9,5 +9,8 @@
|
||||
<Button Name="ClearButton" Access="Public" Text="Clear"/>
|
||||
</BoxContainer>
|
||||
<ItemList Name="TileList" Access="Public" VerticalExpand="True"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc window-erase-button-text}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</TileSpawnWindow>
|
||||
|
||||
@@ -118,9 +118,6 @@ namespace Robust.Client.UserInterface
|
||||
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
if (ProcessRune(ref this, new Rune(' '), out breakLine))
|
||||
continue;
|
||||
|
||||
control.Measure(new Vector2(Width, Height));
|
||||
|
||||
var desiredSize = control.DesiredPixelSize;
|
||||
@@ -167,6 +164,16 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly void HideControls()
|
||||
{
|
||||
if (_tagControls == null)
|
||||
return;
|
||||
foreach (var control in _tagControls.Values)
|
||||
{
|
||||
control.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Draw(
|
||||
MarkupTagManager tagManager,
|
||||
DrawingHandleScreen handle,
|
||||
@@ -216,8 +223,11 @@ namespace Robust.Client.UserInterface
|
||||
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
var invertedScale = 1f / uiScale;
|
||||
// Controls may have been previously hidden via HideControls due to being "out-of frame".
|
||||
// If this ever gets replaced with RectClipContents / scissor box testing, this can be removed.
|
||||
control.Visible = true;
|
||||
|
||||
var invertedScale = 1f / uiScale;
|
||||
control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale);
|
||||
control.Measure(new Vector2(Width, Height));
|
||||
var advanceX = control.DesiredPixelSize.X;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -28,10 +29,11 @@ internal sealed partial class UserInterfaceManager
|
||||
{
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
HorizontalAlignment = Control.HAlignment.Stretch,
|
||||
VerticalAlignment = Control.VAlignment.Stretch,
|
||||
UIScaleSet = window.ContentScale.X
|
||||
VerticalAlignment = Control.VAlignment.Stretch
|
||||
};
|
||||
|
||||
newRoot.UIScaleSet = CalculateAutoScale(newRoot);
|
||||
|
||||
_roots.Add(newRoot);
|
||||
_windowsToRoot.Add(window.Id, newRoot);
|
||||
|
||||
|
||||
@@ -58,16 +58,16 @@ internal sealed class XamlHotReloadManager : IXamlHotReloadManager
|
||||
var watcher = new FileSystemWatcher(location)
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite,
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
|
||||
};
|
||||
|
||||
watcher.Changed += (_, args) =>
|
||||
void OnWatcherEvent(object sender, FileSystemEventArgs args)
|
||||
{
|
||||
switch (args.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Renamed:
|
||||
case WatcherChangeTypes.Deleted:
|
||||
return;
|
||||
case WatcherChangeTypes.Renamed:
|
||||
case WatcherChangeTypes.Created:
|
||||
case WatcherChangeTypes.Changed:
|
||||
case WatcherChangeTypes.All:
|
||||
@@ -98,7 +98,10 @@ internal sealed class XamlHotReloadManager : IXamlHotReloadManager
|
||||
|
||||
_xamlProxyManager.SetImplementation(resourceFileName, newText);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
watcher.Changed += OnWatcherEvent;
|
||||
watcher.Renamed += OnWatcherEvent;
|
||||
watcher.EnableRaisingEvents = true;
|
||||
return watcher;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Utility
|
||||
{
|
||||
@@ -9,6 +8,12 @@ namespace Robust.Client.Utility
|
||||
{
|
||||
[Pure]
|
||||
public static string GetUserDataDir(IGameControllerInternal gameController)
|
||||
{
|
||||
return Path.Combine(GetRootUserDataDir(gameController), "data");
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public static string GetRootUserDataDir(IGameControllerInternal gameController)
|
||||
{
|
||||
string appDataDir;
|
||||
|
||||
@@ -30,8 +35,7 @@ namespace Robust.Client.Utility
|
||||
appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
#endif
|
||||
|
||||
return Path.Combine(appDataDir, gameController.Options.UserDataDirectoryName, "data");
|
||||
return Path.Combine(appDataDir, gameController.Options.UserDataDirectoryName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public static class Diagnostics
|
||||
public const string IdDataFieldNoVVReadWrite = "RA0029";
|
||||
public const string IdUseNonGenericVariant = "RA0030";
|
||||
public const string IdPreferOtherType = "RA0031";
|
||||
public const string IdDuplicateDependency = "RA0032";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -66,32 +66,26 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter)
|
||||
{
|
||||
var count = filter.Count;
|
||||
DebugTools.Assert(component.IncludedEntities == null);
|
||||
component.IncludedEntities = new();
|
||||
|
||||
if (count == 0)
|
||||
if (filter.Count == 0)
|
||||
return;
|
||||
|
||||
_pvs.AddSessionOverrides(uid, filter);
|
||||
|
||||
var ents = new HashSet<EntityUid>(count);
|
||||
|
||||
foreach (var session in filter.Recipients)
|
||||
{
|
||||
var ent = session.AttachedEntity;
|
||||
|
||||
if (ent == null)
|
||||
continue;
|
||||
|
||||
ents.Add(ent.Value);
|
||||
if (session.AttachedEntity is {} ent)
|
||||
component.IncludedEntities.Add(ent);
|
||||
}
|
||||
|
||||
DebugTools.Assert(component.IncludedEntities == null);
|
||||
component.IncludedEntities = ents;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
AddAudioFilter(entity, entity.Comp, playerFilter);
|
||||
entity.Comp.Global = true;
|
||||
@@ -175,6 +169,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return (entity, entity.Comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
|
||||
SoundSpecifier? sound,
|
||||
EntityUid source,
|
||||
EntityUid? soundInitiator,
|
||||
AudioParams? audioParams = null
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
|
||||
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
|
||||
[Dependency] private readonly IWatchdogApiInternal _watchdogApi = default!;
|
||||
[Dependency] private readonly HubManager _hubManager = default!;
|
||||
[Dependency] private readonly IScriptHost _scriptHost = default!;
|
||||
[Dependency] private readonly IMetricsManagerInternal _metricsManager = default!;
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
@@ -566,7 +566,7 @@ namespace Robust.Server
|
||||
// Don't start the main loop. This only works if a reason is passed to Shutdown(...)
|
||||
if (_shutdownReason != null)
|
||||
{
|
||||
_logger.Fatal("Shutdown has been requested before the main loop has been started, complying.");
|
||||
_logger.Fatal("Shutdown has been requested before the main loop has been started, complying. Reason: {0}", _shutdownReason);
|
||||
}
|
||||
else _mainLoop.Run();
|
||||
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Erin Catto
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Server.Console.Commands
|
||||
{
|
||||
/*
|
||||
* I didn't use blueprints because this is way easier to iterate upon as I can shit out testbed upon testbed on new maps
|
||||
* and never have to leave my debugger.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Copies of Box2D's physics testbed for debugging.
|
||||
/// </summary>
|
||||
public sealed class TestbedCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
|
||||
public override string Command => "testbed";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError("Require 2 args for testbed!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
shell.WriteError($"Unable to parse map {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError($"map {args[0]} does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell.Player == null)
|
||||
{
|
||||
shell.WriteError("No player found");
|
||||
return;
|
||||
}
|
||||
|
||||
Action testbed;
|
||||
SetupPlayer(mapId, shell);
|
||||
|
||||
switch (args[1])
|
||||
{
|
||||
case "boxstack":
|
||||
testbed = () => CreateBoxStack(mapId);
|
||||
break;
|
||||
case "circlestack":
|
||||
testbed = () => CreateCircleStack(mapId);
|
||||
break;
|
||||
case "pyramid":
|
||||
testbed = () => CreatePyramid(mapId);
|
||||
break;
|
||||
case "tumbler":
|
||||
testbed = () => CreateTumbler(mapId);
|
||||
break;
|
||||
default:
|
||||
shell.WriteError($"testbed {args[0]} not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Timer.Spawn(1000, () =>
|
||||
{
|
||||
if (!_map.MapExists(mapId)) return;
|
||||
testbed();
|
||||
});
|
||||
|
||||
shell.WriteLine($"Testbed on map {mapId}");
|
||||
}
|
||||
|
||||
private void SetupPlayer(MapId mapId, IConsoleShell shell)
|
||||
{
|
||||
_map.SetMapPaused(mapId, false);
|
||||
var mapUid = _map.GetMapEntityIdOrThrow(mapId);
|
||||
_ent.System<Gravity2DController>().SetGravity(mapUid, new Vector2(0, -9.8f));
|
||||
|
||||
shell.ExecuteCommand("aghost");
|
||||
shell.ExecuteCommand($"tp 0 0 {mapId}");
|
||||
shell.RemoteExecuteCommand($"physics shapes");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void CreateBoxStack(MapId mapId)
|
||||
{
|
||||
var physics = _ent.System<SharedPhysicsSystem>();
|
||||
var fixtures = _ent.System<FixtureSystem>();
|
||||
|
||||
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PolygonShape shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = _ent.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
|
||||
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
shape = new PolygonShape();
|
||||
shape.SetAsBox(0.5f, 0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
|
||||
private void CreateCircleStack(MapId mapId)
|
||||
{
|
||||
var physics = _ent.System<SharedPhysicsSystem>();
|
||||
var fixtures = _ent.System<FixtureSystem>();
|
||||
|
||||
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PhysShapeCircle shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = _ent.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
|
||||
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
shape = new PhysShapeCircle(0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
// TODO: Need to detect shape and work out if we need to use fixedrotation
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
|
||||
private void CreatePyramid(MapId mapId)
|
||||
{
|
||||
const byte count = 20;
|
||||
|
||||
// Setup ground
|
||||
var physics = _ent.System<SharedPhysicsSystem>();
|
||||
var fixtures = _ent.System<FixtureSystem>();
|
||||
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
|
||||
// Setup boxes
|
||||
float a = 0.5f;
|
||||
PolygonShape shape = new();
|
||||
shape.SetAsBox(a, a);
|
||||
|
||||
var x = new Vector2(-7.0f, 0.75f);
|
||||
Vector2 y;
|
||||
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
|
||||
Vector2 deltaY = new Vector2(1.125f, 0.0f);
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
y = x;
|
||||
|
||||
for (var j = i; j < count; ++j)
|
||||
{
|
||||
var boxUid = _ent.SpawnEntity(null, new MapCoordinates(y, mapId));
|
||||
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
|
||||
y += deltaY;
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
}
|
||||
|
||||
x += deltaX;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTumbler(MapId mapId)
|
||||
{
|
||||
var physics = _ent.System<SharedPhysicsSystem>();
|
||||
var fixtures = _ent.System<FixtureSystem>();
|
||||
var joints = _ent.System<SharedJointSystem>();
|
||||
|
||||
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
|
||||
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
|
||||
// Due to lookup changes fixtureless bodies are invalid, so
|
||||
var cShape = new PhysShapeCircle(1f);
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
|
||||
|
||||
var bodyUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var body = _ent.AddComponent<PhysicsComponent>(bodyUid);
|
||||
|
||||
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
|
||||
physics.SetSleepingAllowed(bodyUid, body, false);
|
||||
physics.SetFixedRotation(bodyUid, false, body: body);
|
||||
|
||||
|
||||
// TODO: Box2D just deref, bleh shape structs someday
|
||||
var shape1 = new PolygonShape();
|
||||
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
|
||||
|
||||
var shape2 = new PolygonShape();
|
||||
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
|
||||
|
||||
var shape3 = new PolygonShape();
|
||||
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
|
||||
|
||||
var shape4 = new PolygonShape();
|
||||
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
physics.WakeBody(bodyUid, body: body);
|
||||
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
|
||||
revolute.LocalAnchorA = new Vector2(0f, 10f);
|
||||
revolute.LocalAnchorB = new Vector2(0f, 0f);
|
||||
revolute.ReferenceAngle = 0f;
|
||||
revolute.MotorSpeed = 0.05f * MathF.PI;
|
||||
revolute.MaxMotorTorque = 100000000f;
|
||||
revolute.EnableMotor = true;
|
||||
|
||||
// Box2D has this as 800 which is jesus christo.
|
||||
// Wouldn't recommend higher than 100 in debug and higher than 300 on release unless
|
||||
// you really want a profile.
|
||||
var count = 300;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Timer.Spawn(i * 20, () =>
|
||||
{
|
||||
if (!_map.MapExists(mapId)) return;
|
||||
var boxUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
var shape = new PolygonShape();
|
||||
shape.SetAsBox(0.125f, 0.125f);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,6 @@ namespace Robust.Server.Console
|
||||
{
|
||||
var message = new MsgConCmdReg();
|
||||
|
||||
var counter = 0;
|
||||
var toolshedCommands = _toolshed.DefaultEnvironment.AllCommands().ToArray();
|
||||
message.Commands = new List<MsgConCmdReg.Command>(AvailableCommands.Count + toolshedCommands.Length);
|
||||
var commands = new HashSet<string>();
|
||||
|
||||
@@ -53,20 +53,21 @@ namespace Robust.Server.GameObjects
|
||||
var query = AllEntityQuery<MapGridComponent>();
|
||||
while (query.MoveNext(out var uid, out var grid))
|
||||
{
|
||||
if (!GridEmpty(grid)) continue;
|
||||
if (!GridEmpty((uid, grid)))
|
||||
continue;
|
||||
toDelete.Add(uid);
|
||||
}
|
||||
|
||||
foreach (var uid in toDelete)
|
||||
{
|
||||
MapManager.DeleteGrid(uid);
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool GridEmpty(MapGridComponent grid)
|
||||
private bool GridEmpty(Entity<MapGridComponent> entity)
|
||||
{
|
||||
return !(grid.GetAllTiles().Any());
|
||||
return !(GetAllTiles(entity, entity).Any());
|
||||
}
|
||||
|
||||
private void HandleGridEmpty(EntityUid uid, MapGridComponent component, EmptyGridEvent args)
|
||||
@@ -74,7 +75,7 @@ namespace Robust.Server.GameObjects
|
||||
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
|
||||
return;
|
||||
|
||||
MapManager.DeleteGrid(args.GridId);
|
||||
EntityManager.DeleteEntity(args.GridId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,10 @@ internal sealed class PvsChunk
|
||||
{
|
||||
// TODO ARCH multi-component queries
|
||||
if (!meta.TryGetComponent(child, out var childMeta)
|
||||
|| !xform.TryGetComponent(child, out var childXform))
|
||||
|| !xform.TryGetComponent(child, out var childXform)
|
||||
|| childMeta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
DebugTools.Assert($"PVS chunk contains a deleted entity: {child}");
|
||||
DebugTools.Assert($"PVS chunk contains a delete or terminating entity: {child}");
|
||||
MarkDirty();
|
||||
return false;
|
||||
}
|
||||
@@ -188,9 +189,10 @@ internal sealed class PvsChunk
|
||||
{
|
||||
// TODO ARCH multi-component queries
|
||||
if (!meta.TryGetComponent(child, out var childMeta)
|
||||
|| !xform.TryGetComponent(child, out var childXform))
|
||||
|| !xform.TryGetComponent(child, out var childXform)
|
||||
|| childMeta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
DebugTools.Assert($"PVS chunk contains a deleted entity: {child}");
|
||||
DebugTools.Assert($"PVS chunk contains a delete or terminating entity: {child}");
|
||||
MarkDirty();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -190,6 +191,15 @@ internal struct PvsMetadata
|
||||
private byte Pad0;
|
||||
public uint Marker;
|
||||
#endif
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public void Validate(MetaDataComponent comp)
|
||||
{
|
||||
DebugTools.AssertEqual(NetEntity, comp.NetEntity);
|
||||
DebugTools.AssertEqual(VisMask, comp.VisibilityMask);
|
||||
DebugTools.AssertEqual(LifeStage, comp.EntityLifeStage);
|
||||
DebugTools.Assert(LastModifiedTick == comp.EntityLastModifiedTick || LastModifiedTick.Value == 0);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
|
||||
@@ -250,13 +250,6 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
AddSessionOverride(uid.Value, session);
|
||||
}
|
||||
|
||||
[Obsolete("Use variant that takes in an EntityUid")]
|
||||
public void AddSessionOverrides(NetEntity entity, Filter filter, bool removeExistingOverride = true)
|
||||
{
|
||||
if (TryGetEntity(entity, out var uid))
|
||||
AddSessionOverrides(uid.Value, filter);
|
||||
}
|
||||
|
||||
[Obsolete("Don't use this, clear specific overrides")]
|
||||
public void ClearOverride(NetEntity entity)
|
||||
{
|
||||
|
||||
@@ -232,6 +232,7 @@ internal sealed partial class PvsSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddEntityToChunk(EntityUid uid, MetaDataComponent meta, PvsChunkLocation location)
|
||||
{
|
||||
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
ref var chunk = ref CollectionsMarshal.GetValueRefOrAddDefault(_chunks, location, out var existing);
|
||||
if (!existing)
|
||||
{
|
||||
|
||||
@@ -245,8 +245,6 @@ internal sealed partial class PvsSystem
|
||||
|
||||
private void OnEntityAdded(Entity<MetaDataComponent> entity)
|
||||
{
|
||||
DebugTools.Assert(entity.Comp.PvsData.Index == default);
|
||||
|
||||
AssignEntityPointer(entity.Comp);
|
||||
}
|
||||
|
||||
@@ -255,6 +253,7 @@ internal sealed partial class PvsSystem
|
||||
/// </summary>
|
||||
private void AssignEntityPointer(MetaDataComponent meta)
|
||||
{
|
||||
DebugTools.Assert(meta.PvsData == PvsIndex.Invalid);
|
||||
if (_dataFreeListHead == PvsIndex.Invalid)
|
||||
{
|
||||
ExpandEntityCapacity();
|
||||
@@ -267,8 +266,6 @@ internal sealed partial class PvsSystem
|
||||
ref var freeLink = ref Unsafe.As<PvsMetadata, PvsMetadataFreeLink>(ref metadata);
|
||||
_dataFreeListHead = freeLink.NextFree;
|
||||
|
||||
// TODO: re-introduce this assert.
|
||||
// DebugTools.AssertEqual(((PvsMetadata*) ptr)->NetEntity, NetEntity.Invalid);
|
||||
DebugTools.AssertNotEqual(meta.NetEntity, NetEntity.Invalid);
|
||||
|
||||
meta.PvsData = index;
|
||||
@@ -287,9 +284,9 @@ internal sealed partial class PvsSystem
|
||||
private void OnEntityDeleted(Entity<MetaDataComponent> entity)
|
||||
{
|
||||
var ptr = entity.Comp.PvsData;
|
||||
entity.Comp.PvsData = default;
|
||||
entity.Comp.PvsData = PvsIndex.Invalid;
|
||||
|
||||
if (ptr == default)
|
||||
if (ptr == PvsIndex.Invalid)
|
||||
return;
|
||||
|
||||
_incomingReturns.Add(ptr);
|
||||
@@ -300,7 +297,8 @@ internal sealed partial class PvsSystem
|
||||
/// </summary>
|
||||
private void AfterEntityFlush()
|
||||
{
|
||||
DebugTools.Assert(EntityManager.EntityCount == 0);
|
||||
if (EntityManager.EntityCount > 0)
|
||||
throw new Exception("Cannot reset PVS data without first deleting all entities.");
|
||||
|
||||
ClearPvsData();
|
||||
ShrinkDataMemory();
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Server.GameStates
|
||||
|
||||
private void OnEntityDirty(Entity<MetaDataComponent> uid)
|
||||
{
|
||||
if (uid.Comp.PvsData != default)
|
||||
if (uid.Comp.PvsData != PvsIndex.Invalid)
|
||||
{
|
||||
ref var meta = ref _metadataMemory.GetRef(uid.Comp.PvsData.Index);
|
||||
meta.LastModifiedTick = uid.Comp.EntityLastModifiedTick;
|
||||
|
||||
@@ -107,7 +107,7 @@ internal sealed partial class PvsSystem
|
||||
|
||||
internal void SyncMetadata(MetaDataComponent meta)
|
||||
{
|
||||
if (meta.PvsData == default)
|
||||
if (meta.PvsData == PvsIndex.Invalid)
|
||||
return;
|
||||
|
||||
ref var ptr = ref _metadataMemory.GetRef(meta.PvsData.Index);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
@@ -25,6 +26,7 @@ internal sealed partial class PvsSystem
|
||||
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedGlobalOverride))
|
||||
{
|
||||
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
|
||||
meta.Validate(ent.Meta);
|
||||
if ((mask & meta.VisMask) == meta.VisMask)
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
}
|
||||
@@ -51,6 +53,7 @@ internal sealed partial class PvsSystem
|
||||
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedForceOverride))
|
||||
{
|
||||
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
|
||||
meta.Validate(ent.Meta);
|
||||
if ((mask & meta.VisMask) == meta.VisMask)
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ internal sealed partial class PvsSystem
|
||||
foreach (ref var ent in span)
|
||||
{
|
||||
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
|
||||
meta.Validate(ent.Meta);
|
||||
if ((mask & meta.VisMask) == meta.VisMask)
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
}
|
||||
@@ -78,8 +79,7 @@ internal sealed partial class PvsSystem
|
||||
|
||||
if (meta.LifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, lifestage is {meta.LifeStage}.\n{Environment.StackTrace}");
|
||||
EntityManager.QueueDeleteEntity(ent.Uid);
|
||||
Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, Meta lifestage: {ent.Meta.EntityLifeStage}, PVS lifestage: {meta.LifeStage}.\n{Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
SubscribeLocalEvent<EntityTerminatingEvent>(OnEntityTerminating);
|
||||
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
@@ -137,6 +136,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
EntityManager.EntityAdded += OnEntityAdded;
|
||||
EntityManager.EntityDeleted += OnEntityDeleted;
|
||||
EntityManager.AfterEntityFlush += AfterEntityFlush;
|
||||
EntityManager.BeforeEntityTerminating += OnEntityTerminating;
|
||||
|
||||
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
|
||||
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
@@ -162,6 +162,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
EntityManager.EntityAdded -= OnEntityAdded;
|
||||
EntityManager.EntityDeleted -= OnEntityDeleted;
|
||||
EntityManager.AfterEntityFlush -= AfterEntityFlush;
|
||||
EntityManager.BeforeEntityTerminating -= OnEntityTerminating;
|
||||
|
||||
_parallelMgr.ParallelCountChanged -= ResetParallelism;
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ namespace Robust.Server.Physics
|
||||
foreach (var index in node.Indices)
|
||||
{
|
||||
var tilePos = offset + index;
|
||||
tileData.Add((tilePos, oldGrid.GetTileRef(tilePos).Tile));
|
||||
tileData.Add((tilePos, _maps.GetTileRef(oldGridUid, oldGrid, tilePos).Tile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace Robust.Server.Physics
|
||||
}
|
||||
|
||||
// Set tiles on old grid
|
||||
oldGrid.SetTiles(tileData);
|
||||
_maps.SetTiles(oldGridUid, oldGrid, tileData);
|
||||
GenerateSplitNodes(newGridUid, newGrid);
|
||||
SendNodeDebug(newGridUid);
|
||||
}
|
||||
@@ -388,7 +388,7 @@ namespace Robust.Server.Physics
|
||||
|
||||
private void GenerateSplitNodes(EntityUid gridUid, MapGridComponent grid)
|
||||
{
|
||||
foreach (var chunk in grid.GetMapChunks().Values)
|
||||
foreach (var chunk in _maps.GetMapChunks(gridUid, grid).Values)
|
||||
{
|
||||
var group = CreateNodes(gridUid, grid, chunk);
|
||||
_nodes[gridUid].Add(chunk.Indices, group);
|
||||
@@ -479,7 +479,7 @@ namespace Robust.Server.Physics
|
||||
if (index.X == 0)
|
||||
{
|
||||
// Check West
|
||||
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X - 1, chunk.Indices.Y), out neighborChunk) &&
|
||||
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X - 1, chunk.Indices.Y), out neighborChunk) &&
|
||||
TryGetNode(gridEuid, neighborChunk, new Vector2i(chunk.ChunkSize - 1, index.Y), out neighborNode))
|
||||
{
|
||||
chunkNode.Neighbors.Add(neighborNode);
|
||||
@@ -490,7 +490,7 @@ namespace Robust.Server.Physics
|
||||
if (index.Y == 0)
|
||||
{
|
||||
// Check South
|
||||
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X, chunk.Indices.Y - 1), out neighborChunk) &&
|
||||
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X, chunk.Indices.Y - 1), out neighborChunk) &&
|
||||
TryGetNode(gridEuid, neighborChunk, new Vector2i(index.X, chunk.ChunkSize - 1), out neighborNode))
|
||||
{
|
||||
chunkNode.Neighbors.Add(neighborNode);
|
||||
@@ -501,7 +501,7 @@ namespace Robust.Server.Physics
|
||||
if (index.X == chunk.ChunkSize - 1)
|
||||
{
|
||||
// Check East
|
||||
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X + 1, chunk.Indices.Y), out neighborChunk) &&
|
||||
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X + 1, chunk.Indices.Y), out neighborChunk) &&
|
||||
TryGetNode(gridEuid, neighborChunk, new Vector2i(0, index.Y), out neighborNode))
|
||||
{
|
||||
chunkNode.Neighbors.Add(neighborNode);
|
||||
@@ -512,7 +512,7 @@ namespace Robust.Server.Physics
|
||||
if (index.Y == chunk.ChunkSize - 1)
|
||||
{
|
||||
// Check North
|
||||
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X, chunk.Indices.Y + 1), out neighborChunk) &&
|
||||
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X, chunk.Indices.Y + 1), out neighborChunk) &&
|
||||
TryGetNode(gridEuid, neighborChunk, new Vector2i(index.X, 0), out neighborNode))
|
||||
{
|
||||
chunkNode.Neighbors.Add(neighborNode);
|
||||
|
||||
@@ -137,8 +137,7 @@ namespace Robust.Server.Player
|
||||
{
|
||||
UserId = client.UserId,
|
||||
Name = client.Name,
|
||||
Status = client.Status,
|
||||
Ping = client.Channel!.Ping
|
||||
Status = client.Status
|
||||
};
|
||||
list.Add(info);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace Robust.Server
|
||||
deps.Register<IViewVariablesManager, ServerViewVariablesManager>();
|
||||
deps.Register<IServerViewVariablesInternal, ServerViewVariablesManager>();
|
||||
deps.Register<IWatchdogApi, WatchdogApi>();
|
||||
deps.Register<IWatchdogApiInternal, WatchdogApi>();
|
||||
deps.Register<IScriptHost, ScriptHost>();
|
||||
deps.Register<IMetricsManager, MetricsManager>();
|
||||
deps.Register<IMetricsManagerInternal, MetricsManager>();
|
||||
@@ -97,6 +98,7 @@ namespace Robust.Server
|
||||
deps.Register<NetworkResourceManager>();
|
||||
deps.Register<IHttpClientHolder, HttpClientHolder>();
|
||||
deps.Register<UploadedContentManager>();
|
||||
deps.Register<IHWId, DummyHWId>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,34 +24,8 @@ namespace Robust.Server.ServerStatus
|
||||
IDictionary<string, string> ResponseHeaders { get; }
|
||||
bool KeepAlive { get; set; }
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
T? RequestBodyJson<T>();
|
||||
Task<T?> RequestBodyJsonAsync<T>();
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
void Respond(
|
||||
string text,
|
||||
HttpStatusCode code = HttpStatusCode.OK,
|
||||
string contentType = "text/plain");
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
void Respond(
|
||||
string text,
|
||||
int code = 200,
|
||||
string contentType = "text/plain");
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
void Respond(
|
||||
byte[] data,
|
||||
HttpStatusCode code = HttpStatusCode.OK,
|
||||
string contentType = "text/plain");
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
void Respond(
|
||||
byte[] data,
|
||||
int code = 200,
|
||||
string contentType = "text/plain");
|
||||
|
||||
Task RespondNoContentAsync();
|
||||
|
||||
Task RespondAsync(
|
||||
@@ -74,14 +48,8 @@ namespace Robust.Server.ServerStatus
|
||||
int code = 200,
|
||||
string contentType = "text/plain");
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
void RespondError(HttpStatusCode code);
|
||||
|
||||
Task RespondErrorAsync(HttpStatusCode code);
|
||||
|
||||
[Obsolete("Use async versions instead")]
|
||||
void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK);
|
||||
|
||||
Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK);
|
||||
|
||||
Task<Stream> RespondStreamAsync(HttpStatusCode code = HttpStatusCode.OK);
|
||||
|
||||
@@ -27,19 +27,6 @@ namespace Robust.Server.ServerStatus
|
||||
/// </summary>
|
||||
event Action<JsonNode> OnInfoRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Set information used by automatic-client-zipping to determine the layout of your dev setup,
|
||||
/// and which assembly files to send.
|
||||
/// </summary>
|
||||
/// <param name="clientBinFolder">
|
||||
/// The name of your client project in the bin/ folder on the top of your project.
|
||||
/// </param>
|
||||
/// <param name="clientAssemblyNames">
|
||||
/// The list of client assemblies to send from the aforementioned folder.
|
||||
/// </param>
|
||||
[Obsolete("This API is deprecated as it cannot share information with standalone packaging. Use SetMagicAczProvider instead")]
|
||||
void SetAczInfo(string clientBinFolder, string[] clientAssemblyNames);
|
||||
|
||||
void SetMagicAczProvider(IMagicAczProvider provider);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,12 +2,92 @@ using System;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// API for interacting with <c>SS14.Watchdog</c>.
|
||||
/// </summary>
|
||||
public interface IWatchdogApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when the game server should restart for an update.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This only indicates that the game server should restart as soon as possible without disruption,
|
||||
/// e.g. at the end of a round. It should not shut down immediately unless possible.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This the same event as <see cref="RestartRequested"/>, but without additional data available such as reason.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
event Action UpdateReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the watchdog has indicated that the server should restart as soon as possible.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This only indicates that the game server should restart as soon as possible without disruption,
|
||||
/// e.g. at the end of a round. It should not shut down immediately unless possible.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This the same event as <see cref="UpdateReceived"/>, but with additional data.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
event Action<RestartRequestedData> RestartRequested;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Engine-internal API for <see cref="IWatchdogApi"/>.
|
||||
/// </summary>
|
||||
internal interface IWatchdogApiInternal : IWatchdogApi
|
||||
{
|
||||
void Heartbeat();
|
||||
|
||||
void Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data used by <see cref="IWatchdogApi.RestartRequested"/>.
|
||||
/// </summary>
|
||||
public sealed class RestartRequestedData
|
||||
{
|
||||
internal static readonly RestartRequestedData DefaultData = new(RestartRequestedReason.UpdateAvailable, null);
|
||||
|
||||
/// <summary>
|
||||
/// Primary reason code for why the server should be restarted.
|
||||
/// </summary>
|
||||
public RestartRequestedReason Reason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A message provided with additional information about the restart reason. Not always provided.
|
||||
/// </summary>
|
||||
public string? AdditionalMessage { get; }
|
||||
|
||||
internal RestartRequestedData(RestartRequestedReason reason, string? additionalMessage)
|
||||
{
|
||||
Reason = reason;
|
||||
AdditionalMessage = additionalMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primary reason codes for why the server should restart in <see cref="RestartRequestedData"/>.
|
||||
/// </summary>
|
||||
public enum RestartRequestedReason : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Restart reason does not fall in an existing category.
|
||||
/// </summary>
|
||||
Other = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The server should restart because an update is available.
|
||||
/// </summary>
|
||||
UpdateAvailable,
|
||||
|
||||
/// <summary>
|
||||
/// The server should restart for maintenance.
|
||||
/// </summary>
|
||||
Maintenance,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,22 +171,6 @@ internal sealed partial class StatusHost
|
||||
|
||||
// -- Information Input --
|
||||
|
||||
public void SetAczInfo(string clientBinFolder, string[] clientAssemblyNames)
|
||||
{
|
||||
_aczLock.Wait();
|
||||
try
|
||||
{
|
||||
if (_aczPrepared != null)
|
||||
throw new InvalidOperationException("ACZ already prepared");
|
||||
|
||||
_aczInfo = (clientBinFolder, clientAssemblyNames);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_aczLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMagicAczProvider(IMagicAczProvider provider)
|
||||
{
|
||||
_magicAczProvider = provider;
|
||||
|
||||
@@ -269,57 +269,11 @@ namespace Robust.Server.ServerStatus
|
||||
_responseHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public T? RequestBodyJson<T>()
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(RequestBody);
|
||||
}
|
||||
|
||||
public async Task<T?> RequestBodyJsonAsync<T>()
|
||||
{
|
||||
return await JsonSerializer.DeserializeAsync<T>(RequestBody);
|
||||
}
|
||||
|
||||
public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
Respond(text, (int)code, contentType);
|
||||
}
|
||||
|
||||
public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
_context.Response.StatusCode = code;
|
||||
_context.Response.ContentType = contentType;
|
||||
|
||||
if (RequestMethod == HttpMethod.Head)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var writer = new StreamWriter(_context.Response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
writer.Write(text);
|
||||
}
|
||||
|
||||
public void Respond(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
Respond(data, (int)code, contentType);
|
||||
}
|
||||
|
||||
public void Respond(byte[] data, int code = 200, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
_context.Response.StatusCode = code;
|
||||
_context.Response.ContentType = contentType;
|
||||
_context.Response.ContentLength64 = data.Length;
|
||||
|
||||
if (RequestMethod == HttpMethod.Head)
|
||||
{
|
||||
_context.Response.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_context.Response.OutputStream.Write(data);
|
||||
_context.Response.Close();
|
||||
}
|
||||
|
||||
public Task RespondNoContentAsync()
|
||||
{
|
||||
RespondShared();
|
||||
@@ -373,27 +327,11 @@ namespace Robust.Server.ServerStatus
|
||||
_context.Response.Close();
|
||||
}
|
||||
|
||||
public void RespondError(HttpStatusCode code)
|
||||
{
|
||||
Respond(code.ToString(), code);
|
||||
}
|
||||
|
||||
public Task RespondErrorAsync(HttpStatusCode code)
|
||||
{
|
||||
return RespondAsync(code.ToString(), code);
|
||||
}
|
||||
|
||||
public void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK)
|
||||
{
|
||||
RespondShared();
|
||||
|
||||
_context.Response.ContentType = "application/json";
|
||||
|
||||
JsonSerializer.Serialize(_context.Response.OutputStream, jsonData);
|
||||
|
||||
_context.Response.Close();
|
||||
}
|
||||
|
||||
public async Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK)
|
||||
{
|
||||
RespondShared();
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,11 +16,9 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
public sealed class WatchdogApi : IWatchdogApi, IPostInjectInit
|
||||
internal sealed class WatchdogApi : IWatchdogApiInternal, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IStatusHost _statusHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
@@ -42,7 +41,7 @@ namespace Robust.Server.ServerStatus
|
||||
HttpClientUserAgent.AddUserAgent(_httpClient);
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = Logger.GetSawmill("watchdogApi");
|
||||
|
||||
@@ -52,7 +51,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
private async Task<bool> UpdateHandler(IStatusHandlerContext context)
|
||||
{
|
||||
if (context.RequestMethod != HttpMethod.Post || context.Url!.AbsolutePath != "/update")
|
||||
if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/update")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -73,7 +72,46 @@ namespace Robust.Server.ServerStatus
|
||||
return true;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => UpdateReceived?.Invoke());
|
||||
RestartRequestParameters? parameters = null;
|
||||
if (context.RequestHeaders.TryGetValue("Content-Type", out var contentType)
|
||||
&& contentType == MediaTypeNames.Application.Json)
|
||||
{
|
||||
try
|
||||
{
|
||||
parameters = await context.RequestBodyJsonAsync<RestartRequestParameters>();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// parameters null so it'll catch the block down below.
|
||||
}
|
||||
|
||||
if (parameters == null)
|
||||
{
|
||||
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
RestartRequestedData restartData;
|
||||
if (parameters == null)
|
||||
{
|
||||
restartData = RestartRequestedData.DefaultData;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow parsing to fail for forwards compatibility.
|
||||
var reasonCode = Enum.TryParse<RestartRequestedReason>(parameters.Reason, out var code)
|
||||
? code
|
||||
: RestartRequestedReason.Other;
|
||||
|
||||
restartData = new RestartRequestedData(reasonCode, parameters.Message);
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() =>
|
||||
{
|
||||
RestartRequested?.Invoke(restartData);
|
||||
UpdateReceived?.Invoke();
|
||||
});
|
||||
|
||||
await context.RespondAsync("Success", HttpStatusCode.OK);
|
||||
|
||||
@@ -86,7 +124,7 @@ namespace Robust.Server.ServerStatus
|
||||
/// </remarks>
|
||||
private async Task<bool> ShutdownHandler(IStatusHandlerContext context)
|
||||
{
|
||||
if (context.RequestMethod != HttpMethod.Post || context.Url!.AbsolutePath != "/shutdown")
|
||||
if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/shutdown")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -106,7 +144,8 @@ namespace Robust.Server.ServerStatus
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
_sawmill.Verbose(
|
||||
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth,
|
||||
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}",
|
||||
auth,
|
||||
_watchdogToken);
|
||||
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
|
||||
return true;
|
||||
@@ -137,6 +176,7 @@ namespace Robust.Server.ServerStatus
|
||||
}
|
||||
|
||||
public event Action? UpdateReceived;
|
||||
public event Action<RestartRequestedData>? RestartRequested;
|
||||
|
||||
public async void Heartbeat()
|
||||
{
|
||||
@@ -204,5 +244,12 @@ namespace Robust.Server.ServerStatus
|
||||
// ReSharper disable once RedundantDefaultMemberInitializer
|
||||
public string Reason { get; set; } = default!;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class RestartRequestParameters
|
||||
{
|
||||
public string Reason { get; set; } = nameof(RestartRequestedReason.Other);
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public record struct NoSuchPlayerError(string Username) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found.");
|
||||
return FormattedMessage.FromUnformatted($"No player with the username/GUID {Username} could be found.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
|
||||
@@ -87,33 +87,33 @@ namespace Robust.Shared.Scripting
|
||||
|
||||
public object? prop(object target, string name)
|
||||
{
|
||||
return target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
!.GetValue(target);
|
||||
var prop = (PropertyInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Property, name);
|
||||
return prop!.GetValue(target);
|
||||
}
|
||||
|
||||
public void setprop(object target, string name, object? value)
|
||||
{
|
||||
target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
|
||||
!.SetValue(target, value);
|
||||
var prop = (PropertyInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Property, name);
|
||||
prop!.SetValue(target, value);
|
||||
}
|
||||
|
||||
public object? fld(object target, string name)
|
||||
{
|
||||
return target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
|
||||
!.GetValue(target);
|
||||
var fld = (FieldInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Field, name);
|
||||
return fld!.GetValue(target);
|
||||
}
|
||||
|
||||
public void setfld(object target, string name, object? value)
|
||||
{
|
||||
target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
|
||||
!.SetValue(target, value);
|
||||
var fld = (FieldInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Field, name);
|
||||
fld!.SetValue(target, value);
|
||||
}
|
||||
|
||||
public object? call(object target, string name, params object[] args)
|
||||
{
|
||||
var t = target.GetType();
|
||||
// TODO: overloads
|
||||
var m = t.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
var m = (MethodInfo?) ReflectionGetInstanceMember(t, MemberTypes.Method, name);
|
||||
return m!.Invoke(target, args);
|
||||
}
|
||||
|
||||
@@ -206,8 +206,11 @@ namespace Robust.Shared.Scripting
|
||||
public void Dirty(EntityUid uid)
|
||||
=> ent.DirtyEntity(uid);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Remove this helper when component.Owner finally gets removed.
|
||||
public void Dirty(Component comp)
|
||||
=> ent.Dirty(comp.Owner, comp);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
public string Name(EntityUid uid)
|
||||
=> ent.GetComponent<MetaDataComponent>(uid).EntityName;
|
||||
@@ -284,5 +287,21 @@ namespace Robust.Shared.Scripting
|
||||
}
|
||||
|
||||
public Dictionary<string, object?> Variables { get; } = new();
|
||||
|
||||
private static MemberInfo? ReflectionGetInstanceMember(Type type, MemberTypes memberType, string name)
|
||||
{
|
||||
for (var curType = type; curType != null; curType = curType.BaseType)
|
||||
{
|
||||
var member = curType.GetMember(
|
||||
name,
|
||||
memberType,
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (member.Length > 0)
|
||||
return member[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -14,14 +15,13 @@ using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Scripting
|
||||
{
|
||||
internal static class ScriptInstanceShared
|
||||
internal static partial class ScriptInstanceShared
|
||||
{
|
||||
public static CSharpParseOptions ParseOptions { get; } =
|
||||
new(kind: SourceCodeKind.Script, languageVersion: LanguageVersion.Latest);
|
||||
@@ -186,11 +186,12 @@ namespace Robust.Shared.Scripting
|
||||
var assemblies = ScriptInstanceShared.GetAutoImportAssemblies(refl).ToArray();
|
||||
foreach (var m in missing)
|
||||
{
|
||||
var mName = ConvertMissingTypeName(m);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
foreach (var type in assembly.DefinedTypes)
|
||||
{
|
||||
if (type.IsPublic && type.Name == m)
|
||||
if (type.IsPublic && type.Name == mName)
|
||||
{
|
||||
found.Add(type.Namespace!);
|
||||
goto nextMissing;
|
||||
@@ -225,5 +226,22 @@ namespace Robust.Shared.Scripting
|
||||
return "<CSharpObjectFormatter.FormatObject threw>";
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertMissingTypeName(string name)
|
||||
{
|
||||
var match = TypeMissingParserRegex().Match(name);
|
||||
var typeName = match.Groups[1].Value;
|
||||
if (match.Groups[2].Success)
|
||||
{
|
||||
// We have generics
|
||||
var genericCount = match.Groups[2].Length + 1;
|
||||
return $"{typeName}`{genericCount}";
|
||||
}
|
||||
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
[GeneratedRegex("^(.+?)(?:<(,*)>)?$")]
|
||||
private static partial Regex TypeMissingParserRegex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,6 @@ public abstract partial class SoundSpecifier
|
||||
{
|
||||
[DataField("params")]
|
||||
public AudioParams Params { get; set; } = AudioParams.Default;
|
||||
|
||||
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
|
||||
public abstract string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -45,12 +42,6 @@ public sealed partial class SoundPathSpecifier : SoundSpecifier
|
||||
if (@params.HasValue)
|
||||
Params = @params.Value;
|
||||
}
|
||||
|
||||
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
|
||||
public override string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null)
|
||||
{
|
||||
return Path.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -70,15 +61,4 @@ public sealed partial class SoundCollectionSpecifier : SoundSpecifier
|
||||
if (@params.HasValue)
|
||||
Params = @params.Value;
|
||||
}
|
||||
|
||||
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
|
||||
public override string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null)
|
||||
{
|
||||
if (Collection == null)
|
||||
return string.Empty;
|
||||
|
||||
IoCManager.Resolve(ref rand, ref proto);
|
||||
var soundCollection = proto.Index<SoundCollectionPrototype>(Collection);
|
||||
return rand.Pick(soundCollection.PickFiles).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,7 +417,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -425,10 +424,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, sound.Params);
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -436,7 +434,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -444,10 +441,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient)
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params);
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
public abstract void LoadStream<T>(Entity<AudioComponent> entity, T stream);
|
||||
@@ -457,7 +453,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -465,10 +460,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params);
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -477,7 +471,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -486,7 +479,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -495,7 +487,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -504,7 +495,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayEntity(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params);
|
||||
@@ -516,7 +506,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params);
|
||||
@@ -528,7 +517,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params);
|
||||
@@ -539,7 +527,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params);
|
||||
@@ -550,7 +537,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params);
|
||||
@@ -561,7 +547,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename,
|
||||
EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
@@ -570,10 +555,17 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid,
|
||||
AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Plays a predicted sound following an entity for only one entity. The client-side system plays this sound as normal, server will do nothing.
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="source">The UID of the entity "emitting" the audio.</param>
|
||||
/// <param name="soundInitiator">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayLocal(SoundSpecifier? sound, EntityUid source, EntityUid? soundInitiator, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range,
|
||||
/// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays
|
||||
@@ -582,7 +574,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="source">The UID of the entity "emitting" the audio.</param>
|
||||
/// <param name="user">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -593,7 +584,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="coordinates">The entitycoordinates "emitting" the audio</param>
|
||||
/// <param name="user">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -602,7 +592,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -611,7 +600,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -620,7 +608,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -629,7 +616,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams);
|
||||
@@ -641,7 +627,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params);
|
||||
@@ -653,7 +638,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("sound")]
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params);
|
||||
|
||||
@@ -394,6 +394,18 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<int> NetEncryptionThreadChannelSize =
|
||||
CVarDef.Create("net.encryption_thread_channel_size", 16);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the server should request HWID system for client identification.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Note that modern HWIDs are only available if the connection is authenticated.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> NetHWId =
|
||||
CVarDef.Create("net.hwid", true, CVar.SERVERONLY);
|
||||
|
||||
|
||||
/**
|
||||
* SUS
|
||||
*/
|
||||
@@ -671,6 +683,13 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<string> BuildManifestHash =
|
||||
CVarDef.Create("build.manifest_hash", "");
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to disable the display of all entities in the spawn menu that are not labeled with the ShowSpawnMenu category.
|
||||
/// This is useful for forks that just want to disable the standard upstream content
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> EntitiesCategoryFilter =
|
||||
CVarDef.Create("build.entities_category_filter", "");
|
||||
|
||||
/*
|
||||
* WATCHDOG
|
||||
*/
|
||||
@@ -1288,7 +1307,7 @@ namespace Robust.Shared
|
||||
/// Default is 35 m/s. Around half a tile per tick at 60 ticks per second.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> MaxLinVelocity =
|
||||
CVarDef.Create("physics.maxlinvelocity", 35f);
|
||||
CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum angular velocity in full rotations per second.
|
||||
@@ -1636,6 +1655,16 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<long> ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size",
|
||||
1024L * 1024, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Size of the replay (in kilobytes) at which point the replay is considered "large",
|
||||
/// and replay clients should enable server GC (if possible) to improve performance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set to -1 to never make replays use server GC.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<long> ReplayServerGCSizeThreshold =
|
||||
CVarDef.Create("replay.server_gc_size_threshold", 50L * 1024);
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed size of individual files created by the replay (in kilobytes), where each file contains data
|
||||
/// for one or more ticks. Actual files may be slightly larger, this is just a threshold for the file to get
|
||||
|
||||
@@ -30,8 +30,13 @@ namespace Robust.Shared.Console
|
||||
bool IsServer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The remote peer that owns this shell, or the local player if this is a client-side local shell (<see cref="IsLocal" /> is true and <see cref="IsClient"/> is true).
|
||||
/// The remote peer that owns this shell, or the local player if this is a local shell.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This parameter is null for commands executed directly from the server console, as that has no player.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
ICommonSession? Player { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace Robust.Shared.Containers
|
||||
[RegisterComponent, ComponentProtoName("ContainerContainer")]
|
||||
public sealed partial class ContainerManagerComponent : Component, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
[DataField("containers")]
|
||||
@@ -34,19 +33,6 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public T MakeContainer<T>(EntityUid uid, string id)
|
||||
where T : BaseContainer
|
||||
=> _entMan.System<SharedContainerSystem>().MakeContainer<T>(uid, id, this);
|
||||
|
||||
[Obsolete]
|
||||
public BaseContainer GetContainer(string id)
|
||||
=> _entMan.System<SharedContainerSystem>().GetContainer(Owner, id, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool HasContainer(string id)
|
||||
=> _entMan.System<SharedContainerSystem>().HasContainer(Owner, id, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? container)
|
||||
=> _entMan.System<SharedContainerSystem>().TryGetContainer(Owner, id, out container, this);
|
||||
@@ -55,20 +41,6 @@ namespace Robust.Shared.Containers
|
||||
public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out BaseContainer? container)
|
||||
=> _entMan.System<SharedContainerSystem>().TryGetContainingContainer(Owner, entity, out container, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool ContainsEntity(EntityUid entity)
|
||||
=> _entMan.System<SharedContainerSystem>().ContainsEntity(Owner, entity, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool Remove(EntityUid toremove,
|
||||
TransformComponent? xform = null,
|
||||
MetaDataComponent? meta = null,
|
||||
bool reparent = true,
|
||||
bool force = false,
|
||||
EntityCoordinates? destination = null,
|
||||
Angle? localRotation = null)
|
||||
=> _entMan.System<SharedContainerSystem>().RemoveEntity(Owner, toremove, this, xform, meta, reparent, force, destination, localRotation);
|
||||
|
||||
[Obsolete]
|
||||
public AllContainersEnumerable GetAllContainers()
|
||||
=> _entMan.System<SharedContainerSystem>().GetAllContainers(Owner, this);
|
||||
|
||||
@@ -193,16 +193,6 @@ namespace Robust.Shared.Containers
|
||||
return true;
|
||||
}
|
||||
|
||||
[Obsolete("Use variant without skipExistCheck argument")]
|
||||
public bool TryGetContainingContainer(
|
||||
EntityUid uid,
|
||||
EntityUid containedUid,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
bool skipExistCheck)
|
||||
{
|
||||
return TryGetContainingContainer(uid, containedUid, out container);
|
||||
}
|
||||
|
||||
public bool TryGetContainingContainer(
|
||||
EntityUid uid,
|
||||
EntityUid containedUid,
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user