Compare commits

..

4 Commits

Author SHA1 Message Date
Pieter-Jan Briers
8d926d0f97 Version: 220.2.2 2024-08-11 19:32:34 +02:00
Pieter-Jan Briers
4cbbb6f848 Compile compat fixes
(cherry picked from commit 025d90d281)
(cherry picked from commit 799702b814)
(cherry picked from commit 4600ee8e5788891f1b610e2d5141fb4e1228d323)
2024-08-11 19:32:34 +02:00
Pieter-Jan Briers
d9a4e0d628 Version: 220.2.1 2024-08-11 17:56:08 +02:00
Pieter-Jan Briers
d282c46d44 Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

(cherry picked from commit 7d778248ee)
(cherry picked from commit f66cda74e95619ddba2221bda644bf4394619805)
(cherry picked from commit db8ba83866c523e08e4fba0b80cd954f4f190613)
2024-08-11 17:56:08 +02:00
131 changed files with 1158 additions and 2459 deletions

View File

@@ -7,18 +7,6 @@ indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 120
# ReSharper properties
resharper_csharp_max_line_length = 120
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true
resharper_place_field_attribute_on_same_line = if_owner_is_single_line
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
[*.{csproj,xml,yml,dll.config,targets,props}]
indent_size = 2

View File

@@ -10,7 +10,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}

View File

@@ -55,7 +55,7 @@
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />

View File

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

View File

@@ -54,183 +54,10 @@ END TEMPLATE-->
*None yet*
## 223.2.0
## 220.2.2
### New features
* Added several new `FormattedMessage` methods for better exception tolerance when parsing markup. Several existing methods have been marked as obsolete, with new renamed methods taking their place.
## 223.1.2
### Bugfixes
* `MapGridComponent.LastTileModifiedTick` is now actually networked to clients.
## 223.1.1
### Bugfixes
* Fixed an exception caused by enum cvars using integer type values instead of enum values
## 223.1.0
### Other
* Various `ContainerSystem` methods have been obsoleted in favour of overrides that take in an `Entity` struct instead of `EntityUid`
* Various `EntityCoordinates` methods have been obsoleted with replacements added to `SharedTransformSystem`
## 223.0.0
### Breaking changes
* The `ComponentState` class is now abstract. Networked components that don't have state information now just return a null state.
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFullState>`
### New features
* A new `replay.checkpoint_min_interval` cvar has been added. It can be used to limit the frequency at which checkpoints are generated when loading a replay.
* Added `IConfigurationManager.OnCVarValueChanged`. This is a c# event that gets invoked whenever any cvar value changes.
### Bugfixes
* `IEyeManager.GetWorldViewbounds()` and `IEyeManager.GetWorldViewbounds()` should now return the correct bounds if the main viewport does not take up the whole screen.
### Other
* The default values of various replay related cvars have been changed to try and reduce memory usage.
## 222.4.0
### New features
* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`.
## 222.3.0
### New features
* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel.
* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work.
### Bugfixes
* Cross-map BUI range checks now work.
* Paused entities update on prototype reload.
### Other
* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves.
* Physics component has delta states to reduce network usage.
## 222.2.0
### New features
* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`)
### Bugfixes
* Fix `SerializationManager.TryGetVariableType` checking the wrong property.
* Fixed GrammarSystem mispredicting a character's gender
### Other
* User interface system now performs range checks in parallel
## 222.1.1
### Bugfixes
* Fixed never setting BoundUserInterface.State.
### Other
* Add truncate for filesaving.
* Add method for getting the type of a data field by name from ISerializationManager.
## 222.1.0
### New features
* Added `BoundKeyEventArgs.IsRepeat`.
* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars.
### Bugfixes
* Fix assert trip when holding repeatable keybinds.
### Other
* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled.
## 222.0.0
### Breaking changes
* Mark IComponentFactory argument in EntityPrototype as mandatory.
### New features
* Add `EntProtoId<T>` to check for components on the attached entity as well.
### Bugfixes
* Fix PVS iterating duplicate chunks for multiple viewsubscriptions.
### Other
* Defer clientside BUI opens if it's the first state that comes in.
## 221.2.0
### New features
* Add SetMapAudio helper to SharedAudioSystem to setup map-wide audio entities.
* Add SetWorldRotNoLerp method to SharedTransformSystem to avoid client lerping.
### Bugfixes
* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration.
## 221.1.0
## 221.0.0
### Breaking changes
* `EntParentChangedMessage.OldMapId` is now an `EntityUid` instead of `MapId`
* `TransformSystem.DetachParentToNull()` is being renamed to `DetachEntity`
* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions
### New features
* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control.
* Add SwapPositions to TransformSystem to swap two entity's transforms.
### Bugfixes
* Improve client gamestate exception tolerance.
### Other
* If the currently hovered control is disposed, `UserInterfaceManager` will now look for a new control, rather than just setting the hovered control to null.
### Internal
* Use more `EntityQuery<T>` internally in EntityManager and PhysicsSystem.
## 220.2.1
## 220.2.0
@@ -258,7 +85,7 @@ END TEMPLATE-->
### Breaking changes
* Refactor UserInterfaceSystem.
* Refactor UserInterfaceSystem.
- The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate.
- Interface data is now stored via key rather than as a flat list which is a breaking change for YAML.
- BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before.

View File

@@ -1,57 +0,0 @@
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.NoUncachedRegexAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class NoUncachedRegexAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
// 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 System.Text.RegularExpressions;
public static class Foo
{
public static void Bad()
{
Regex.Replace("foo", "bar", "baz");
}
public static void Good()
{
var r = new Regex("bar");
r.Replace("foo", "baz");
}
}
""";
await Verifier(code,
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
);
}
}

View File

@@ -6,7 +6,6 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Robust.Shared.Serialization.Manager.Definition;
namespace Robust.Analyzers;
@@ -57,18 +56,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to add a setter."
);
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
Diagnostics.IdDataFieldRedundantTag,
"Data field has redundant tag specified",
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
"Usage",
DiagnosticSeverity.Info,
true,
"Make sure to remove the tag string from the data field attribute."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
DataFieldRedundantTagRule
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
);
public override void Initialize(AnalysisContext context)
@@ -136,11 +125,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
}
}
@@ -165,11 +149,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
}
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
@@ -269,29 +248,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool HasRedundantTag(ISymbol symbol)
{
if (!IsDataField(symbol, out var _, out var attribute))
return false;
// No args, no problem
if (attribute.ConstructorArguments.Length == 0)
return false;
// If a tag is explicitly specified, it will be the first argument...
var tagArgument = attribute.ConstructorArguments[0];
// ...but the first arg could also something else, since tag is optional
// so we make sure that it's a string
if (tagArgument.Value is not string explicitName)
return false;
// Get the name that sourcegen would provide
var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name);
// If the explicit name matches the sourcegen name, we have a redundancy
return explicitName == automaticName;
}
private static bool IsImplicitDataDefinition(ITypeSymbol type)
{
if (HasAttribute(type, ImplicitDataDefinitionNamespace))

View File

@@ -1,5 +1,8 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
@@ -13,11 +16,8 @@ namespace Robust.Analyzers;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class DefinitionFixer : CodeFixProvider
{
private const string DataFieldAttributeName = "DataField";
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
IdDataFieldRedundantTag
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
@@ -34,8 +34,6 @@ public sealed class DefinitionFixer : CodeFixProvider
return RegisterDataFieldFix(context, diagnostic);
case IdDataFieldPropertyWritable:
return RegisterDataFieldPropertyFix(context, diagnostic);
case IdDataFieldRedundantTag:
return RegisterRedundantTagFix(context, diagnostic);
}
}
@@ -74,68 +72,6 @@ public sealed class DefinitionFixer : CodeFixProvider
return document.WithSyntaxRoot(root);
}
private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
if (token == null)
return;
// Find the DataField attribute
AttributeSyntax? dataFieldAttribute = null;
foreach (var attributeList in token.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString() == DataFieldAttributeName)
{
dataFieldAttribute = attribute;
break;
}
}
if (dataFieldAttribute != null)
break;
}
if (dataFieldAttribute == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Remove explicitly set tag",
c => RemoveRedundantTag(context.Document, dataFieldAttribute, c),
"Remove explicitly set tag"
), diagnostic);
}
private static async Task<Document> RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
if (syntax.ArgumentList == null)
return document;
AttributeSyntax? newSyntax;
if (syntax.ArgumentList.Arguments.Count == 1)
{
// If this is the only argument, delete the ArgumentList so we don't leave empty parentheses
newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia);
}
else
{
// Remove the first argument, which is the tag
var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0);
var newArgList = syntax.ArgumentList.WithArguments(newArgs);
// Construct a new attribute with the tag removed
newSyntax = syntax.WithArgumentList(newArgList);
}
root = root!.ReplaceNode(syntax, newSyntax!);
return document.WithSyntaxRoot(root);
}
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);

View File

@@ -1,66 +0,0 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer
{
private const string RegexTypeName = "Regex";
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}";
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdUncachedRegex,
"Use of uncached static Regex function",
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public static readonly HashSet<string> BadFunctions =
[
"Count",
"EnumerateMatches",
"IsMatch",
"Match",
"Matches",
"Replace",
"Split"
];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation);
}
private static void CheckInvocation(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation invocation)
return;
// All Regex functions we care about are static.
var targetMethod = invocation.TargetMethod;
if (!targetMethod.IsStatic)
return;
// Bail early.
if (targetMethod.ContainingType.Name != "Regex")
return;
var regexType = context.Compilation.GetTypeByMetadataName(RegexType);
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType))
return;
if (!BadFunctions.Contains(targetMethod.Name))
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation()));
}
}

View File

@@ -16,11 +16,6 @@
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for DataDefinitionAnalyzer. -->
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>

View File

@@ -239,7 +239,6 @@ namespace Robust.Build.Tasks
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
res.Remove();
}
return true;
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var type = Type.GetType(args[0]);
var type = GetType(args[0]);
if (type == null)
{
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
shell.WriteLine(sig);
}
}
private Type? GetType(string name)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(name) is { } type)
return type;
}
return null;
}
}
#endif
}

View File

@@ -225,7 +225,7 @@ namespace Robust.Client.Debugging
{
var viewBounds = args.WorldBounds;
var viewAABB = args.WorldAABB;
var mapId = args.MapId;
var mapId = _eyeManager.CurrentMap;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
{
@@ -373,7 +373,7 @@ namespace Robust.Client.Debugging
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
{
var mapId = args.MapId;
var mapId = _eyeManager.CurrentMap;
var mousePos = _inputManager.MouseScreenPosition;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)

View File

@@ -1681,8 +1681,6 @@ namespace Robust.Client.GameObjects
DirOffset = toClone.DirOffset;
_autoAnimated = toClone._autoAnimated;
RenderingStrategy = toClone.RenderingStrategy;
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
}
void ISerializationHooks.AfterDeserialization()
@@ -2157,12 +2155,6 @@ namespace Robust.Client.GameObjects
public object LayerKey = layerKey;
public string? ParameterTexture;
public string? ParameterUV;
public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey)
{
ParameterTexture = toClone.ParameterTexture;
ParameterUV = toClone.ParameterUV;
}
}
void IAnimationProperties.SetAnimatableProperty(string name, object value)

View File

@@ -120,7 +120,7 @@ namespace Robust.Client.GameObjects
{
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
container.Init(this, id, (uid, component));
InitContainer(container, (uid, component), id);
component.Containers.Add(id, container);
}

View File

@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = args.MapId;
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
foreach (var (_, treeComp) in _trees.GetIntersectingTrees(map, args.WorldBounds))

View File

@@ -70,7 +70,7 @@ namespace Robust.Client.GameObjects
protected internal override void Draw(in OverlayDrawArgs args)
{
var currentMap = args.MapId;
var currentMap = _eyeManager.CurrentMap;
var viewport = args.WorldBounds;
var worldHandle = args.WorldHandle;

View File

@@ -54,11 +54,11 @@ namespace Robust.Client.GameStates
private readonly HashSet<NetEntity> _stateEnts = new();
private readonly List<EntityUid> _toDelete = new();
private readonly List<IComponent> _toRemove = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
private uint _metaCompNetId;
@@ -125,8 +125,6 @@ namespace Robust.Client.GameStates
#endif
private bool _resettingPredictedEntities;
private readonly List<EntityUid> _brokenEnts = new();
private readonly List<(EntityUid, NetEntity)> _toStart = new();
/// <inheritdoc />
public void Initialize()
@@ -259,7 +257,7 @@ namespace Robust.Client.GameStates
public void UpdateFullRep(GameState state, bool cloneDelta = false)
=> _processor.UpdateFullRep(state, cloneDelta);
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
=> _processor.GetFullRep();
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
@@ -600,12 +598,8 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
if (compState != null)
{
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
}
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.LastModifiedTick = _timing.LastRealTick;
}
}
@@ -637,12 +631,8 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
if (state != null)
{
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
}
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
comp.ClearCreationTick(); // don't undo the re-adding.
comp.LastModifiedTick = _timing.LastRealTick;
}
@@ -677,16 +667,7 @@ namespace Robust.Client.GameStates
foreach (var netEntity in createdEntities)
{
#if EXCEPTION_TOLERANCE
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
{
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
continue;
}
#else
var (_, meta) = _entityManager.GetEntityData(netEntity);
#endif
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
@@ -695,7 +676,7 @@ namespace Robust.Client.GameStates
DebugTools.Assert(component.NetSyncEnabled);
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
compData.Add(netId, state);
}
}
@@ -895,22 +876,9 @@ namespace Robust.Client.GameStates
{
foreach (var (entity, data) in _toApply)
{
#if EXCEPTION_TOLERANCE
try
{
#endif
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
_entityManager.DeleteEntity(entity);
RequestFullState();
continue;
}
#endif
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
if (!data.EnteringPvs)
continue;
@@ -949,7 +917,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, metas, xformSys);
ProcessDeletions(delSpan, xforms, xformSys);
}
catch (Exception e)
{
@@ -994,7 +962,6 @@ namespace Robust.Client.GameStates
}
var xforms = _entities.GetEntityQuery<TransformComponent>();
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_toDelete.Clear();
@@ -1023,12 +990,12 @@ namespace Robust.Client.GameStates
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
xformSys.DetachEntity(ent, xform);
xformSys.DetachParentToNull(ent, xform);
// Then detach all children.
foreach (var child in xform._children)
{
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
@@ -1047,9 +1014,9 @@ namespace Robust.Client.GameStates
}
}
private void ProcessDeletions(ReadOnlySpan<NetEntity> delSpan,
private void ProcessDeletions(
ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
@@ -1076,13 +1043,13 @@ namespace Robust.Client.GameStates
continue; // Already deleted? or never sent to us?
// First, a single recursive map change
xformSys.DetachEntity(id.Value, xform);
xformSys.DetachParentToNull(id.Value, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
}
// Finally, delete the entity.
@@ -1177,7 +1144,7 @@ namespace Robust.Client.GameStates
}
meta._flags |= MetaDataFlags.Detached;
xformSys.DetachEntity(ent.Value, xform);
xformSys.DetachParentToNull(ent.Value, xform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
@@ -1190,58 +1157,63 @@ namespace Robust.Client.GameStates
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
{
_toStart.Clear();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
#if EXCEPTION_TOLERANCE
var brokenEnts = new List<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
EntityUid entity = default;
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
(entity, var meta) = _entityManager.GetEntityData(netEntity);
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, netEntity));
#endif
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
}
#endif
}
}
using (_prof.Group("Start Entity"))
{
foreach (var (entity, netEntity) in _toStart)
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
_entities.StartEntity(entity);
#endif
_entities.StartEntity(entity);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
}
#endif
}
}
foreach (var entity in _brokenEnts)
#if EXCEPTION_TOLERANCE
foreach (var entity in brokenEnts)
{
_entityManager.DeleteEntity(entity);
}
_brokenEnts.Clear();
#endif
}
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
@@ -1357,9 +1329,6 @@ namespace Robust.Client.GameStates
foreach (var (comp, cur, next) in _compStateWork.Values)
{
if (cur == null && next == null)
continue;
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
@@ -1433,7 +1402,7 @@ namespace Robust.Client.GameStates
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
}
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachEntity(uid, xform);
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
if (container != null)
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
@@ -1512,9 +1481,6 @@ namespace Robust.Client.GameStates
_entityManager.AddComponent(uid, comp, true, meta);
}
if (state == null)
continue;
var handleState = new ComponentHandleState(state, null);
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
}

View File

@@ -32,7 +32,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
/// </summary>
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _lastStateFullRep
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
= new();
/// <inheritdoc />
@@ -212,7 +212,7 @@ Had full state: {LastFullState != null}"
{
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
{
compData = new();
compData = new Dictionary<ushort, IComponentState>();
_lastStateFullRep.Add(entityState.NetEntity, compData);
}
@@ -221,20 +221,21 @@ Had full state: {LastFullState != null}"
var compState = change.State;
if (compState is IComponentDeltaState delta
&& !delta.FullState
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
{
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
DebugTools.Assert(old is IComponentDeltaState oldDelta && oldDelta.FullState, "last state is not a full state");
if (cloneDelta)
{
compState = delta.CreateNewFullState(old!);
compState = delta.CreateNewFullState(old);
}
else
{
delta.ApplyToFullState(old!);
delta.ApplyToFullState(old);
compState = old;
}
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
DebugTools.Assert(compState is IComponentDeltaState newState && newState.FullState, "newly constructed state is not a full state");
}
compData[change.NetID] = compState;
@@ -390,7 +391,7 @@ Had full state: {LastFullState != null}"
LastFullStateRequested = null;
}
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> implicitData)
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
{
foreach (var (netEntity, implicitEntState) in implicitData)
{
@@ -398,7 +399,6 @@ Had full state: {LastFullState != null}"
foreach (var (netId, implicitCompState) in implicitEntState)
{
DebugTools.Assert(implicitCompState is not IComponentDeltaState);
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
if (!exists)
@@ -407,32 +407,36 @@ Had full state: {LastFullState != null}"
continue;
}
if (serverState is not IComponentDeltaState serverDelta)
if (serverState is not IComponentDeltaState serverDelta || serverDelta.FullState)
continue;
DebugTools.AssertNotNull(implicitCompState);
// Server sent an initial delta state. This is fine as long as the client can infer an initial full
// state from the entity prototype.
serverDelta.ApplyToFullState(implicitCompState!);
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
{
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
continue;
}
serverDelta.ApplyToFullState(implicitCompState);
serverState = implicitCompState;
DebugTools.Assert(serverState is not IComponentDeltaState);
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
}
}
}
public Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity netEntity)
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
{
return _lastStateFullRep[netEntity];
}
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
{
return _lastStateFullRep;
}
public bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary)
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
{
return _lastStateFullRep.TryGetValue(entity, out dictionary);
}

View File

@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Returns the full collection of cached game states that are used to reset predicted entities.
/// </summary>
Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep();
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state

View File

@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
/// The data to merge.
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
/// </param>
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> data);
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
/// <summary>
/// Get the last state data from the server for an entity.
/// </summary>
/// <returns>Dictionary (net ID -> ComponentState)</returns>
Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity entity);
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
/// <summary>
/// Calculate the number of applicable states in the game state buffer from a given tick.
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
int GetApplicableStateCount(GameTick? fromTick);
bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary);
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
}
}

View File

@@ -49,7 +49,7 @@ namespace Robust.Client.GameStates
while (query.MoveNext(out var uid, out var transform))
{
// if not on the same map, continue
if (transform.MapID != args.MapId || _container.IsEntityInContainer(uid))
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
continue;
if (transform.GridUid == uid)

View File

@@ -64,22 +64,29 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public Box2 GetWorldViewport()
{
return GetWorldViewbounds().CalcBoundingBox();
var vpSize = _displayManager.ScreenSize;
var topLeft = ScreenToMap(Vector2.Zero);
var topRight = ScreenToMap(new Vector2(vpSize.X, 0));
var bottomRight = ScreenToMap(vpSize);
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y));
var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
var top = MathHelper.Max(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
return new Box2(left, bottom, right, top);
}
/// <inheritdoc />
public Box2Rotated GetWorldViewbounds()
{
// This is an inefficient and roundabout way of geting the viewport.
// But its a method that shouldn't get used much.
var vp = MainViewport as Control;
var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize;
var vpSize = _displayManager.ScreenSize;
var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position;
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position;
// This assumes the main viewports eye and the main eye are the same.
var rotation = new Angle(CurrentEye.Rotation);
var center = (bottomLeft + topRight) / 2;

View File

@@ -1,5 +1,4 @@
using System;
using System.Numerics;
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
@@ -14,29 +13,26 @@ namespace Robust.Client.Graphics
public interface IEyeManager
{
/// <summary>
/// The primary eye, which is usually the eye associated with the main viewport.
/// The current eye that is being used to render the game.
/// </summary>
/// <remarks>
/// Generally, you should avoid using this whenever possible. E.g., when rendering overlays should use the
/// eye & viewbounds that gets passed to the draw method.
/// Setting this property to null will use the default eye.
/// </remarks>
IEye CurrentEye { get; set; }
IViewportControl MainViewport { get; set; }
[Obsolete]
/// <summary>
/// The ID of the map on which the current eye is "placed".
/// </summary>
MapId CurrentMap { get; }
/// <summary>
/// A world-space box that is at LEAST the area covered by the main viewport.
/// A world-space box that is at LEAST the area covered by the viewport.
/// May be larger due to say rotation.
/// </summary>
Box2 GetWorldViewport();
/// <summary>
/// A world-space box of the area visible in the main viewport.
/// </summary>
Box2Rotated GetWorldViewbounds();
/// <summary>
@@ -47,7 +43,7 @@ namespace Robust.Client.Graphics
void GetScreenProjectionMatrix(out Matrix3 projMatrix);
/// <summary>
/// Projects a point from world space to UI screen space using the main viewport.
/// Projects a point from world space to UI screen space using the current camera.
/// </summary>
/// <param name="point">Point in world to transform.</param>
/// <returns>Corresponding point in UI screen space.</returns>

View File

@@ -480,7 +480,7 @@ namespace Robust.Client.Graphics.Clyde
var worldBounds = CalcWorldBounds(viewport);
var worldAABB = worldBounds.CalcBoundingBox();
if (eye.Position.MapId != MapId.Nullspace)
if (_eyeManager.CurrentMap != MapId.Nullspace)
{
using (DebugGroup("Lights"))
using (_prof.Group("Lights"))
@@ -544,12 +544,9 @@ namespace Robust.Client.Graphics.Clyde
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
}
if (eye.Position.MapId != MapId.Nullspace)
using (_prof.Group("Overlays WS"))
{
using (_prof.Group("Overlays WS"))
{
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
}
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
}
_currentViewport = oldVp;

View File

@@ -664,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
fixed (byte* p = buffer)
{
GL.GetTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, (IntPtr) p);
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
}
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);

View File

@@ -346,7 +346,7 @@ namespace Robust.Client.Input
{
if (binding.CanRepeat)
{
return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat);
return SetBindState(binding, BoundKeyState.Down, uiOnly);
}
return true;
@@ -375,7 +375,7 @@ namespace Robust.Client.Input
SetBindState(binding, BoundKeyState.Up);
}
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false)
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false)
{
if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down)
{
@@ -387,7 +387,6 @@ namespace Robust.Client.Input
// I honestly have no idea what the best solution here is.
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
DebugTools.Assert(!isRepeat || binding.CanRepeat);
try
{
@@ -400,7 +399,7 @@ namespace Robust.Client.Input
binding.State = state;
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
MouseScreenPosition, binding.CanFocus, isRepeat);
MouseScreenPosition, binding.CanFocus);
// UI returns true here into blockPass if it wants to prevent us from giving input events
// to the viewport, but doesn't want it hard-handled so we keep processing possible key actions.

View File

@@ -19,7 +19,7 @@ namespace Robust.Client.Placement.Modes
public SnapgridCenter(PlacementManager pMan) : base(pMan) { }
public override void Render(in OverlayDrawArgs args)
public override void Render(DrawingHandleWorld handle)
{
if (Grid != null)
{
@@ -34,18 +34,18 @@ namespace Robust.Client.Placement.Modes
{
var from = ScreenToWorld(new Vector2(a, 0));
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
handle.DrawLine(from, to, new Color(0, 0, 1f));
}
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
{
var from = ScreenToWorld(new Vector2(0, a));
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
handle.DrawLine(from, to, new Color(0, 0, 1f));
}
}
// Draw grid BELOW the ghost thing.
base.Render(args);
base.Render(handle);
}
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)

View File

@@ -628,20 +628,20 @@ namespace Robust.Client.Placement
return true;
}
private void Render(in OverlayDrawArgs args)
private void Render(DrawingHandleWorld handle)
{
if (CurrentMode == null || !IsActive)
{
if (EraserRect.HasValue)
{
args.WorldHandle.UseShader(_drawingShader);
args.WorldHandle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
args.WorldHandle.UseShader(null);
handle.UseShader(_drawingShader);
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
handle.UseShader(null);
}
return;
}
CurrentMode.Render(args);
CurrentMode.Render(handle);
if (CurrentPermission is not {Range: > 0} ||
!CurrentMode.RangeRequired ||
@@ -650,7 +650,7 @@ namespace Robust.Client.Placement
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
args.WorldHandle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
handle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
}
private void HandleStartPlacement(MsgPlacement msg)

View File

@@ -88,7 +88,7 @@ namespace Robust.Client.Placement
/// <returns></returns>
public abstract bool IsValidPosition(EntityCoordinates position);
public virtual void Render(in OverlayDrawArgs args)
public virtual void Render(DrawingHandleWorld handle)
{
var uid = pManager.CurrentPlacementOverlayEntity;
if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible)
@@ -125,8 +125,7 @@ namespace Robust.Client.Placement
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
var rot = args.Viewport.Eye?.Rotation ?? default;
spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos);
spriteSys.Render(uid.Value, sprite, handle, pManager.EyeManager.CurrentEye.Rotation, worldRot, worldPos);
}
}

View File

@@ -19,7 +19,7 @@ namespace Robust.Client.Placement
protected internal override void Draw(in OverlayDrawArgs args)
{
_manager.Render(args);
_manager.Render(args.WorldHandle);
}
}
}

View File

@@ -133,11 +133,6 @@ public sealed partial class ReplayLoadManager
var spawnedTracker = 0;
var stateTracker = 0;
var curState = state0;
var stats_due_ticks = 0;
var stats_due_spawned = 0;
var stats_due_state = 0;
for (var i = 1; i < states.Count; i++)
{
if (i % 10 == 0)
@@ -155,28 +150,11 @@ public sealed partial class ReplayLoadManager
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
// Don't create checkpoints too frequently no matter the circumstance
if (ticksSinceLastCheckpoint < _checkpointMinInterval)
continue;
// Check if enough time, spawned entities or changed states have occurred.
if (ticksSinceLastCheckpoint < _checkpointInterval
&& spawnedTracker < _checkpointEntitySpawnThreshold
&& stateTracker < _checkpointEntityStateThreshold)
{
continue;
// Track and update statistics about why checkpoints are getting created:
if (ticksSinceLastCheckpoint >= _checkpointInterval)
{
stats_due_ticks += 1;
}
else if (spawnedTracker >= _checkpointEntitySpawnThreshold)
{
stats_due_spawned += 1;
}
else if (stateTracker >= _checkpointEntityStateThreshold)
{
stats_due_state += 1;
}
ticksSinceLastCheckpoint = 0;
@@ -191,8 +169,7 @@ public sealed partial class ReplayLoadManager
checkPoints.Add(new CheckpointState(newState, timeBase, cvars, i, detached));
}
_sawmill.Info($"Finished generating {checkPoints.Count} checkpoints. Elapsed time: {st.Elapsed}. Checkpoint every {(float)states.Count / checkPoints.Count} ticks on average");
_sawmill.Info($"Checkpoint stats - Spawning: {stats_due_spawned} StateChanges: {stats_due_state} Ticks: {stats_due_ticks}. ");
_sawmill.Info($"Finished generating checkpoints. Elapsed time: {st.Elapsed}");
await callback(states.Count, states.Count, LoadingState.ProcessingFiles, false);
return (checkPoints.ToArray(), serverTime);
}
@@ -363,7 +340,7 @@ public sealed partial class ReplayLoadManager
#if DEBUG
foreach (var state in modifiedState.ComponentChanges.Value)
{
DebugTools.Assert(state.State is not IComponentDeltaState delta);
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
}
#endif
continue;
@@ -376,7 +353,7 @@ public sealed partial class ReplayLoadManager
#if DEBUG
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
{
DebugTools.Assert(state.State is not IComponentDeltaState delta);
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
}
#endif
}
@@ -407,20 +384,20 @@ public sealed partial class ReplayLoadManager
if (!newCompStates.Remove(existing.NetID, out var newCompState))
continue;
if (newCompState.State is not IComponentDeltaState delta)
if (newCompState.State is not IComponentDeltaState delta || delta.FullState)
{
combined[index] = newCompState;
continue;
}
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
DebugTools.Assert(existing.State is IComponentDeltaState fullDelta && fullDelta.FullState);
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State), newCompState.LastModifiedTick);
}
foreach (var compChange in newCompStates.Values)
{
// I'm not 100% sure about this, but I think delta states should always be full states here?
DebugTools.Assert(compChange.State is not IComponentDeltaState delta);
DebugTools.Assert(compChange.State is not IComponentDeltaState delta || delta.FullState);
combined.Add(compChange);
}

View File

@@ -45,7 +45,7 @@ public sealed partial class ReplayLoadManager
continue;
var state = _entMan.GetComponentState(_entMan.EventBus, component, null, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
list.Add(new ComponentChange(netId, state, GameTick.Zero));
set.Add(netId);
}
@@ -61,7 +61,7 @@ public sealed partial class ReplayLoadManager
{
if (comp.NetID == _metaId)
{
var state = (MetaDataComponentState) comp.State!;
var state = (MetaDataComponentState) comp.State;
return state.PrototypeId;
}
}

View File

@@ -34,7 +34,6 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
private ushort _metaId;
private bool _initialized;
private int _checkpointInterval;
private int _checkpointMinInterval;
private int _checkpointEntitySpawnThreshold;
private int _checkpointEntityStateThreshold;
private ISawmill _sawmill = default!;
@@ -46,7 +45,6 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
_initialized = true;
_confMan.OnValueChanged(CVars.CheckpointInterval, value => _checkpointInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointMinInterval, value => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointEntitySpawnThreshold, value => _checkpointEntitySpawnThreshold = value,
true);
_confMan.OnValueChanged(CVars.CheckpointEntityStateThreshold, value => _checkpointEntityStateThreshold = value,

View File

@@ -41,12 +41,12 @@ internal sealed partial class ReplayPlaybackManager
skipEffectEvents = true;
ResetToNearestCheckpoint(value, false);
}
else if (value > Replay.CurrentIndex + _checkpointMinInterval)
else if (value > Replay.CurrentIndex + _checkpointInterval)
{
// If we are skipping many ticks into the future, we try to skip directly to a checkpoint instead of
// applying every tick.
var nextCheckpoint = GetNextCheckpoint(Replay, Replay.CurrentIndex);
if (nextCheckpoint.Index < value && nextCheckpoint.Index > Replay.CurrentIndex)
if (nextCheckpoint.Index < value)
ResetToNearestCheckpoint(value, false);
}

View File

@@ -51,7 +51,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
public ReplayData? Replay { get; private set; }
public NetUserId? Recorder => Replay?.Recorder;
private int _checkpointMinInterval;
private int _checkpointInterval;
private int _visualEventThreshold;
public uint? AutoPauseCountdown { get; set; }
public int? ScrubbingTarget { get; set; }
@@ -93,7 +93,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
_initialized = true;
_sawmill = _logMan.GetSawmill("replay");
_metaId = _factory.GetRegistration(typeof(MetaDataComponent)).NetID!.Value;
_confMan.OnValueChanged(CVars.CheckpointMinInterval, (value) => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointInterval, (value) => _checkpointInterval = value, true);
_confMan.OnValueChanged(CVars.ReplaySkipThreshold, (value) => _visualEventThreshold = value, true);
_client.RunLevelChanged += OnRunLevelChanged;
}

View File

@@ -52,10 +52,6 @@ namespace Robust.Client.UserInterface
[ViewVariables] public bool IsMeasureValid { get; private set; }
[ViewVariables] public bool IsArrangeValid { get; private set; }
/// <summary>
/// Controls the amount of empty space in virtual pixels around the control.
/// </summary>
/// <remarks>Values can be provided as "All" or "Horizontal, Vertical" or "Left, Top, Right, Bottom"</remarks>
[ViewVariables]
public Thickness Margin
{

View File

@@ -130,7 +130,7 @@ public sealed class TileSpawningUIController : UIController
_window.TileList.Clear();
IEnumerable<ITileDefinition> tileDefs = _tiles.Where(def => !def.EditorHidden);
IEnumerable<ITileDefinition> tileDefs = _tiles;
if (!string.IsNullOrEmpty(searchStr))
{

View File

@@ -53,7 +53,7 @@ namespace Robust.Client.UserInterface.Controls
private TimeSpan? _lastClickTime;
private Vector2? _lastClickPosition;
private bool IsPlaceHolderVisible => !(HidePlaceHolderOnFocus && HasKeyboardFocus()) && string.IsNullOrEmpty(_text) && _placeHolder != null;
private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null;
public event Action<LineEditEventArgs>? OnTextChanged;
public event Action<LineEditEventArgs>? OnTextEntered;
@@ -186,8 +186,6 @@ namespace Robust.Client.UserInterface.Controls
public int SelectionLower => Math.Min(_selectionStart, _cursorPosition);
public int SelectionUpper => Math.Max(_selectionStart, _cursorPosition);
public bool HidePlaceHolderOnFocus { get; set; }
public bool IgnoreNext { get; set; }
private (int start, int length)? _imeData;

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface
return Task.FromResult<Stream?>(null);
}
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null)
{
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
}

View File

@@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface
return await OpenFileNfd(filters);
}
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters)
{
var name = await GetSaveFileName(filters);
if (name == null)
@@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface
try
{
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
return (File.Open(name, FileMode.Open), true);
}
catch (FileNotFoundException)
{

View File

@@ -28,7 +28,6 @@ namespace Robust.Client.UserInterface
/// The file stream the user chose to save to, and whether the file already existed.
/// Null if the user cancelled the action.
/// </returns>
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null);
}
}

View File

@@ -135,17 +135,6 @@ namespace Robust.Client.UserInterface
/// Plays the UI hover sound if relevant.
/// </summary>
void HoverSound();
/// <summary>
/// Sets <see cref="CurrentlyHovered"/> to the given control.
/// </summary>
void SetHovered(Control? control);
/// <summary>
/// Forces <see cref="CurrentlyHovered"/> to get updated. This is done automatically when the mouse is moved,
/// but not necessarily a new or existing control is rearranged.
/// </summary>
void UpdateHovered();
}
public readonly struct PostDrawUIRootEventArgs

View File

@@ -9,7 +9,6 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface;
@@ -21,10 +20,9 @@ internal partial class UserInterfaceManager
private bool _needUpdateActiveCursor;
[ViewVariables] public Control? KeyboardFocused { get; private set; }
[ViewVariables] public Control? CurrentlyHovered { get; private set; }
[ViewVariables] public Control? CurrentlyHovered { get; private set; } = default!;
private Control? _controlFocused;
[ViewVariables]
public Control? ControlFocused
{
@@ -102,7 +100,6 @@ internal partial class UserInterfaceManager
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
args.PointerLocation.Position - control.GlobalPixelPosition);
@@ -114,20 +111,16 @@ internal partial class UserInterfaceManager
args.Handle();
}
// Attempt to ensure that keybind-up events get raised after a keybind-down.
DebugTools.Assert(!_focusedControls.TryGetValue(args.Function, out var existing)
|| !existing.VisibleInTree
|| args.IsRepeat && existing == control);
_focusedControls[args.Function] = control;
OnKeyBindDown?.Invoke(control);
}
public void KeyBindUp(BoundKeyEventArgs args)
{
// Only raise keybind-up for the control on which we previously raised keybind-down
if (!_focusedControls.Remove(args.Function, out var control) || !control.VisibleInTree)
if (!_focusedControls.TryGetValue(args.Function, out var control))
{
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
@@ -138,6 +131,7 @@ internal partial class UserInterfaceManager
// Always mark this as handled.
// The only case it should not be is if we do not have a control to click on,
// in which case we never reach this.
_focusedControls.Remove(args.Function);
args.Handle();
}
@@ -146,7 +140,23 @@ internal partial class UserInterfaceManager
_resetTooltipTimer();
// Update which control is considered hovered.
var newHovered = MouseGetControl(mouseMoveEventArgs.Position);
SetHovered(newHovered);
if (newHovered != CurrentlyHovered)
{
_clearTooltip();
CurrentlyHovered?.MouseExited();
CurrentlyHovered = newHovered;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
var target = ControlFocused ?? newHovered;
if (target != null)
@@ -162,33 +172,6 @@ internal partial class UserInterfaceManager
}
}
public void UpdateHovered()
{
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
SetHovered(ctrl);
}
public void SetHovered(Control? control)
{
if (control == CurrentlyHovered)
return;
_clearTooltip();
CurrentlyHovered?.MouseExited();
CurrentlyHovered = control;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
private void UpdateActiveCursor()
{
// Consider mouse input focus first so that dragging windows don't act up etc.

View File

@@ -77,12 +77,15 @@ internal sealed partial class UserInterfaceManager
ReleaseKeyboardFocus(control);
RemoveModal(control);
if (control == ControlFocused)
ControlFocused = null;
if (control == CurrentlyHovered)
UpdateHovered();
{
control.MouseExited();
CurrentlyHovered = null;
_clearTooltip();
}
if (control != ControlFocused) return;
ControlFocused = null;
}
public void PushModal(Control modal)

View File

@@ -29,8 +29,6 @@ public static class Diagnostics
public const string IdComponentPauseNoParentAttribute = "RA0023";
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
public const string IdDependencyFieldAssigned = "RA0025";
public const string IdUncachedRegex = "RA0026";
public const string IdDataFieldRedundantTag = "RA0027";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -1,4 +1,6 @@
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -34,7 +36,6 @@ public class Generator : IIncrementalGenerator
var (compilation, declarations) = source;
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
var declarationsGenerated = new HashSet<string>();
foreach (var declaration in declarations)
{
@@ -43,14 +44,6 @@ public class Generator : IIncrementalGenerator
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
if (!declarationsGenerated.Add(symbolName))
continue;
var nonPartial = !IsPartial(declaration);
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
@@ -114,6 +107,11 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
{{containingTypesEnd}}
""");
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
var sourceText = CSharpSyntaxTree
.ParseText(builder.ToString())
.GetRoot()

View File

@@ -42,17 +42,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
component.Source = new DummyAudioSource();
}
public override void SetMapAudio(Entity<AudioComponent>? audio)
{
if (audio == null)
return;
base.SetMapAudio(audio);
// Also need a global override because clients not near 0,0 won't get the audio.
_pvs.AddGlobalOverride(audio.Value);
}
private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter)
{
var count = filter.Count;

View File

@@ -51,15 +51,10 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
public (GameTick ToTick, List<PvsIndex> PreviouslySent)? LastSent;
/// <summary>
/// Visible chunks, sorted by proximity to the client's viewers.
/// Visible chunks, sorted by proximity to the clients's viewers;
/// </summary>
public readonly List<(PvsChunk Chunk, float ChebyshevDistance)> Chunks = new();
/// <summary>
/// Unsorted set of visible chunks. Used to construct the <see cref="Chunks"/> list.
/// </summary>
public readonly HashSet<PvsChunk> ChunkSet = new();
/// <summary>
/// Squared distance ta all of the visible chunks.
/// </summary>
@@ -122,7 +117,6 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
{
PlayerStates.Clear();
Chunks.Clear();
ChunkSet.Clear();
States.Clear();
State = null;
}

View File

@@ -90,13 +90,15 @@ internal sealed partial class PvsSystem
foreach (var session in _sessions)
{
session.Chunks.Clear();
session.ChunkSet.Clear();
GetSessionViewers(session);
foreach (var eye in session.Viewers)
{
GetVisibleChunks(eye, session.ChunkSet);
GetVisibleChunks(eye, session.Chunks);
}
// The list of visible chunks should be unique.
DebugTools.Assert(session.Chunks.Select(x => x.Chunk).ToHashSet().Count == session.Chunks.Count);
}
DebugTools.Assert(_dirtyChunks.ToHashSet().Count == _dirtyChunks.Count);
DebugTools.Assert(_cleanChunks.ToHashSet().Count == _cleanChunks.Count);
@@ -106,7 +108,7 @@ internal sealed partial class PvsSystem
/// Get the chunks visible to a single entity and add them to a player's set of visible chunks.
/// </summary>
private void GetVisibleChunks(Entity<TransformComponent, EyeComponent?> eye,
HashSet<PvsChunk> chunks)
List<(PvsChunk Chunk, float ChebyshevDistance)> playerChunks)
{
var (viewPos, range, mapUid) = CalcViewBounds(eye);
if (mapUid is not {} map)
@@ -119,7 +121,7 @@ internal sealed partial class PvsSystem
if (!_chunks.TryGetValue(loc, out var chunk))
continue;
chunks.Add(chunk);
playerChunks.Add((chunk, default));
if (chunk.UpdateQueued)
continue;
@@ -145,7 +147,7 @@ internal sealed partial class PvsSystem
if (!_chunks.TryGetValue(loc, out var chunk))
continue;
chunks.Add(chunk);
playerChunks.Add((chunk, default));
if (chunk.UpdateQueued)
continue;

View File

@@ -55,10 +55,13 @@ internal sealed partial class PvsSystem
return;
}
if (xform.ParentUid != xform.GridUid && xform.ParentUid != xform.MapUid)
var root = (xform.GridUid ?? xform.MapUid);
DebugTools.AssertNotNull(root);
if (xform.ParentUid != root)
return;
var location = new PvsChunkLocation(xform.ParentUid, GetChunkIndices(xform._localPosition));
var location = new PvsChunkLocation(root.Value, GetChunkIndices(xform._localPosition));
if (meta.LastPvsLocation == location)
return;

View File

@@ -50,11 +50,9 @@ internal sealed partial class PvsSystem
continue;
var state = EntityManager.GetComponentState(bus, component, player, fromTick);
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState delta || delta.FullState);
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
if (state != null)
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState);
if (sendCompList)
netComps!.Add(netId);
}
@@ -87,7 +85,7 @@ internal sealed partial class PvsSystem
continue;
var state = EntityManager.GetComponentState(bus, component, player, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
netComps.Add(netId);
}

View File

@@ -137,19 +137,15 @@ internal sealed partial class PvsSystem
if (!CullingEnabled || session.DisableCulling)
return;
var chunkSet = session.ChunkSet;
var chunks = session.Chunks;
var distances = session.ChunkDistanceSq;
DebugTools.AssertEqual(chunks.Count, 0);
distances.Clear();
distances.EnsureCapacity(chunkSet.Count);
chunks.EnsureCapacity(chunkSet.Count);
distances.EnsureCapacity(chunks.Count);
// Assemble list of chunks and their distances to the nearest eye.
foreach(var chunk in chunkSet)
foreach (ref var tuple in CollectionsMarshal.AsSpan(chunks))
{
var chunk = tuple.Chunk;
var dist = float.MaxValue;
var chebDist = float.MaxValue;
@@ -169,7 +165,7 @@ internal sealed partial class PvsSystem
}
distances.Add(dist);
chunks.Add((chunk, chebDist));
tuple.ChebyshevDistance = chebDist;
}
// Sort chunks based on distances

View File

@@ -134,7 +134,7 @@ internal sealed partial class PvsSystem : EntitySystem
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_transform.OnBeforeMoveEvent += OnEntityMove;
_transform.OnGlobalMoveEvent += OnEntityMove;
EntityManager.EntityAdded += OnEntityAdded;
EntityManager.EntityDeleted += OnEntityDeleted;
EntityManager.AfterEntityFlush += AfterEntityFlush;
@@ -159,7 +159,7 @@ internal sealed partial class PvsSystem : EntitySystem
base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_transform.OnBeforeMoveEvent -= OnEntityMove;
_transform.OnGlobalMoveEvent -= OnEntityMove;
EntityManager.EntityAdded -= OnEntityAdded;
EntityManager.EntityDeleted -= OnEntityDeleted;
EntityManager.AfterEntityFlush -= AfterEntityFlush;

View File

@@ -43,9 +43,6 @@
<Content Include="server_config.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="run_server.bat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="ExtraMappedSerializerStrings.txt">
<LogicalName>Robust.Server.ExtraMappedSerializerStrings.txt</LogicalName>
</EmbeddedResource>

View File

@@ -68,7 +68,7 @@ namespace Robust.Server.ServerStatus
if (auth != _watchdogToken)
{
// Holy shit nobody read these logs please.
_sawmill.Verbose(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
_sawmill.Info(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
return true;
}
@@ -105,7 +105,7 @@ namespace Robust.Server.ServerStatus
if (auth != _watchdogToken)
{
_sawmill.Verbose(
_sawmill.Warning(
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth,
_watchdogToken);
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);

View File

@@ -1,2 +0,0 @@
Robust.Server.exe
pause

View File

@@ -52,7 +52,7 @@ tags = ""
# Must be in the form of an ss14:// or ss14s:// URI pointing to the status API.
server_url = ""
# Comma-separated list of URLs of hub servers to advertise to.
hub_urls = "https://hub.spacestation14.com/"
hub_urls = "https://central.spacestation14.io/hub/"
[build]
# *Absolutely all of these can be supplied using a "build.json" file*
@@ -98,5 +98,5 @@ hub_urls = "https://hub.spacestation14.com/"
# You should probably never EVER need to touch this, but if you need a custom auth server,
# (the auth server being the one which manages Space Station 14 accounts), you change it here.
# server = https://auth.spacestation14.com/
# server = https://central.spacestation14.io/auth/

View File

@@ -8,7 +8,6 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
@@ -33,12 +32,11 @@ public abstract partial class SharedAudioSystem : EntitySystem
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
public const float DefaultSoundRange = 15;
public const float DefaultSoundRange = 20;
/// <summary>
/// Used in the PAS to designate the physics collision mask of occluders.
@@ -133,18 +131,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
return (float) (Timing.CurTime - (component.PauseTime ?? TimeSpan.Zero) - component.AudioStart).TotalSeconds;
}
/// <summary>
/// Marks this audio as being map-based.
/// </summary>
public virtual void SetMapAudio(Entity<AudioComponent>? audio)
{
if (audio == null)
return;
audio.Value.Comp.Global = true;
MetadataSys.AddFlag(audio.Value.Owner, MetaDataFlags.Undetachable);
}
/// <summary>
/// Sets the shared state for an audio entity.
/// </summary>

View File

@@ -368,21 +368,6 @@ namespace Robust.Shared
public static readonly CVarDef<float> NetHappyEyeballsDelay =
CVarDef.Create("net.happy_eyeballs_delay", 0.025f, CVar.CLIENTONLY);
/// <summary>
/// Controls whether the networking library will log warning messages.
/// </summary>
/// <remarks>
/// Disabling this should make the networking layer more resilient against some DDoS attacks.
/// </remarks>
public static readonly CVarDef<bool> NetLidgrenLogWarning =
CVarDef.Create("net.lidgren_log_warning", true);
/// <summary>
/// Controls whether the networking library will log error messages.
/// </summary>
public static readonly CVarDef<bool> NetLidgrenLogError =
CVarDef.Create("net.lidgren_log_error", true);
/**
* SUS
*/
@@ -899,7 +884,7 @@ namespace Robust.Shared
CVarDef.Create("render.sprite_direction_bias", -0.05, CVar.ARCHIVE | CVar.CLIENTONLY);
public static readonly CVarDef<string> RenderFOVColor =
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.REPLICATED | CVar.SERVER);
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY);
/*
* CONTROLS
@@ -1647,20 +1632,15 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<int> ReplaySkipThreshold = CVarDef.Create("replay.skip_threshold", 30);
/// <summary>
/// Minimum number of ticks before a new checkpoint tick is generated (overrides SpawnThreshold and StateThreshold)
/// </summary>
public static readonly CVarDef<int> CheckpointMinInterval = CVarDef.Create("replay.checkpoint_min_interval", 60);
/// <summary>
/// Maximum number of ticks before a new checkpoint tick is generated.
/// </summary>
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 500);
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 200);
/// <summary>
/// Maximum number of entities that can be spawned before a new checkpoint tick is generated.
/// </summary>
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 1000);
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 100);
/// <summary>
/// Maximum number of entity states that can be applied before a new checkpoint tick is generated.

View File

@@ -294,9 +294,6 @@ public struct ValueList<T> : IEnumerable<T>
if (Capacity < capacity)
Grow(capacity);
if (capacity == 0)
return capacity;
return _items!.Length;
}

View File

@@ -71,8 +71,6 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
}
else
{
// TODO EXCEPTION TOLERANCE
// Ensure lookup trees update before content code handles move events.
SubscribeLocalEvent<TComp, MoveEvent>(HandleMove);
}

View File

@@ -33,7 +33,7 @@ internal sealed class RecursiveMoveSystem : EntitySystem
public override void Shutdown()
{
if (_subscribed)
_transform.OnBeforeMoveEvent -= AnythingMoved;
_transform.OnGlobalMoveEvent -= AnythingMoved;
_subscribed = false;
}
@@ -44,7 +44,7 @@ internal sealed class RecursiveMoveSystem : EntitySystem
return;
_subscribed = true;
_transform.OnBeforeMoveEvent += AnythingMoved;
_transform.OnGlobalMoveEvent += AnythingMoved;
}
private void AnythingMoved(ref MoveEvent args)

View File

@@ -32,8 +32,6 @@ namespace Robust.Shared.Configuration
private ISawmill _sawmill = default!;
public event Action<CVarChangeInfo>? OnCVarValueChanged;
/// <summary>
/// Constructs a new ConfigurationManager.
/// </summary>
@@ -96,15 +94,15 @@ namespace Robust.Shared.Configuration
if (_configVars.TryGetValue(cvar, out var cfgVar))
{
// overwrite the value with the saved one
var oldValue = GetConfigVarValue(cfgVar);
changedInvokes.Add(SetupInvokeValueChanged(cfgVar, value, oldValue));
cfgVar.Value = value;
if (SetupInvokeValueChanged(cfgVar, value) is { } invoke)
changedInvokes.Add(invoke);
}
else
{
//or add another unregistered CVar
//Note: the initial defaultValue is null, but it will get overwritten when the cvar is registered.
cfgVar = new ConfigVar(cvar, null!, CVar.NONE) { Value = value };
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
cfgVar = new ConfigVar(cvar, 0, CVar.NONE) { Value = value };
_configVars.Add(cvar, cfgVar);
}
@@ -129,26 +127,26 @@ namespace Robust.Shared.Configuration
}
var convertedValue = value;
if (cVar.Type != value.GetType())
if (!cVar.DefaultValue.GetType().IsEnum && cVar.DefaultValue.GetType() != value.GetType())
{
try
{
convertedValue = ConvertToCVarType(value, cVar.Type!);
convertedValue = ConvertToCVarType(value, cVar.DefaultValue.GetType());
}
catch
{
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.Type}. Toml type: {value.GetType()}");
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.DefaultValue.GetType()}. Toml type: {value.GetType()}");
continue;
}
}
cVar.DefaultValue = value;
if (cVar.OverrideValue == null && cVar.Value == null)
{
var oldValue = GetConfigVarValue(cVar);
callbackEvents.Add(SetupInvokeValueChanged(cVar, convertedValue, oldValue));
if (SetupInvokeValueChanged(cVar, convertedValue) is { } invoke)
callbackEvents.Add(invoke);
}
cVar.DefaultValue = convertedValue;
}
}
@@ -319,7 +317,6 @@ namespace Robust.Shared.Configuration
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags)
{
DebugTools.AssertEqual(defaultValue.GetType(), type);
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
$"{name}: Enum cvars must have int as underlying type.");
@@ -338,7 +335,7 @@ namespace Robust.Shared.Configuration
if (cVar.Registered)
_sawmill.Error($"The variable '{name}' has already been registered.");
if (cVar.Value != null && type != cVar.Value.GetType())
if (!type.IsEnum && cVar.Value != null && !type.IsAssignableFrom(cVar.Value.GetType()))
{
try
{
@@ -353,7 +350,7 @@ namespace Robust.Shared.Configuration
cVar.DefaultValue = defaultValue;
cVar.Flags = flags;
cVar.Register();
cVar.Registered = true;
if (cVar.OverrideValue != null)
{
@@ -363,9 +360,10 @@ namespace Robust.Shared.Configuration
return;
}
var cvar = new ConfigVar(name, defaultValue, flags);
cvar.Register();
_configVars.Add(name, cvar);
_configVars.Add(name, new ConfigVar(name, defaultValue, flags)
{
Registered = true
});
}
public void OnValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged, bool invokeImmediately = false)
@@ -414,11 +412,10 @@ namespace Robust.Shared.Configuration
public void OnValueChanged<T>(string name, CVarChanged<T> onValueChanged, bool invokeImmediately = false)
where T : notnull
{
object value;
using (Lock.WriteGuard())
{
var reg = _configVars[name];
value = GetConfigVarValue(reg);
reg.ValueChanged.AddInPlace(
(object value, in CVarChangeInfo info) => onValueChanged((T)value, info),
onValueChanged);
@@ -426,7 +423,7 @@ namespace Robust.Shared.Configuration
if (invokeImmediately)
{
onValueChanged(GetCVar<T>(name), new CVarChangeInfo(name, _gameTiming.CurTick, value, value));
onValueChanged(GetCVar<T>(name), new CVarChangeInfo(_gameTiming.CurTick));
}
}
@@ -515,13 +512,12 @@ namespace Robust.Shared.Configuration
{
if (!Equals(cVar.OverrideValueParsed ?? cVar.Value, value))
{
var oldValue = GetConfigVarValue(cVar);
invoke = SetupInvokeValueChanged(cVar, value, oldValue, intendedTick);
// Setting an overriden var just turns off the override, basically.
cVar.OverrideValue = null;
cVar.OverrideValueParsed = null;
cVar.Value = value;
invoke = SetupInvokeValueChanged(cVar, value, intendedTick);
}
}
else
@@ -547,14 +543,10 @@ namespace Robust.Shared.Configuration
if (!_configVars.TryGetValue(name, out var cVar) || !cVar.Registered)
throw new InvalidConfigurationException($"Trying to set unregistered variable '{name}'");
if (cVar.OverrideValue == null && cVar.Value == null)
{
var oldValue = GetConfigVarValue(cVar);
invoke = SetupInvokeValueChanged(cVar, value, oldValue);
}
cVar.DefaultValue = value;
if (cVar.OverrideValue == null && cVar.Value == null)
invoke = SetupInvokeValueChanged(cVar, value);
}
if (invoke != null)
@@ -595,7 +587,7 @@ namespace Robust.Shared.Configuration
}
// If it's null it's a string, since the rest is primitives which aren't null.
return cVar.Type!;
return cVar.DefaultValue.GetType();
}
protected static object GetConfigVarValue(ConfigVar cVar)
@@ -614,19 +606,18 @@ namespace Robust.Shared.Configuration
if (_configVars.TryGetValue(key, out var cfgVar))
{
cfgVar.OverrideValue = value;
if (!cfgVar.Registered)
continue;
var newValue = ParseOverrideValue(value, cfgVar.Type!);
var oldValue = GetConfigVarValue(cfgVar);
invokes.Add(SetupInvokeValueChanged(cfgVar, newValue, oldValue));
cfgVar.OverrideValueParsed = newValue;
if (cfgVar.Registered)
{
cfgVar.OverrideValueParsed = ParseOverrideValue(value, cfgVar.DefaultValue?.GetType());
if (SetupInvokeValueChanged(cfgVar, cfgVar.OverrideValueParsed) is { } invoke)
invokes.Add(invoke);
}
}
else
{
//or add another unregistered CVar
//Note: the initial defaultValue is null, but it will get overwritten when the cvar is registered.
var cVar = new ConfigVar(key, null!, CVar.NONE) { OverrideValue = value };
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
var cVar = new ConfigVar(key, 0, CVar.NONE) { OverrideValue = value };
_configVars.Add(key, cVar);
}
}
@@ -705,20 +696,25 @@ namespace Robust.Shared.Configuration
}
}
private void InvokeValueChanged(in ValueChangedInvoke invoke)
private static void InvokeValueChanged(in ValueChangedInvoke invoke)
{
OnCVarValueChanged?.Invoke(invoke.Info);
foreach (var entry in invoke.Invoke.Entries)
{
entry.Value!.Invoke(invoke.Value, in invoke.Info);
}
}
private ValueChangedInvoke SetupInvokeValueChanged(ConfigVar var, object newValue, object oldValue, GameTick? tick = null)
private ValueChangedInvoke? SetupInvokeValueChanged(ConfigVar var, object value, GameTick? tick = null)
{
tick ??= _gameTiming.CurTick;
var info = new CVarChangeInfo(var.Name, tick.Value, newValue, oldValue);
return new ValueChangedInvoke(info, var.ValueChanged);
if (var.ValueChanged.Count == 0)
return null;
return new ValueChangedInvoke
{
Info = new CVarChangeInfo(tick ?? _gameTiming.CurTick),
Invoke = var.ValueChanged,
Value = value
};
}
private IEnumerable<(string cvar, object value)> ParseCVarValuesFromToml(Stream stream)
@@ -771,9 +767,6 @@ namespace Robust.Shared.Configuration
/// <returns></returns>
private static object ConvertToCVarType(object value, Type cVar)
{
if (cVar.IsEnum)
return Enum.Parse(cVar, value.ToString() ?? string.Empty);
return Convert.ChangeType(value, cVar);
}
@@ -793,15 +786,10 @@ namespace Robust.Shared.Configuration
public ConfigVar(string name, object defaultValue, CVar flags)
{
Name = name;
DefaultValue = defaultValue;
Flags = flags;
_defaultValue = defaultValue;
}
/// <summary>
/// The type of the cvar's value. This may be null until the cvar is registered.
/// </summary>
public Type? Type { get; internal set; }
/// <summary>
/// The name of the CVar.
/// </summary>
@@ -810,16 +798,7 @@ namespace Robust.Shared.Configuration
/// <summary>
/// The default value of this CVar.
/// </summary>
public object DefaultValue
{
get => _defaultValue;
set
{
if (Registered)
DebugTools.AssertEqual(value.GetType(), Type);
_defaultValue = value;
}
}
public object DefaultValue { get; set; }
/// <summary>
/// Optional flags to modify the behavior of this CVar.
@@ -829,45 +808,12 @@ namespace Robust.Shared.Configuration
/// <summary>
/// The current value of this CVar.
/// </summary>
public object? Value
{
get => _value;
set
{
if (value != null && Registered)
DebugTools.AssertEqual(value.GetType(), Type);
_value = value;
}
}
public object? Value { get; set; }
/// <summary>
/// Has this CVar been registered in code?
/// </summary>
public bool Registered { get; private set; }
public void Register()
{
if (Registered)
{
DebugTools.AssertNotNull(DefaultValue);
DebugTools.AssertEqual(DefaultValue.GetType(), Type);
DebugTools.Assert(Value == null || Value.GetType() == Type);
DebugTools.Assert(OverrideValueParsed == null || OverrideValueParsed.GetType() == Type);
return;
}
if (_defaultValue == null)
throw new NullReferenceException("Must specify default value before registering");
if (Value != null && DefaultValue.GetType() != Value.GetType())
throw new Exception($"The cvar value & default value must be of the same type");
if (OverrideValueParsed != null && DefaultValue.GetType() != OverrideValueParsed.GetType())
throw new Exception($"The cvar override value & default value must be of the same type");
Type = DefaultValue.GetType();
Registered = true;
}
public bool Registered { get; set; }
/// <summary>
/// Was the CVar present in the config file?
@@ -876,25 +822,12 @@ namespace Robust.Shared.Configuration
public bool ConfigModified;
public InvokeList<ValueChangedDelegate> ValueChanged;
private object _defaultValue;
private object? _value;
private object? _overrideValueParsed;
// We don't know what the type of the var is until it's registered.
// So we can't actually parse them until then.
// So we keep the raw string around.
public string? OverrideValue { get; set; }
public object? OverrideValueParsed
{
get => _overrideValueParsed;
set
{
if (value != null && Registered)
DebugTools.AssertEqual(value.GetType(), Type);
_overrideValueParsed = value;
}
}
public object? OverrideValueParsed { get; set; }
}
/// <summary>
@@ -903,14 +836,8 @@ namespace Robust.Shared.Configuration
private struct ValueChangedInvoke
{
public InvokeList<ValueChangedDelegate> Invoke;
public object Value => Info.NewValue;
public object Value;
public CVarChangeInfo Info;
public ValueChangedInvoke(CVarChangeInfo info, InvokeList<ValueChangedDelegate> invoke) : this()
{
Info = info;
Invoke = invoke;
}
}
protected delegate void ValueChangedDelegate(object value, in CVarChangeInfo info);

View File

@@ -10,11 +10,6 @@ namespace Robust.Shared.Configuration
/// </summary>
public readonly struct CVarChangeInfo
{
/// <summary>
/// The name of the cvar that was changed.
/// </summary>
public readonly string Name;
/// <summary>
/// The tick this CVar changed at.
/// </summary>
@@ -25,22 +20,9 @@ namespace Robust.Shared.Configuration
/// </remarks>
public readonly GameTick TickChanged;
/// <summary>
/// The new value.
/// </summary>
public readonly object NewValue;
/// <summary>
/// The previous value.
/// </summary>
public readonly object OldValue;
internal CVarChangeInfo(string name, GameTick tickChanged, object newValue, object oldValue)
internal CVarChangeInfo(GameTick tickChanged)
{
Name = name;
TickChanged = tickChanged;
NewValue = newValue;
OldValue = oldValue;
}
}
@@ -270,7 +252,5 @@ namespace Robust.Shared.Configuration
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
void UnsubValueChanged<T>(string name, CVarChanged<T> onValueChanged)
where T : notnull;
public event Action<CVarChangeInfo>? OnCVarValueChanged;
}
}

View File

@@ -1,8 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -15,19 +23,6 @@ namespace Robust.Shared.Containers
[ImplicitDataDefinitionForInheritors]
public abstract partial class BaseContainer
{
// Will be null until after the component has been initialized.
protected SharedContainerSystem? System;
[Access(typeof(SharedContainerSystem), typeof(ContainerManagerComponent))]
internal void Init(SharedContainerSystem system, string id, Entity<ContainerManagerComponent> owner)
{
DebugTools.Assert(ID == null || ID == id);
ID = id;
Owner = owner;
Manager = owner;
System = system;
}
/// <summary>
/// Readonly collection of all the entities contained within this specific container
/// </summary>

View File

@@ -2,8 +2,10 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
@@ -49,7 +51,14 @@ namespace Robust.Shared.Containers
if (!_containerList.Contains(contained))
return false;
System?.AssertInContainer(contained, this);
#if DEBUG
if (IoCManager.Resolve<IGameTiming>().ApplyingState)
return true;
var entMan = IoCManager.Resolve<IEntityManager>();
var flags = entMan.GetComponent<MetaDataComponent>(contained).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}");
#endif
return true;
}

View File

@@ -30,7 +30,9 @@ namespace Robust.Shared.Containers
{
foreach (var (id, container) in Containers)
{
container.Init(default!, id, (Owner, this));
container.ID = id;
container.Owner = Owner;
container.Manager = this;
}
}

View File

@@ -2,9 +2,11 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
@@ -57,7 +59,14 @@ namespace Robust.Shared.Containers
if (contained != ContainedEntity)
return false;
System?.AssertInContainer(contained, this);
#if DEBUG
if (IoCManager.Resolve<IGameTiming>().ApplyingState)
return true;
var entMan = IoCManager.Resolve<IEntityManager>();
var flags = entMan.GetComponent<MetaDataComponent>(contained).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}");
#endif
return true;
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
@@ -13,7 +12,6 @@ using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
@@ -26,7 +24,6 @@ namespace Robust.Shared.Containers
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedJointSystem _joint = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<ContainerManagerComponent> _managerQuery;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -42,7 +39,6 @@ namespace Robust.Shared.Containers
base.Initialize();
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<ContainerManagerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ContainerManagerComponent, ComponentStartup>(OnStartupValidation);
SubscribeLocalEvent<ContainerManagerComponent, ComponentGetState>(OnContainerGetState);
SubscribeLocalEvent<ContainerManagerComponent, ComponentRemove>(OnContainerManagerRemove);
@@ -56,18 +52,9 @@ namespace Robust.Shared.Containers
TransformQuery = GetEntityQuery<TransformComponent>();
}
private void OnInit(Entity<ContainerManagerComponent> ent, ref ComponentInit args)
{
foreach (var (id, container) in ent.Comp.Containers)
{
container.Init(this, id, ent);
}
}
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
{
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet =
new(component.Containers.Count);
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet = new(component.Containers.Count);
foreach (var container in component.Containers.Values)
{
@@ -78,11 +65,7 @@ namespace Robust.Shared.Containers
uidArr[index] = GetNetEntity(container.ContainedEntities[index]);
}
var sContainer =
new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name,
container.ShowContents,
container.OccludesLight,
uidArr);
var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name, container.ShowContents, container.OccludesLight, uidArr);
containerSet.Add(container.ID, sContainer);
}
@@ -113,12 +96,18 @@ namespace Robust.Shared.Containers
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
var container = _dynFactory.CreateInstanceUnchecked<T>(typeof(T), inject: false);
container.Init(this, id, (uid, containerManager));
InitContainer(container, (uid, containerManager), id);
containerManager.Containers[id] = container;
Dirty(uid, containerManager);
return container;
}
protected void InitContainer(BaseContainer container, Entity<ContainerManagerComponent> containerEnt, string id)
{
DebugTools.AssertNull(container.ID);
((container.Owner, container.Manager), container.ID) = (containerEnt, id);
}
public virtual void ShutdownContainer(BaseContainer container)
{
container.InternalShutdown(EntityManager, this, _net.IsClient);
@@ -126,11 +115,7 @@ namespace Robust.Shared.Containers
container.ExpectedEntities.Clear();
}
public T EnsureContainer<T>(
EntityUid uid,
string id,
out bool alreadyExisted,
ContainerManagerComponent? containerManager = null)
public T EnsureContainer<T>(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null)
where T : BaseContainer
{
if (!Resolve(uid, ref containerManager, false))
@@ -151,7 +136,7 @@ namespace Robust.Shared.Containers
}
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
where T : BaseContainer
where T : BaseContainer
{
return EnsureContainer<T>(uid, id, out _, containerManager);
}
@@ -172,11 +157,7 @@ namespace Robust.Shared.Containers
return containerManager.Containers.ContainsKey(id);
}
public bool TryGetContainer(
EntityUid uid,
string id,
[NotNullWhen(true)] out BaseContainer? container,
ContainerManagerComponent? containerManager = null)
public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager, false))
{
@@ -184,30 +165,16 @@ namespace Robust.Shared.Containers
return false;
}
if (!containerManager.Containers.TryGetValue(id, out container))
return false;
DebugTools.AssertEqual(container.ID, id);
DebugTools.AssertNotNull(container.Manager);
DebugTools.AssertNotEqual(container.Owner, EntityUid.Invalid);
return true;
return containerManager.Containers.TryGetValue(id, out container);
}
[Obsolete("Use variant without skipExistCheck argument")]
public bool TryGetContainingContainer(
EntityUid uid,
EntityUid containedUid,
[NotNullWhen(true)] out BaseContainer? container,
bool skipExistCheck)
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,
[NotNullWhen(true)] out BaseContainer? container,
ContainerManagerComponent? containerManager = null)
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
@@ -229,10 +196,7 @@ namespace Robust.Shared.Containers
return false;
}
public bool ContainsEntity(
EntityUid uid,
EntityUid containedUid,
ContainerManagerComponent? containerManager = null)
public bool ContainsEntity(EntityUid uid, EntityUid containedUid, ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
@@ -264,20 +228,13 @@ namespace Robust.Shared.Containers
foreach (var containers in containerManager.Containers.Values)
{
if (containers.Contains(toremove))
return Remove((toremove, containedXform, containedMeta),
containers,
reparent,
force,
destination,
localRotation);
return Remove((toremove, containedXform, containedMeta), containers, reparent, force, destination, localRotation);
}
return true; // If we don't contain the entity, it will always be removed
}
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(
EntityUid uid,
ContainerManagerComponent? containerManager = null)
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(EntityUid uid, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager))
return new ContainerManagerComponent.AllContainersEnumerable();
@@ -289,32 +246,20 @@ namespace Robust.Shared.Containers
#region Container Helpers
[Obsolete("Use Entity<T> variant")]
public bool TryGetContainingContainer(
EntityUid uid,
[NotNullWhen(true)] out BaseContainer? container,
MetaDataComponent? meta = null,
TransformComponent? transform = null)
{
return TryGetContainingContainer((uid, transform, meta), out container);
}
public bool TryGetContainingContainer(
Entity<TransformComponent?, MetaDataComponent?> ent,
[NotNullWhen(true)] out BaseContainer? container)
public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out BaseContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null)
{
container = null;
if (!Resolve(ent, ref ent.Comp2, false))
if (!Resolve(uid, ref meta, false))
return false;
if ((ent.Comp2.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
return false;
if (!Resolve(ent, ref ent.Comp1, false))
if (!Resolve(uid, ref transform, false))
return false;
return TryGetContainingContainer(ent.Comp1.ParentUid, ent, out container);
return TryGetContainingContainer(transform.ParentUid, uid, out container);
}
/// <summary>
@@ -411,19 +356,10 @@ namespace Robust.Shared.Containers
return TryFindComponentsOnEntityContainerOrParent(xform.ParentUid, entityQuery, foundComponents);
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
{
return IsInSameOrNoContainer((user, null, null), (other, null, null));
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container.
/// </summary>
public bool IsInSameOrNoContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other)
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
{
var isUserContained = TryGetContainingContainer(user, out var userContainer);
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
@@ -438,33 +374,14 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
{
return IsInSameOrParentContainer((user, null), other);
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container, or if one
/// entity contains the other (i.e., is the parent).
/// </summary>
public bool IsInSameOrParentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other)
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
{
return IsInSameOrParentContainer(user, other, out _, out _);
}
/// <inheritdoc cref="IsInSameOrParentContainer(Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?},Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?})"/>
public bool IsInSameOrParentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other,
out BaseContainer? userContainer,
out BaseContainer? otherContainer)
{
var isUserContained = TryGetContainingContainer(user, out userContainer);
var isOtherContained = TryGetContainingContainer(other, out otherContainer);
var isUserContained = TryGetContainingContainer(user, out var userContainer);
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained) return true;
@@ -479,21 +396,6 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrTransparentContainer(
EntityUid user,
EntityUid other,
BaseContainer? userContainer = null,
BaseContainer? otherContainer = null,
bool userSeeInsideSelf = false)
{
return IsInSameOrTransparentContainer((user, null),
other,
userContainer,
otherContainer,
userSeeInsideSelf);
}
/// <summary>
/// Check whether a given entity can see another entity despite whatever containers they may be in.
/// </summary>
@@ -505,8 +407,8 @@ namespace Robust.Shared.Containers
/// this means that the two entity arguments are NOT interchangeable.
/// </remarks>
public bool IsInSameOrTransparentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other,
EntityUid user,
EntityUid other,
BaseContainer? userContainer = null,
BaseContainer? otherContainer = null,
bool userSeeInsideSelf = false)
@@ -531,11 +433,11 @@ namespace Robust.Shared.Containers
// Is the user in a see-through container?
if (userContainer?.ShowContents ?? false)
return IsInSameOrTransparentContainer((userContainer.Owner, null, null), other, otherContainer: otherContainer);
return IsInSameOrTransparentContainer(userContainer.Owner, other, otherContainer: otherContainer);
// Is the other entity in a see-through container?
if (otherContainer?.ShowContents ?? false)
return IsInSameOrTransparentContainer(user, (otherContainer.Owner, null, null), userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
return IsInSameOrTransparentContainer(user, otherContainer.Owner, userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
return false;
}
@@ -701,16 +603,5 @@ namespace Robust.Shared.Containers
if (TryComp(message.OldParent, out ContainerManagerComponent? containerManager))
RemoveEntity(message.OldParent.Value, message.Entity, containerManager, message.Transform, meta, reparent: false, force: true);
}
[Conditional("DEBUG"), Access(typeof(BaseContainer))]
public void AssertInContainer(EntityUid uid, BaseContainer container)
{
if (_timing.ApplyingState)
return; // Entity might not yet have had its state updated.
var flags = MetaData(uid).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0,
$"Entity has bad container flags. Ent: {ToPrettyString(uid)}. Container: {container.ID}, Owner: {ToPrettyString(container.Owner)}");
}
}
}

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
String("short").ThenReturn(PrimitiveTypeCode.Int16);
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
String("int").ThenReturn(PrimitiveTypeCode.Int32);

View File

@@ -84,12 +84,146 @@ Types:
- "bool get_HasContents()"
Lidgren.Network:
NetBuffer:
All: True
Methods:
- "byte[] get_Data()"
- "void set_Data(byte[])"
- "int get_LengthBytes()"
- "void set_LengthBytes(int)"
- "int get_LengthBits()"
- "void set_LengthBits(int)"
- "long get_Position()"
- "void set_Position(long)"
- "int get_PositionInBytes()"
- "byte[] PeekDataBuffer()"
- "bool PeekBoolean()"
- "byte PeekByte()"
- "sbyte PeekSByte()"
- "byte PeekByte(int)"
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
- "byte[] PeekBytes(int)"
- "void PeekBytes(byte[], int, int)"
- "short PeekInt16()"
- "ushort PeekUInt16()"
- "int PeekInt32()"
- "int PeekInt32(int)"
- "uint PeekUInt32()"
- "uint PeekUInt32(int)"
- "ulong PeekUInt64()"
- "long PeekInt64()"
- "ulong PeekUInt64(int)"
- "long PeekInt64(int)"
- "float PeekFloat()"
- "System.Half PeekHalf()"
- "float PeekSingle()"
- "double PeekDouble()"
- "string PeekString()"
- "int PeekStringSize()"
- "bool ReadBoolean()"
- "byte ReadByte()"
- "bool ReadByte(ref byte)"
- "sbyte ReadSByte()"
- "byte ReadByte(int)"
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
- "byte[] ReadBytes(int)"
- "bool ReadBytes(int, ref byte[])"
- "bool TryReadBytes(System.Span`1<byte>)"
- "void ReadBytes(byte[], int, int)"
- "void ReadBits(System.Span`1<byte>, int)"
- "void ReadBits(byte[], int, int)"
- "short ReadInt16()"
- "ushort ReadUInt16()"
- "int ReadInt32()"
- "bool ReadInt32(ref int)"
- "int ReadInt32(int)"
- "uint ReadUInt32()"
- "bool ReadUInt32(ref uint)"
- "uint ReadUInt32(int)"
- "ulong ReadUInt64()"
- "long ReadInt64()"
- "ulong ReadUInt64(int)"
- "long ReadInt64(int)"
- "float ReadFloat()"
- "System.Half ReadHalf()"
- "float ReadSingle()"
- "bool ReadSingle(ref float)"
- "double ReadDouble()"
- "uint ReadVariableUInt32()"
- "bool ReadVariableUInt32(ref uint)"
- "int ReadVariableInt32()"
- "long ReadVariableInt64()"
- "ulong ReadVariableUInt64()"
- "float ReadSignedSingle(int)"
- "float ReadUnitSingle(int)"
- "float ReadRangedSingle(float, float, int)"
- "int ReadRangedInteger(int, int)"
- "long ReadRangedInteger(long, long)"
- "string ReadString()"
- "bool ReadString(ref string)"
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
- "System.Net.IPEndPoint ReadIPEndPoint()"
- "void SkipPadBits()"
- "void ReadPadBits()"
- "void SkipPadBits(int)"
- "void EnsureBufferSize(int)"
- "void Write(bool)"
- "void Write(byte)"
- "void WriteAt(int, byte)"
- "void Write(sbyte)"
- "void Write(byte, int)"
- "void Write(byte[])"
- "void Write(System.ReadOnlySpan`1<byte>)"
- "void Write(byte[], int, int)"
- "void Write(ushort)"
- "void WriteAt(int, ushort)"
- "void Write(ushort, int)"
- "void Write(short)"
- "void WriteAt(int, short)"
- "void Write(int)"
- "void WriteAt(int, int)"
- "void Write(uint)"
- "void WriteAt(int, uint)"
- "void Write(uint, int)"
- "void Write(int, int)"
- "void Write(ulong)"
- "void WriteAt(int, ulong)"
- "void Write(ulong, int)"
- "void Write(long)"
- "void Write(long, int)"
- "void Write(System.Half)"
- "void Write(float)"
- "void Write(double)"
- "int WriteVariableUInt32(uint)"
- "int WriteVariableInt32(int)"
- "int WriteVariableInt64(long)"
- "int WriteVariableUInt64(ulong)"
- "void WriteSignedSingle(float, int)"
- "void WriteUnitSingle(float, int)"
- "void WriteRangedSingle(float, float, float, int)"
- "int WriteRangedInteger(int, int, int)"
- "int WriteRangedInteger(long, long, long)"
- "void Write(string)"
- "void Write(System.Net.IPEndPoint)"
- "void WriteTime(bool)"
- "void WriteTime(double, bool)"
- "void WritePadBits()"
- "void WritePadBits(int)"
- "void Write(Lidgren.Network.NetBuffer)"
- "void Zero(int)"
- "void .ctor()"
NetDeliveryMethod: { }
NetIncomingMessage:
All: True
Methods:
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
- "int get_SequenceChannel()"
- "System.Net.IPEndPoint get_SenderEndPoint()"
- "Lidgren.Network.NetConnection get_SenderConnection()"
- "double get_ReceiveTime()"
- "double ReadTime(bool)"
- "string ToString()"
NetOutgoingMessage:
All: True
Methods:
- "string ToString()"
Nett:
CommentLocation: { } # Enum
Toml:
@@ -442,14 +576,7 @@ Types:
AddressFamily: { }
System.Numerics:
BitOperations: { All: True }
Complex: { All: True }
Matrix3x2: { All: True }
Matrix4x4: { All: True }
Plane: { All: True }
Quaternion: { All: True }
Vector2: { All: True }
Vector3: { All: True }
Vector4: { All: True }
System.Reflection:
Assembly:
Methods:
@@ -777,7 +904,7 @@ Types:
Array:
Methods:
- "!!0 Find<>(!!0[], System.Predicate`1<!!0>)"
- "void Resize<>(ref !!0[], int)"
- "!!0 Resize<>(!!0[], int)"
- "!!1 ConvertAll<,>(!!0[], System.Converter`2<!!0, !!1>)"
- "!!0[] Empty<>()"
- "!!0[] FindAll<>(!!0[], System.Predicate`1<!!0>)"

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
path = path.Directory;
var fullPath = GetFullPath(path);
Process.Start(new ProcessStartInfo
if (OperatingSystem.IsWindows())
{
UseShellExecute = true,
FileName = fullPath,
});
Process.Start(new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsMacOS())
{
Process.Start(new ProcessStartInfo
{
FileName = "open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
{
Process.Start(new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else
{
throw new NotSupportedException("Opening OS windows not supported on this OS");
}
}
#endregion

View File

@@ -289,12 +289,6 @@ namespace Robust.Shared.GameObjects
return GetRegistration(componentType).Name;
}
[Pure]
public string GetComponentName<T>() where T : IComponent, new()
{
return GetRegistration<T>().Name;
}
[Pure]
public string GetComponentName(ushort netID)
{
@@ -330,7 +324,7 @@ namespace Robust.Shared.GameObjects
public ComponentRegistration GetRegistration<T>() where T : IComponent, new()
{
return GetRegistration(CompIdx.Index<T>());
return GetRegistration(typeof(T));
}
public ComponentRegistration GetRegistration(IComponent component)

View File

@@ -1,56 +1,43 @@
using System;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects;
[RequiresSerializable]
[Serializable, NetSerializable]
[Virtual]
public abstract class ComponentState : IComponentState;
/// <summary>
/// Represents the state of a component for networking purposes.
/// </summary>
public interface IComponentState;
public interface IComponentDeltaState : IComponentState
namespace Robust.Shared.GameObjects
{
public void ApplyToFullState(IComponentState fullState);
public IComponentState CreateNewFullState(IComponentState fullState);
}
/// <summary>
/// Interface for component states that only contain partial state data. The actual delta state class should be a
/// separate class from the full component states.
/// </summary>
/// <typeparam name="TState">The full-state class associated with this partial state</typeparam>
public interface IComponentDeltaState<TState> : IComponentDeltaState where TState: IComponentState
{
/// <summary>
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// </summary>
public void ApplyToFullState(TState fullState);
/// <summary>
/// This function should take in a full state and return a new full state with the current delta applied, WITHOUT
/// modifying the original input state.
/// </summary>
public TState CreateNewFullState(TState fullState);
void IComponentDeltaState.ApplyToFullState(IComponentState fullState)
[RequiresSerializable]
[Serializable, NetSerializable]
[Virtual]
public class ComponentState : IComponentState
{
if (fullState is TState state)
ApplyToFullState(state);
else
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
}
IComponentState IComponentDeltaState.CreateNewFullState(IComponentState fullState)
/// <summary>
/// Represents the state of a component for networking purposes.
/// </summary>
public interface IComponentState
{
if (fullState is TState state)
return CreateNewFullState(state);
else
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
}
/// <summary>
/// Interface for components that support delta-states.
/// </summary>
public interface IComponentDeltaState
{
/// <summary>
/// Whether this state is a delta or full state.
/// </summary>
bool FullState { get; }
/// <summary>
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// </summary>
public void ApplyToFullState(IComponentState fullState);
/// <summary>
/// This function should take in a full state and return a new full state with the current delta applied,
/// WITHOUT modifying the original input state.
/// </summary>
public IComponentState CreateNewFullState(IComponentState fullState);
}
}

View File

@@ -2,35 +2,45 @@ using System;
using System.Collections.Generic;
using Robust.Shared.Enums;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects.Components.Localization;
/// <summary>
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
// [Access(typeof(GrammarSystem))] TODO access
public sealed partial class GrammarComponent : Component
namespace Robust.Shared.GameObjects.Components.Localization
{
[DataField, AutoNetworkedField]
public Dictionary<string, string> Attributes = new();
[ViewVariables]
public Gender? Gender
/// <summary>
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent]
[NetworkedComponent()]
public sealed partial class GrammarComponent : Component
{
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
[Obsolete("Use GrammarSystem.SetGender instead")]
set => IoCManager.Resolve<IEntityManager>().System<GrammarSystem>().SetGender((Owner, this), value);
}
[DataField("attributes")]
public Dictionary<string, string> Attributes { get; private set; } = new();
[ViewVariables]
public bool? ProperNoun
{
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
[Obsolete("Use GrammarSystem.SetProperNoun instead")]
set => IoCManager.Resolve<IEntityManager>().System<GrammarSystem>().SetProperNoun((Owner, this), value);
[ViewVariables]
public Gender? Gender
{
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
set
{
if (value.HasValue)
Attributes["gender"] = value.Value.ToString();
else
Attributes.Remove("gender");
}
}
[ViewVariables]
public bool? ProperNoun
{
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
set
{
if (value.HasValue)
Attributes["proper"] = value.Value.ToString();
else
Attributes.Remove("proper");
}
}
}
}

View File

@@ -1,39 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects.Components.Localization;
namespace Robust.Shared.GameObjects;
public sealed class GrammarSystem : EntitySystem
{
public void Clear(Entity<GrammarComponent> grammar)
{
grammar.Comp.Attributes.Clear();
Dirty(grammar);
}
public bool TryGet(Entity<GrammarComponent> grammar, string key, [NotNullWhen(true)] out string? value)
{
return grammar.Comp.Attributes.TryGetValue(key, out value);
}
public void Set(Entity<GrammarComponent> grammar, string key, string? value)
{
if (value == null)
grammar.Comp.Attributes.Remove(key);
else
grammar.Comp.Attributes[key] = value;
Dirty(grammar);
}
public void SetGender(Entity<GrammarComponent> grammar, Gender? gender)
{
Set(grammar, "gender", gender?.ToString());
}
public void SetProperNoun(Entity<GrammarComponent> grammar, bool? proper)
{
Set(grammar, "proper", proper?.ToString());
}
}

View File

@@ -157,7 +157,9 @@ namespace Robust.Shared.GameObjects
if (!Initialized)
return;
_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), _parent, _localPosition, oldRotation, MapUid);
var moveEvent = new MoveEvent((Owner, this, meta), Coordinates, Coordinates, oldRotation, _localRotation, _gameTiming.ApplyingState);
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
_entMan.System<SharedTransformSystem>().InvokeGlobalMoveEvent(ref moveEvent);
}
}
@@ -332,9 +334,7 @@ namespace Robust.Shared.GameObjects
if (_localPosition.EqualsApprox(value))
return;
var oldParent = _parent;
var oldPos = _localPosition;
var oldGridPos = Coordinates;
_localPosition = value;
var meta = _entMan.GetComponent<MetaDataComponent>(Owner);
_entMan.Dirty(Owner, this, meta);
@@ -343,7 +343,9 @@ namespace Robust.Shared.GameObjects
if (!Initialized)
return;
_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), oldParent, oldPos, _localRotation, MapUid);
var moveEvent = new MoveEvent((Owner, this, meta), oldGridPos, Coordinates, _localRotation, _localRotation, _gameTiming.ApplyingState);
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
_entMan.System<SharedTransformSystem>().InvokeGlobalMoveEvent(ref moveEvent);
}
}
@@ -600,12 +602,8 @@ namespace Robust.Shared.GameObjects
/// move events, subscribe to the <see cref="SharedTransformSystem.OnGlobalMoveEvent"/>.
/// </summary>
[ByRefEvent]
public readonly struct MoveEvent(
Entity<TransformComponent, MetaDataComponent> entity,
EntityCoordinates oldPos,
EntityCoordinates newPos,
Angle oldRotation,
Angle newRotation)
public readonly struct MoveEvent(Entity<TransformComponent, MetaDataComponent> entity, EntityCoordinates oldPos,
EntityCoordinates newPos, Angle oldRotation, Angle newRotation, bool stateHandling = false)
{
public readonly Entity<TransformComponent, MetaDataComponent> Entity = entity;
public readonly EntityCoordinates OldPosition = oldPos;
@@ -617,6 +615,15 @@ namespace Robust.Shared.GameObjects
public TransformComponent Component => Entity.Comp1;
public bool ParentChanged => NewPosition.EntityId != OldPosition.EntityId;
[Obsolete("Check IGameTiming.ApplyingState")]
public readonly bool FromStateHandling = stateHandling;
[Obsolete]
public MoveEvent(EntityUid uid, EntityCoordinates oldPos, EntityCoordinates newPos, Angle oldRot, Angle newRot, TransformComponent xform, bool state)
: this((uid, xform, default!), oldPos, newPos, oldRot, newRot)
{
}
}
public struct TransformChildrenEnumerator : IDisposable

View File

@@ -19,7 +19,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// The last received state object sent from the server.
/// </summary>
protected internal BoundUserInterfaceState? State { get; internal set; }
protected BoundUserInterfaceState? State { get; private set; }
protected BoundUserInterface(EntityUid owner, Enum uiKey)
{

View File

@@ -71,17 +71,12 @@ namespace Robust.Shared.GameObjects
/// Raised whenever the server receives a BUI message from a client relating to a UI that requires input
/// validation.
/// </summary>
public sealed class BoundUserInterfaceMessageAttempt(
EntityUid actor,
EntityUid target,
Enum uiKey,
BoundUserInterfaceMessage message)
public sealed class BoundUserInterfaceMessageAttempt(EntityUid actor, EntityUid target, Enum uiKey)
: CancellableEntityEventArgs
{
public readonly EntityUid Actor = actor;
public readonly EntityUid Target = target;
public readonly Enum UiKey = uiKey;
public readonly BoundUserInterfaceMessage Message = message;
}
[NetSerializable, Serializable]
@@ -135,12 +130,12 @@ namespace Robust.Shared.GameObjects
}
[NetSerializable, Serializable]
public sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
{
}
[NetSerializable, Serializable]
public sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
internal sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
{
}

View File

@@ -167,7 +167,7 @@ namespace Robust.Shared.GameObjects
if (eventHandler == null)
throw new ArgumentNullException(nameof(eventHandler));
var order = CreateOrderingData(orderType, before, after);
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
SubscribeEventCommon<T>(source, subscriber,
(ref Unit ev) => eventHandler(Unsafe.As<Unit, T>(ref ev)), eventHandler, order, false);
@@ -187,7 +187,7 @@ namespace Robust.Shared.GameObjects
EntityEventRefHandler<T> eventHandler,
Type orderType, Type[]? before = null, Type[]? after = null) where T : notnull
{
var order = CreateOrderingData(orderType, before, after);
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
SubscribeEventCommon<T>(source, subscriber, (ref Unit ev) =>
{

View File

@@ -5,7 +5,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
@@ -14,7 +13,6 @@ internal sealed partial class EntityEventBus : IEventBus
{
private IEntityManager _entMan;
private IComponentFactory _comFac;
private IReflectionManager _reflection;
// Data on individual events. Used to check ordering info and fire broadcast events.
private FrozenDictionary<Type, EventData> _eventData = FrozenDictionary<Type, EventData>.Empty;
@@ -31,36 +29,26 @@ internal sealed partial class EntityEventBus : IEventBus
// See EventTable declaration for layout details
internal Dictionary<EntityUid, EventTable> _entEventTables = new();
/// <summary>
/// Array of component events and their handlers. The array is indexed by a component's
/// <see cref="CompIdx.Value"/>, while the dictionary is indexed by the event type. This does not include events
/// with the <see cref="ComponentEventAttribute"/>
/// </summary>
internal FrozenDictionary<Type, DirectedRegistration>[] _eventSubs = default!;
// CompType -> EventType -> Handler
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptions = default!;
/// <summary>
/// Variant of <see cref="_eventSubs"/> that also includes events with the <see cref="ComponentEventAttribute"/>
/// </summary>
internal FrozenDictionary<Type, DirectedRegistration>[] _compEventSubs = default!;
// Variant of _entSubscriptions that omits any events with the ComponentEventAttribute
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptionsNoCompEv = default!;
// pre-freeze event subscription data
internal Dictionary<Type, DirectedRegistration>?[] _eventSubsUnfrozen =
Array.Empty<Dictionary<Type, DirectedRegistration>>();
// pre-freeze _entSubscriptions data
internal Dictionary<Type, DirectedRegistration>?[] _entSubscriptionsUnfrozen =
Array.Empty<Dictionary<Type, DirectedRegistration>?>();
/// <summary>
/// Inverse of <see cref="_eventSubs"/>, mapping event types to sets of components.
/// </summary>
private Dictionary<Type, HashSet<CompIdx>> _eventSubsInv = new();
// EventType -> { CompType1, ... CompType N }
// Only required to sort ordered subscriptions, which only happens during initialization
// so doesn't need to be a frozen dictionary.
private Dictionary<Type, HashSet<CompIdx>> _entSubscriptionsInv = new();
// prevents shitcode, get your subscriptions figured out before you start spawning entities
private bool _subscriptionLock;
public bool IgnoreUnregisteredComponents;
private readonly List<Type> _childrenTypesTemp = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref Unit ExtractUnitRef(ref object obj, Type objType)
{

View File

@@ -6,7 +6,6 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Collections;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
@@ -118,12 +117,10 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="EntityEventBus"/>.
/// </summary>
/// <param name="entMan">The entity manager to watch for entity/component events.</param>
/// <param name="reflection">The reflection manager to use when finding derived types.</param>
public EntityEventBus(IEntityManager entMan, IReflectionManager reflection)
public EntityEventBus(IEntityManager entMan)
{
_entMan = entMan;
_comFac = entMan.ComponentFactory;
_reflection = reflection;
// Dynamic handling of components is only for RobustUnitTest compatibility spaghetti.
_comFac.ComponentsAdded += ComFacOnComponentsAdded;
@@ -251,7 +248,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(uid, (TComp)comp, args);
var orderData = CreateOrderingData(orderType, before, after);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -284,7 +281,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(uid, (TComp)comp, ref args);
var orderData = CreateOrderingData(orderType, before, after);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -303,7 +300,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(new Entity<TComp>(uid, (TComp) comp), ref args);
var orderData = CreateOrderingData(orderType, before, after);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -330,7 +327,7 @@ namespace Robust.Shared.GameObjects
foreach (var reg in regs)
{
CompIdx.RefArray(ref _eventSubsUnfrozen, reg.Idx) ??= new();
CompIdx.RefArray(ref _entSubscriptionsUnfrozen, reg.Idx) ??= new();
}
}
@@ -354,33 +351,29 @@ namespace Robust.Shared.GameObjects
_subscriptionLock = true;
_eventData = _eventDataUnfrozen.ToFrozenDictionary();
// Find last non-null entry.
var last = 0;
for (var i = 0; i < _eventSubsUnfrozen.Length; i++)
{
var entry = _eventSubsUnfrozen[i];
if (entry != null)
last = i;
}
// TODO PERFORMANCE
// make this only contain events that actually use comp-events
// Assuming it makes the frozen dictionaries more specialized and thus faster.
// AFAIK currently only MapInit is both a comp-event and a general event.
// It should probably be changed to just be a comp event.
_compEventSubs = _eventSubsUnfrozen
.Take(last+1)
.Select(dict => dict?.ToFrozenDictionary()!)
_entSubscriptions = _entSubscriptionsUnfrozen
.Select(x => x?.ToFrozenDictionary())
.ToArray();
_eventSubs = _eventSubsUnfrozen
.Take(last+1)
.Select(dict => dict?.Where(x => !IsComponentEvent(x.Key)).ToFrozenDictionary()!)
.ToArray();
_entSubscriptionsNoCompEv = _entSubscriptionsUnfrozen.Select(FreezeWithoutComponentEvent).ToArray();
CalcOrdering();
}
/// <summary>
/// Freezes a dictionary while committing events with the <see cref="ComponentEventAttribute"/>.
/// This avoids unnecessarily adding one-off events to the list of subscriptions.
/// </summary>
private FrozenDictionary<Type, DirectedRegistration>? FreezeWithoutComponentEvent(
Dictionary<Type, DirectedRegistration>? input)
{
if (input == null)
return null;
return input.Where(x => !IsComponentEvent(x.Key))
.ToFrozenDictionary();
}
private bool IsComponentEvent(Type t)
{
var isCompEv = _eventData[t].ComponentEvent;
@@ -402,8 +395,8 @@ namespace Robust.Shared.GameObjects
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (compType.Value >= _eventSubsUnfrozen.Length
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -418,11 +411,10 @@ namespace Robust.Shared.GameObjects
}
compSubs.Add(eventType, registration);
_entSubscriptionsInv.GetOrNew(eventType).Add(compType);
RegisterCommon(eventType, registration.Ordering, out var data);
data.ComponentEvent = eventType.HasCustomAttribute<ComponentEventAttribute>();
if (!data.ComponentEvent)
_eventSubsInv.GetOrNew(eventType).Add(compType);
}
private void EntSubscribe<TEvent>(
@@ -446,8 +438,8 @@ namespace Robust.Shared.GameObjects
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (compType.Value >= _eventSubsUnfrozen.Length
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -457,7 +449,7 @@ namespace Robust.Shared.GameObjects
var removed = compSubs.Remove(eventType);
if (removed)
_eventSubsInv[eventType].Remove(compType);
_entSubscriptionsInv[eventType].Remove(compType);
}
private void EntAddEntity(EntityUid euid)
@@ -477,7 +469,7 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(_subscriptionLock);
var eventTable = _entEventTables[euid];
var compSubs = _eventSubs[compType.Value];
var compSubs = _entSubscriptionsNoCompEv[compType.Value]!;
foreach (var evType in compSubs.Keys)
{
@@ -536,17 +528,13 @@ namespace Robust.Shared.GameObjects
private void EntRemoveComponent(EntityUid euid, CompIdx compType)
{
var eventTable = _entEventTables[euid];
var compSubs = _eventSubs[compType.Value];
var compSubs = _entSubscriptions[compType.Value]!;
foreach (var evType in compSubs.Keys)
{
DebugTools.Assert(!_eventData[evType].ComponentEvent);
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref dictIdx))
{
DebugTools.Assert("This should not be possible. Were the events for this component never added?");
continue;
}
ref var updateNext = ref dictIdx;
@@ -622,7 +610,9 @@ namespace Robust.Shared.GameObjects
ref Unit args)
where TEvent : notnull
{
if (_compEventSubs[baseType.Value].TryGetValue(typeof(TEvent), out var reg))
var compSubs = _entSubscriptions[baseType.Value]!;
if (compSubs.TryGetValue(typeof(TEvent), out var reg))
reg.Handler(euid, component, ref args);
}
@@ -644,7 +634,7 @@ namespace Robust.Shared.GameObjects
return false;
}
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _eventSubs, euid, _entMan);
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _entSubscriptions, euid, _entMan);
return true;
}
@@ -654,10 +644,10 @@ namespace Robust.Shared.GameObjects
_eventDataUnfrozen.Clear();
_entEventTables.Clear();
_inverseEventSubscriptions.Clear();
_compEventSubs = default!;
_eventSubs = default!;
_entSubscriptions = default!;
_entSubscriptionsNoCompEv = default!;
_eventData = FrozenDictionary<Type, EventData>.Empty;
foreach (var sub in _eventSubsUnfrozen)
foreach (var sub in _entSubscriptionsUnfrozen)
{
sub?.Clear();
}
@@ -670,19 +660,18 @@ namespace Robust.Shared.GameObjects
// punishment for use-after-free
_entMan = null!;
_comFac = null!;
_reflection = null!;
_entEventTables = null!;
_compEventSubs = null!;
_eventSubs = null!;
_eventSubsUnfrozen = null!;
_eventSubsInv = null!;
_entSubscriptions = null!;
_entSubscriptionsNoCompEv = null!;
_entSubscriptionsUnfrozen = null!;
_entSubscriptionsInv = null!;
}
private struct SubscriptionsEnumerator
{
private readonly Type _eventType;
private readonly EntityUid _uid;
private readonly FrozenDictionary<Type, DirectedRegistration>[] _subscriptions;
private readonly FrozenDictionary<Type, DirectedRegistration>?[] _subscriptions;
private readonly IEntityManager _entityManager;
private readonly EventTableListEntry[] _list;
private int _idx;
@@ -691,7 +680,7 @@ namespace Robust.Shared.GameObjects
Type eventType,
int startEntry,
EventTableListEntry[] list,
FrozenDictionary<Type, DirectedRegistration>[] subscriptions,
FrozenDictionary<Type, DirectedRegistration>?[] subscriptions,
EntityUid uid,
IEntityManager entityManager)
{
@@ -718,7 +707,7 @@ namespace Robust.Shared.GameObjects
_idx = entry.Next;
var compType = entry.Component;
var compSubs = _subscriptions[compType.Value];
var compSubs = _subscriptions[compType.Value]!;
if (!compSubs.TryGetValue(_eventType, out registration))
{

View File

@@ -59,10 +59,10 @@ namespace Robust.Shared.GameObjects
// Collect all subscriptions, broadcast and ordered.
IEnumerable<OrderedRegistration> regs = sub.BroadcastRegistrations;
if (_eventSubsInv.TryGetValue(eventType, out var comps))
if (_entSubscriptionsInv.TryGetValue(eventType, out var comps))
{
regs = regs.Concat(comps
.Select(c => _eventSubs[c.Value])
.Select(c => _entSubscriptions[c.Value])
.Where(c => c != null)
.Select(c => c![eventType]));
}
@@ -200,33 +200,5 @@ namespace Robust.Shared.GameObjects
}
}
}
private OrderingData CreateOrderingData(Type orderType, Type[]? before, Type[]? after)
{
AddChildrenTypes(ref before);
AddChildrenTypes(ref after);
return new OrderingData(orderType, before ?? [], after ?? []);
}
private void AddChildrenTypes(ref Type[]? original)
{
if (original == null || original.Length == 0)
return;
_childrenTypesTemp.Clear();
foreach (var beforeType in original)
{
foreach (var child in _reflection.GetAllChildren(beforeType))
{
_childrenTypesTemp.Add(child);
}
}
if (_childrenTypesTemp.Count > 0)
{
Array.Resize(ref original, original.Length + _childrenTypesTemp.Count);
_childrenTypesTemp.CopyTo(original, original.Length - _childrenTypesTemp.Count);
}
}
}
}

View File

@@ -36,6 +36,8 @@ namespace Robust.Shared.GameObjects
private const int EntityCapacity = 1024;
private const int NetComponentCapacity = 8;
private static readonly IComponentState DefaultComponentState = new ComponentState();
private FrozenDictionary<Type, Dictionary<EntityUid, IComponent>> _entTraitDict
= FrozenDictionary<Type, Dictionary<EntityUid, IComponent>>.Empty;
@@ -112,7 +114,7 @@ namespace Robust.Shared.GameObjects
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
{
DebugTools.AssertOwner(uid, metadata);
metadata ??= MetaQuery.GetComponent(uid);
metadata ??= GetComponent<MetaDataComponent>(uid);
DebugTools.Assert(metadata.EntityLifeStage == EntityLifeStage.PreInit);
SetLifeStage(metadata, EntityLifeStage.Initializing);
@@ -156,12 +158,13 @@ namespace Robust.Shared.GameObjects
// TODO: please for the love of god remove these initialization order hacks.
// Init transform first, we always have it.
var transform = TransformQuery.GetComponent(uid);
var transform = GetComponent<TransformComponent>(uid);
if (transform.LifeStage == ComponentLifeStage.Initialized)
LifeStartup(transform);
// Init physics second if it exists.
if (_physicsQuery.TryComp(uid, out var phys) && phys.LifeStage == ComponentLifeStage.Initialized)
if (TryGetComponent<PhysicsComponent>(uid, out var phys)
&& phys.LifeStage == ComponentLifeStage.Initialized)
{
LifeStartup(phys);
}
@@ -291,7 +294,7 @@ namespace Robust.Shared.GameObjects
if (!uid.IsValid() || !EntityExists(uid))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
AddComponentInternal(uid, newComponent, false, true, null);
AddComponentInternal(uid, newComponent, false, true);
return new CompInitializeHandle<T>(this, uid, newComponent, reg.Idx);
}
@@ -299,11 +302,10 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : IComponent
{
if (!MetaQuery.Resolve(uid, ref metadata, false))
if (!uid.IsValid() || !EntityExists(uid))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
if (component == null)
throw new ArgumentNullException(nameof(component));
if (component == null) throw new ArgumentNullException(nameof(component));
#pragma warning disable CS0618 // Type or member is obsolete
if (component.Owner == default)
@@ -319,17 +321,14 @@ namespace Robust.Shared.GameObjects
AddComponentInternal(uid, component, overwrite, false, metadata);
}
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent
{
if (!MetaQuery.ResolveInternal(uid, ref metadata, false))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
// get interface aliases for mapping
var reg = _componentFactory.GetRegistration(component);
AddComponentInternal(uid, component, reg, overwrite, skipInit, metadata);
}
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent metadata) where T : IComponent
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent
{
// We can't use typeof(T) here in case T is just Component
DebugTools.Assert(component is MetaDataComponent ||
@@ -643,14 +642,13 @@ namespace Robust.Shared.GameObjects
_runtimeLog.LogException(e, nameof(CullRemovedComponents));
}
#endif
var meta = MetaQuery.GetComponent(uid);
DeleteComponent(uid, component, false, meta);
DeleteComponent(uid, component, false);
}
_deleteSet.Clear();
}
private void DeleteComponent(EntityUid entityUid, IComponent component, bool terminating, MetaDataComponent? metadata)
private void DeleteComponent(EntityUid entityUid, IComponent component, bool terminating, MetaDataComponent? metadata = null)
{
if (!MetaQuery.ResolveInternal(entityUid, ref metadata))
return;
@@ -1402,13 +1400,13 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public IComponentState? GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
public IComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
{
DebugTools.Assert(component.NetSyncEnabled, $"Attempting to get component state for an un-synced component: {component.GetType()}");
var getState = new ComponentGetState(session, fromTick);
eventBus.RaiseComponentEvent(component, ref getState);
return getState.State;
return getState.State ?? DefaultComponentState;
}
public bool CanGetComponentState(IEventBus eventBus, IComponent component, ICommonSession player)
@@ -1521,7 +1519,7 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
public bool TryComp(EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
=> TryGetComponent(uid, out component);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1584,13 +1582,6 @@ namespace Robust.Shared.GameObjects
return default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 Comp(EntityUid uid)
{
return GetComponent(uid);
}
#region Internal
/// <summary>

View File

@@ -11,11 +11,9 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Timing;
@@ -41,7 +39,6 @@ namespace Robust.Shared.GameObjects
[IoC.Dependency] private readonly ISerializationManager _serManager = default!;
[IoC.Dependency] private readonly ProfManager _prof = default!;
[IoC.Dependency] private readonly INetManager _netMan = default!;
[IoC.Dependency] private readonly IReflectionManager _reflection = default!;
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
// positions on spawn....
@@ -50,7 +47,6 @@ namespace Robust.Shared.GameObjects
public EntityQuery<MetaDataComponent> MetaQuery;
public EntityQuery<TransformComponent> TransformQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ActorComponent> _actorQuery;
#endregion Dependencies
@@ -127,7 +123,7 @@ namespace Robust.Shared.GameObjects
if (Initialized)
throw new InvalidOperationException("Initialize() called multiple times");
_eventBus = new EntityEventBus(this, _reflection);
_eventBus = new EntityEventBus(this);
InitializeComponents();
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
@@ -214,7 +210,6 @@ namespace Robust.Shared.GameObjects
_containers = System<SharedContainerSystem>();
MetaQuery = GetEntityQuery<MetaDataComponent>();
TransformQuery = GetEntityQuery<TransformComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_actorQuery = GetEntityQuery<ActorComponent>();
}
@@ -283,7 +278,6 @@ namespace Robust.Shared.GameObjects
#region Entity Management
/// <inheritdoc />
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, overrides);
@@ -324,7 +318,7 @@ namespace Robust.Shared.GameObjects
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
{
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
_xforms.SetCoordinates(newEntity, transform, coords, rotation, unanchor: false);
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
}
else
{
@@ -557,7 +551,17 @@ namespace Robust.Shared.GameObjects
// Detach the base entity to null before iterating over children
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
_xforms.DetachEntity(uid, transform, metadata, parentXform, true);
if (transform.ParentUid != EntityUid.Invalid)
{
try
{
_xforms.DetachParentToNull((uid, transform, metadata), parentXform, true);
}
catch (Exception e)
{
_sawmill.Error($"Caught exception while trying to detach parent of entity '{ToPrettyString(uid, metadata)}' to null.\n{e}");
}
}
foreach (var child in transform._children)
{

View File

@@ -38,10 +38,11 @@ namespace Robust.Shared.GameObjects
[Serializable, NetSerializable]
public readonly struct ComponentChange
{
/// <summary>
// 15ish bytes to create a component (strings are big), 5 bytes to remove one
/// State data for the created/modified component, if any.
/// </summary>
public readonly IComponentState? State;
public readonly IComponentState State;
/// <summary>
/// The Network ID of the component to remove.
@@ -50,7 +51,7 @@ namespace Robust.Shared.GameObjects
public readonly GameTick LastModifiedTick;
public ComponentChange(ushort netId, IComponentState? state, GameTick lastModifiedTick)
public ComponentChange(ushort netId, IComponentState state, GameTick lastModifiedTick)
{
State = state;
NetID = netId;

View File

@@ -109,17 +109,6 @@ namespace Robust.Shared.GameObjects
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
}
protected void SubscribeLocalEvent<TComp, TEvent>(
EntityEventRefHandler<TComp, TEvent> handler,
Type[]? before = null, Type[]? after = null)
where TComp : IComponent
where TEvent : notnull
{
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
/// <seealso cref="SubscribeLocalEvent{TComp, TEvent}(ComponentEventRefHandler{TComp, TEvent}, Type[], Type[])"/>
// [Obsolete("Subscribe to the event by ref instead (ComponentEventRefHandler)")]
protected void SubscribeLocalEvent<TComp, TEvent>(
@@ -144,6 +133,17 @@ namespace Robust.Shared.GameObjects
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
protected void SubscribeLocalEvent<TComp, TEvent>(
EntityEventRefHandler<TComp, TEvent> handler,
Type[]? before = null, Type[]? after = null)
where TComp : IComponent
where TEvent : notnull
{
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
private void ShutdownSubscriptions()
{
foreach (var sub in _subscriptions)

View File

@@ -19,26 +19,26 @@ namespace Robust.Shared.GameObjects
public EntityUid? OldParent { get; }
/// <summary>
/// The map that the entity was on before its parent changed.
/// The map Id that the entity was on before its parent changed.
/// </summary>
/// <remarks>
/// If the old parent was detached to null without manually updating the map ID of its children, then this
/// is required as we cannot simply use the old parent's map ID. Also avoids having to fetch the old
/// parent's transform component.
/// </remarks>
public readonly EntityUid? OldMapId;
public MapId OldMapId { get; }
public TransformComponent Transform { get; }
/// <summary>
/// Creates a new instance of <see cref="EntParentChangedMessage"/>.
/// </summary>
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, EntityUid? oldMapId, TransformComponent xform)
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, MapId oldMapId, TransformComponent xform)
{
Entity = entity;
OldParent = oldParent;
Transform = xform;
OldMapId = oldMapId;
Transform = xform;
}
}
}

View File

@@ -160,9 +160,6 @@ namespace Robust.Shared.GameObjects
[Pure]
string GetComponentName(Type componentType);
[Pure]
string GetComponentName<T>() where T : IComponent, new();
/// <summary>
/// Gets the name of a component, throwing an exception if it does not exist.
/// </summary>

View File

@@ -404,7 +404,7 @@ namespace Robust.Shared.GameObjects
/// <param name="player">The player that is going to receive this state. Null implies that this state is for a replay.</param>
/// <param name="fromTick">The from tick, which indicates the range of data that must be included for delta-states.</param>
/// <returns>The component state of the component.</returns>
IComponentState? GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
IComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
/// <summary>
/// Checks if a certain player should get a component state.

View File

@@ -71,43 +71,12 @@ namespace Robust.Shared.GameObjects
/// </summary>
public event Action? AfterEntityFlush;
/// <summary>
/// Creates an uninitialized entity.
/// </summary>
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <param name="euid">Does nothing. Used to be the forced EntityUid of the new entity.</param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
[Obsolete($"Use one of the other {nameof(CreateEntityUninitialized)} overloads. euid no longer does anything.")]
EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null);
/// <summary>
/// Creates an uninitialized entity.
/// </summary>
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null);
/// <summary>
/// Creates an uninitialized entity and sets its position to the EntityCoordinates provided.
/// </summary>
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <param name="coordinates">Coordinates to set position and parent of the newly spawned entity to.</param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Creates an uninitialized entity and puts it on the grid or map at the MapCoordinates provided.
/// </summary>
/// <param name="prototypeName">Name of the <see cref="EntityPrototype"/> to spawn.</param>
/// <param name="coordinates">Coordinates to place the newly spawned entity.</param>
/// <param name="overrides">Overrides to add or remove components that differ from the prototype.</param>
/// <param name="rotation">Map rotation to set the newly spawned entity to.</param>
/// <returns>A new uninitialized entity.</returns>
/// <remarks>If there is a grid at the <paramref name="coordinates"/>, the entity will be parented to the grid.
/// Otherwise, it will be parented to the map.</remarks>
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);

View File

@@ -125,7 +125,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<MapChangedEvent>(OnMapChange);
_transform.OnBeforeMoveEvent += OnMove;
_transform.OnGlobalMoveEvent += OnMove;
EntityManager.EntityInitialized += OnEntityInit;
SubscribeLocalEvent<TransformComponent, PhysicsBodyTypeChangedEvent>(OnBodyTypeChange);
@@ -142,7 +142,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
{
base.Shutdown();
EntityManager.EntityInitialized -= OnEntityInit;
_transform.OnBeforeMoveEvent -= OnMove;
_transform.OnGlobalMoveEvent -= OnMove;
}
#region DynamicTree

View File

@@ -24,7 +24,7 @@ internal sealed class PrototypeReloadSystem : EntitySystem
if (!eventArgs.ByType.TryGetValue(typeof(EntityPrototype), out var set))
return;
var query = AllEntityQuery<MetaDataComponent>();
var query = EntityQueryEnumerator<MetaDataComponent>();
while (query.MoveNext(out var uid, out var metadata))
{
var id = metadata.EntityPrototype?.ID;

View File

@@ -26,6 +26,7 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<TransformStartupEvent>(OnStartup);
_transform.OnGlobalMoveEvent += OnMove;
}
private void OnStartup(ref TransformStartupEvent ev)
@@ -33,6 +34,17 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
CheckTraverse(ev.Entity.Owner, ev.Entity.Comp);
}
public override void Shutdown()
{
_transform.OnGlobalMoveEvent -= OnMove;
}
private void OnMove(ref MoveEvent moveEv)
{
CheckTraverse(moveEv.Sender, moveEv.Component);
}
internal void CheckTraverse(EntityUid uid, TransformComponent xform)
{
if (!Enabled || _timing.ApplyingState)

View File

@@ -244,51 +244,40 @@ public abstract partial class SharedMapSystem
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
{
HashSet<MapChunk> modifiedChunks;
switch (args.Current)
if (args.Current is not MapGridComponentState state)
return;
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
"Can't modify chunk size of an existing grid.");
component.ChunkSize = state.ChunkSize;
if (state.ChunkData == null && state.FullGridData == null)
return;
var modifiedChunks = new HashSet<MapChunk>();
// delta state
if (state.ChunkData != null)
{
case MapGridComponentDeltaState delta:
foreach (var chunkData in state.ChunkData)
{
modifiedChunks = new();
DebugTools.Assert(component.ChunkSize == delta.ChunkSize || component.Chunks.Count == 0,
"Can't modify chunk size of an existing grid.");
component.ChunkSize = delta.ChunkSize;
if (delta.ChunkData == null)
return;
foreach (var chunkData in delta.ChunkData)
{
ApplyChunkData(uid, component, chunkData, modifiedChunks);
}
component.LastTileModifiedTick = delta.LastTileModifiedTick;
break;
ApplyChunkData(uid, component, chunkData, modifiedChunks);
}
case MapGridComponentState state:
}
// full state
if (state.FullGridData != null)
{
foreach (var index in component.Chunks.Keys)
{
modifiedChunks = new();
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
"Can't modify chunk size of an existing grid.");
component.LastTileModifiedTick = state.LastTileModifiedTick;
component.ChunkSize = state.ChunkSize;
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
}
foreach (var (index, tiles) in state.FullGridData)
{
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
}
break;
if (!state.FullGridData.ContainsKey(index))
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
}
foreach (var (index, tiles) in state.FullGridData)
{
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
}
default:
return;
}
var count = component.Chunks.Count;
@@ -420,7 +409,7 @@ public abstract partial class SharedMapSystem
}
}
args.State = new MapGridComponentDeltaState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
#if DEBUG
if (chunkData == null)
@@ -456,7 +445,7 @@ public abstract partial class SharedMapSystem
chunkData.Add(index, tileBuffer);
}
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
#if DEBUG
foreach (var chunk in chunkData.Values)

View File

@@ -145,11 +145,6 @@ namespace Robust.Shared.GameObjects
ChunkIndex = chunkIndex;
}
/// <summary>
/// Was the tile previously empty or is it now empty.
/// </summary>
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
/// <summary>
/// EntityUid of the grid with the tile-change. TileRef stores the GridId.
/// </summary>

View File

@@ -5,7 +5,6 @@ using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Shared.Map.Components;
@@ -44,13 +43,21 @@ public abstract partial class SharedTransformSystem
xform._anchored = true;
var oldPos = xform._localPosition;
var oldRot = xform._localRotation;
var oldMap = xform.MapUid;
xform._localPosition = tilePos + newGrid.TileSizeHalfVector;
xform._localRotation += rotation;
SetGridId(uid, xform, newGridUid, XformQuery);
var reParent = new EntParentChangedMessage(uid, oldGridUid, xform.MapID, xform);
RaiseLocalEvent(uid, ref reParent, true);
var meta = MetaData(uid);
RaiseMoveEvent((uid, xform, meta), oldGridUid, oldPos, oldRot, oldMap);
var movEevee = new MoveEvent((uid, xform, meta),
new EntityCoordinates(oldGridUid, oldPos),
new EntityCoordinates(newGridUid, xform._localPosition),
oldRot,
xform.LocalRotation,
_gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref movEevee);
InvokeGlobalMoveEvent(ref movEevee);
DebugTools.Assert(XformQuery.GetComponent(oldGridUid).MapID == XformQuery.GetComponent(newGridUid).MapID);
DebugTools.Assert(xform._anchored);
@@ -314,7 +321,7 @@ public abstract partial class SharedTransformSystem
// I hate this too. Once again, required for shit like containers because they CBF doing their own init logic
// and rely on parent changed messages instead. Might also be used by broadphase stuff?
var parentEv = new EntParentChangedMessage(uid, null, null, xform);
var parentEv = new EntParentChangedMessage(uid, null, MapId.Nullspace, xform);
RaiseLocalEvent(uid, ref parentEv, true);
var ev = new TransformStartupEvent((uid, xform));
@@ -442,6 +449,9 @@ public abstract partial class SharedTransformSystem
return;
}
var oldPosition = xform._parent.IsValid() ? new EntityCoordinates(xform._parent, xform._localPosition) : default;
var oldRotation = xform._localRotation;
if (xform.Anchored && unanchor)
Unanchor(uid, xform);
@@ -460,11 +470,6 @@ public abstract partial class SharedTransformSystem
}
}
var oldParentUid = xform._parent;
var oldPosition = xform._localPosition;
var oldRotation = xform._localRotation;
var oldMap = xform.MapUid;
// Set new values
Dirty(uid, xform, meta);
xform.MatricesDirty = true;
@@ -480,7 +485,7 @@ public abstract partial class SharedTransformSystem
{
if (value.EntityId == uid)
{
DetachEntity(uid, xform);
DetachParentToNull(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent an entity to itself: {ToPrettyString(uid)}");
@@ -490,7 +495,7 @@ public abstract partial class SharedTransformSystem
{
if (!XformQuery.Resolve(value.EntityId, ref newParent, false))
{
DetachEntity(uid, xform);
DetachParentToNull(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
@@ -498,7 +503,7 @@ public abstract partial class SharedTransformSystem
if (newParent.LifeStage >= ComponentLifeStage.Stopping || LifeStage(value.EntityId) >= EntityLifeStage.Terminating)
{
DetachEntity(uid, xform);
DetachParentToNull(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
QueueDel(uid);
throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}");
@@ -523,7 +528,7 @@ public abstract partial class SharedTransformSystem
// Even though its temporary, this can still cause the client to get stuck in infinite loops while applying the game state.
// So we will just break the loop by detaching to null and just trusting that the loop wasn't actually a real feature of the server state.
Log.Warning($"Encountered circular transform hierarchy while applying state for entity: {ToPrettyString(uid)}. Detaching child to null: {ToPrettyString(recursiveUid)}");
DetachEntity(recursiveUid, recursiveXform);
DetachParentToNull(recursiveUid, recursiveXform);
break;
}
@@ -540,6 +545,7 @@ public abstract partial class SharedTransformSystem
newParent?._children.Add(uid);
xform._parent = value.EntityId;
var oldMapId = xform.MapID;
if (newParent != null)
{
@@ -570,18 +576,24 @@ public abstract partial class SharedTransformSystem
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
var entParentChangedMessage = new EntParentChangedMessage(uid, oldParent?.Owner, oldMapId, xform);
RaiseLocalEvent(uid, ref entParentChangedMessage, true);
}
}
if (!xform.Initialized)
return;
var newPosition = xform._parent.IsValid() ? new EntityCoordinates(xform._parent, xform._localPosition) : default;
#if DEBUG
// If an entity is parented to the map, its grid uid should be null (unless it is itself a grid or we have a map-grid)
if (xform.ParentUid == xform.MapUid)
DebugTools.Assert(xform.GridUid == null || xform.GridUid == uid || xform.GridUid == xform.MapUid);
#endif
RaiseMoveEvent(entity, oldParentUid, oldPosition, oldRotation, oldMap);
var moveEvent = new MoveEvent((uid, xform, meta), oldPosition, newPosition, oldRotation, xform._localRotation, _gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref moveEvent);
InvokeGlobalMoveEvent(ref moveEvent);
}
public void SetCoordinates(
@@ -656,13 +668,13 @@ public abstract partial class SharedTransformSystem
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
{
DebugTools.AssertOwner(uid, xform);
DebugTools.Assert(uid == xform.Owner);
if (xform.ParentUid == parent)
return;
if (!parent.IsValid())
{
DetachEntity(uid, xform);
DetachParentToNull(uid, xform);
return;
}
@@ -964,7 +976,7 @@ public abstract partial class SharedTransformSystem
// Entity was not actually in the transform hierarchy. This is probably a sign that something is wrong, or that the function is being misused.
Log.Warning($"Target entity ({ToPrettyString(relative)}) not in transform hierarchy while calling {nameof(GetRelativePositionRotation)}.");
var relXform = query.GetComponent(relative);
pos = GetInvWorldMatrix(relXform).Transform(pos);
pos = relXform.InvWorldMatrix.Transform(pos);
break;
}
@@ -978,6 +990,7 @@ public abstract partial class SharedTransformSystem
SetWorldPosition(xform, worldPos);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
{
@@ -1035,16 +1048,6 @@ public abstract partial class SharedTransformSystem
return rotation;
}
public void SetWorldRotationNoLerp(Entity<TransformComponent?> entity, Angle angle)
{
if (!XformQuery.Resolve(entity.Owner, ref entity.Comp))
return;
var current = GetWorldRotation(entity.Comp);
var diff = angle - current;
SetLocalRotationNoLerp(entity, entity.Comp.LocalRotation + diff);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldRotation(EntityUid uid, Angle angle)
{
@@ -1140,8 +1143,7 @@ public abstract partial class SharedTransformSystem
if (xform._localPosition.EqualsApprox(pos) && xform.LocalRotation.EqualsApprox(rot))
return;
var oldParent = xform._parent;
var oldPosition = xform._localPosition;
var oldPosition = xform.Coordinates;
var oldRotation = xform.LocalRotation;
if (!xform.Anchored)
@@ -1159,7 +1161,9 @@ public abstract partial class SharedTransformSystem
if (!xform.Initialized)
return;
RaiseMoveEvent((uid, xform, meta), oldParent, oldPosition, oldRotation, xform.MapUid);
var moveEvent = new MoveEvent((uid, xform, meta), oldPosition, xform.Coordinates, oldRotation, rot, _gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref moveEvent);
InvokeGlobalMoveEvent(ref moveEvent);
}
#endregion
@@ -1322,7 +1326,7 @@ public abstract partial class SharedTransformSystem
if (!_mapManager.IsMap(uid))
Log.Warning($"Failed to attach entity to map or grid. Entity: ({ToPrettyString(uid)}). Trace: {Environment.StackTrace}");
DetachEntity(uid, xform);
DetachParentToNull(uid, xform);
return;
}
@@ -1358,50 +1362,21 @@ public abstract partial class SharedTransformSystem
#region State Handling
[Obsolete("Use DetachEntity")]
public void DetachParentToNull(EntityUid uid, TransformComponent xform)
=> DetachEntity(uid, xform);
/// <inheritdoc cref="DetachEntityInternal"/>
public void DetachEntity(EntityUid uid, TransformComponent xform)
{
XformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
DetachEntity(uid, xform, MetaData(uid), oldXform);
DetachParentToNull(uid, xform, oldXform);
}
/// <inheritdoc cref="DetachEntityInternal"/>
public void DetachEntity(
EntityUid uid,
TransformComponent xform,
MetaDataComponent meta,
TransformComponent? oldXform,
bool terminating = false)
public void DetachParentToNull(EntityUid uid, TransformComponent xform, TransformComponent? oldXform)
{
#if !EXCEPTION_TOLERANCE
DetachEntityInternal(uid, xform, meta, oldXform, terminating);
#else
try
{
DetachEntityInternal(uid, xform, meta, oldXform, terminating);
}
catch (Exception e)
{
Log.Error($"Caught exception while attempting to detach an entity to nullspace. Entity: {ToPrettyString(uid, meta)}. Exception: {e}");
// TODO detach without content event handling.
}
#endif
DetachParentToNull((uid, xform, MetaData(uid)), oldXform);
}
/// <summary>
/// Remove an entity from the transform hierarchy and send it to null space
/// </summary>
internal void DetachEntityInternal(
EntityUid uid,
TransformComponent xform,
MetaDataComponent meta,
TransformComponent? oldXform,
bool terminating = false)
public void DetachParentToNull(Entity<TransformComponent,MetaDataComponent> entity, TransformComponent? oldXform, bool terminating = false)
{
var (uid, xform, meta) = entity;
if (!terminating && meta.EntityLifeStage >= EntityLifeStage.Terminating)
{
// Something is attempting to remove the entity from this entity's parent while it is in the process of being deleted.
@@ -1418,14 +1393,15 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0,
$"Entity is in a container but has no parent? Entity: {ToPrettyString(uid)}");
DebugTools.Assert(
xform.Broadphase == null
|| xform.Broadphase == BroadphaseData.Invalid
|| xform.Broadphase.Value.Uid == uid
|| Deleted(xform.Broadphase.Value.Uid)
|| Terminating(xform.Broadphase.Value.Uid),
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase!.Value.Uid)}");
if (xform.Broadphase != null)
{
DebugTools.Assert(
xform.Broadphase == BroadphaseData.Invalid
|| xform.Broadphase.Value.Uid == uid
|| Deleted(xform.Broadphase.Value.Uid)
|| Terminating(xform.Broadphase.Value.Uid),
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase.Value.Uid)}");
}
return;
}
@@ -1449,7 +1425,7 @@ public abstract partial class SharedTransformSystem
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
}
SetCoordinates((uid, xform, meta), default, Angle.Zero, oldParent: oldXform);
SetCoordinates(entity, default, Angle.Zero, oldParent: oldXform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0,
$"Entity is in a container after having been detached to null-space? Entity: {ToPrettyString(uid)}");
@@ -1484,7 +1460,7 @@ public abstract partial class SharedTransformSystem
var targetXform = target.Comp;
if (!XformQuery.Resolve(target, ref targetXform) || !targetXform.ParentUid.IsValid())
{
DetachEntity(entity, xform);
DetachParentToNull(entity, xform);
return;
}
@@ -1522,7 +1498,7 @@ public abstract partial class SharedTransformSystem
var targetXform = target.Comp;
if (!XformQuery.Resolve(target, ref targetXform) || !targetXform.ParentUid.IsValid())
{
DetachEntity(entity, xform);
DetachParentToNull(entity, xform);
return;
}
@@ -1542,90 +1518,4 @@ public abstract partial class SharedTransformSystem
PlaceNextTo((entity, xform), targetXform.ParentUid);
}
}
/// <summary>
/// Swaps the position of two entities, placing them inside of containers when applicable.
/// </summary>
/// <returns>Returns if the entities can have their positions swapped. Fails if the entities are parented to one another</returns>
/// <exception cref="InvalidOperationException"></exception>
public bool SwapPositions(Entity<TransformComponent?> entity1, Entity<TransformComponent?> entity2)
{
if (!XformQuery.Resolve(entity1, ref entity1.Comp) || !XformQuery.Resolve(entity2, ref entity2.Comp))
return false;
// save ourselves the hassle and just don't move anything.
if (entity1 == entity2)
return true;
// don't parent things to each other by accident
if (IsParentOf(entity1.Comp, entity2) || IsParentOf(entity2.Comp, entity1))
return false;
MapCoordinates? pos1 = null;
MapCoordinates? pos2 = null;
if (_container.TryGetContainingContainer(entity1, out var container1))
_container.Remove(entity1, container1, force: true);
else
pos1 = GetMapCoordinates(entity1.Comp);
if (_container.TryGetContainingContainer(entity2, out var container2))
_container.Remove(entity2, container2, force: true);
else
pos2 = GetMapCoordinates(entity2.Comp);
// making sure we don't accidentally place something inside of itself
if (container1 != null && container1.Owner == entity2.Owner)
return false;
if (container2 != null && container2.Owner == entity1.Owner)
return false;
if (container2 != null)
{
_container.Insert(entity1, container2);
}
else if (pos2 != null)
{
var mapUid = _mapManager.GetMapEntityId(pos2.Value.MapId);
if (!_gridQuery.HasComponent(entity1) && _mapManager.TryFindGridAt(mapUid, pos2.Value.Position, out var targetGrid, out _))
{
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
SetCoordinates(entity1, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(pos2.Value.Position)));
}
else
{
SetCoordinates(entity1, new EntityCoordinates(mapUid, pos2.Value.Position));
}
}
else
{
throw new InvalidOperationException();
}
if (container1 != null)
{
_container.Insert(entity2, container1);
}
else if (pos1 != null)
{
var mapUid = _mapManager.GetMapEntityId(pos1.Value.MapId);
if (!_gridQuery.HasComponent(entity1) && _mapManager.TryFindGridAt(mapUid, pos1.Value.Position, out var targetGrid, out _))
{
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
SetCoordinates(entity2, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(pos1.Value.Position)));
}
else
{
SetCoordinates(entity2, new EntityCoordinates(mapUid, pos1.Value.Position));
}
}
else
{
throw new InvalidOperationException();
}
return true;
}
}

View File

@@ -1,3 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Map;
@@ -34,23 +36,34 @@ public abstract partial class SharedTransformSystem
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
public EntityCoordinates WithEntityId(EntityCoordinates coordinates, EntityUid entity)
{
return entity == coordinates.EntityId
? coordinates
: ToCoordinates(entity, ToMapCoordinates(coordinates));
var mapPos = ToMapCoordinates(coordinates);
// You'd think this would throw like ToCoordinates does but TODO check that.
if (mapPos.MapId == MapId.Nullspace)
{
return new EntityCoordinates(entity, Vector2.Zero);
}
var xform = XformQuery.GetComponent(entity);
if (xform.MapID != mapPos.MapId)
{
return new EntityCoordinates(entity, Vector2.Zero);
}
var localPos = GetInvWorldMatrix(xform).Transform(mapPos.Position);
return new EntityCoordinates(entity, localPos);
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// </summary>
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates, bool logError = true)
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates)
{
if (!TryComp(coordinates.EntityId, out TransformComponent? xform))
{
if (logError)
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}");
if (!IsValid(coordinates))
return MapCoordinates.Nullspace;
}
var xform = XformQuery.GetComponent(coordinates.EntityId);
var worldPos = GetWorldMatrix(xform).Transform(coordinates.Position);
return new MapCoordinates(worldPos, xform.MapID);
}
@@ -66,129 +79,17 @@ public abstract partial class SharedTransformSystem
}
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>
public EntityCoordinates ToCoordinates(Entity<TransformComponent?> entity, MapCoordinates coordinates)
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
public EntityCoordinates ToCoordinates(EntityUid entity, MapCoordinates coordinates)
{
if (!Resolve(entity, ref entity.Comp, false))
{
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}");
return default;
}
var xform = XformQuery.GetComponent(entity);
if (entity.Comp.MapID != coordinates.MapId)
{
Log.Error($"Attempted to convert map coordinates {coordinates} to entity coordinates on a different map. Entity: {ToPrettyString(entity)}");
return default;
}
if (xform.MapID != coordinates.MapId)
throw new InvalidOperationException("Entity is not on the same map!");
var localPos = GetInvWorldMatrix(entity.Comp).Transform(coordinates.Position);
var localPos = GetInvWorldMatrix(xform).Transform(coordinates.Position);
return new EntityCoordinates(entity, localPos);
}
/// <summary>
/// Creates map-relative <see cref="EntityCoordinates"/> given some <see cref="MapCoordinates"/>.
/// </summary>
public EntityCoordinates ToCoordinates(MapCoordinates coordinates)
{
if (_map.TryGetMap(coordinates.MapId, out var uid))
return ToCoordinates(uid.Value, coordinates);
Log.Error($"Attempted to convert map coordinates with unknown map id: {coordinates}");
return default;
}
/// <summary>
/// Returns the grid that the entity whose position the coordinates are relative to is on.
/// </summary>
public EntityUid? GetGrid(EntityCoordinates coordinates)
{
return GetGrid(coordinates.EntityId);
}
public EntityUid? GetGrid(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.GridUid;
}
/// <summary>
/// Returns the Map Id these coordinates are on.
/// </summary>
public MapId GetMapId(EntityCoordinates coordinates)
{
return GetMapId(coordinates.EntityId);
}
public MapId GetMapId(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp) ? MapId.Nullspace : entity.Comp.MapID;
}
/// <summary>
/// Returns the Map that these coordinates are on.
/// </summary>
public EntityUid? GetMap(EntityCoordinates coordinates)
{
return GetMap(coordinates.EntityId);
}
public EntityUid? GetMap(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.MapUid;
}
/// <summary>
/// Compares two sets of coordinates to see if they are in range of each other.
/// </summary>
/// <param name="range">maximum distance between the two sets of coordinates.</param>
/// <returns>True if the two points are within a given range.</returns>
public bool InRange(EntityCoordinates coordA, EntityCoordinates coordB, float range)
{
if (!coordA.EntityId.IsValid() || !coordB.EntityId.IsValid())
return false;
if (coordA.EntityId == coordB.EntityId)
return (coordA.Position - coordB.Position).LengthSquared() < range * range;
var mapA = ToMapCoordinates(coordA, logError:false);
var mapB = ToMapCoordinates(coordB, logError:false);
if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace)
return false;
return mapA.InRange(mapB, range);
}
/// <summary>
/// Compares the positions of two entities to see if they are within some specified distance of each other.
/// </summary>
public bool InRange(Entity<TransformComponent?> entA, Entity<TransformComponent?> entB, float range)
{
if (!Resolve(entA, ref entA.Comp))
return false;
if (!Resolve(entB, ref entB.Comp))
return false;
if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid())
return false;
if (entA.Comp.ParentUid == entB.Comp.ParentUid)
return (entA.Comp.LocalPosition - entB.Comp.LocalPosition).LengthSquared() < range * range;
if (entA.Comp.ParentUid == entB.Owner)
return entA.Comp.LocalPosition.LengthSquared() < range * range;
if (entB.Comp.ParentUid == entA.Owner)
return entB.Comp.LocalPosition.LengthSquared() < range * range;
var mapA = GetMapCoordinates(entA!);
var mapB = GetMapCoordinates(entB!);
if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace)
return false;
return mapA.InRange(mapB, range);
}
}

Some files were not shown because too many files have changed in this diff Show More