Compare commits

..

4 Commits

Author SHA1 Message Date
Pieter-Jan Briers
6cd442d209 Version: 218.2.2 2024-08-11 19:32:35 +02:00
Pieter-Jan Briers
62167dfc94 Compile compat fixes
(cherry picked from commit 025d90d281)
(cherry picked from commit 799702b814)
(cherry picked from commit 4600ee8e5788891f1b610e2d5141fb4e1228d323)
2024-08-11 19:32:35 +02:00
Pieter-Jan Briers
472903a0af Version: 218.2.1 2024-08-11 17:56:09 +02:00
Pieter-Jan Briers
b5fa1c84fc 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:09 +02:00
211 changed files with 2638 additions and 3894 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

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

View File

@@ -54,230 +54,10 @@ END TEMPLATE-->
*None yet*
## 222.4.2
## 218.2.2
## 222.4.1
## 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.0
### New features
* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported.
* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad.
* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers.
## 220.1.0
### Bugfixes
* Fix client-side replay exceptions due to dropped states when recording.
### Other
* Remove IP + HWId from ViewVariables.
* Close BUIs upon disconnect.
## 220.0.0
### Breaking changes
* 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.
- BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly.
## 219.2.0
### New features
* Add SetMapCoordinates to TransformSystem.
* Improve YAML Linter and validation of static fields.
### Bugfixes
* Fix DebugCoordsPanel freezing when hovering a control.
### Other
* Optimise physics networking to not dirty every tick of movement.
## 219.1.3
### Bugfixes
* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map.
## 219.1.2
### Bugfixes
* Fix map-loader not map-initialising grids when loading into a post-init map.
## 219.1.1
### Bugfixes
* Fix map-loader not map-initialising maps when overwriting a post-init map.
## 219.1.0
### New features
* Added a new optional arguments to various entity spawning methods, including a new argument to set the entity's rotation.
### Bugfixes
* Fixes map initialisation not always initialising all entities on a map.
### Other
* The default value of the `auth.mode` cvar has changed
## 219.0.0
### Breaking changes
* Move most IMapManager functionality to SharedMapSystem.
## 218.2.1
## 218.2.0

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

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

@@ -26,8 +26,9 @@ public partial class AddRemoveComponentBenchmark
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
var map = _simulation.CreateMap().Uid;
var coords = new EntityCoordinates(map, default);
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{

View File

@@ -29,8 +29,8 @@ public partial class ComponentIteratorBenchmark
Comps = new A[N+2];
var map = _simulation.CreateMap().MapId;
var coords = new MapCoordinates(default, map);
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{

View File

@@ -31,8 +31,8 @@ public partial class GetComponentBenchmark
Comps = new A[N+2];
var map = _simulation.CreateMap().Uid;
var coords = new EntityCoordinates(map, default);
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{

View File

@@ -29,9 +29,10 @@ public partial class SpawnDeleteEntityBenchmark
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
var (map, mapId) = _simulation.CreateMap();
_mapCoords = new MapCoordinates(default, mapId);
_entCoords = new EntityCoordinates(map, 0, 0);
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
var uid = _simulation.AddMap(_mapCoords.MapId);
_entCoords = new EntityCoordinates(uid, 0, 0);
}
[Benchmark(Baseline = true)]

View File

@@ -91,7 +91,8 @@ public class RecursiveMoveBenchmark : RobustIntegrationTest
// Set up map and spawn player
server.WaitPost(() =>
{
var map = server.ResolveDependency<SharedMapSystem>().CreateMap(out var mapId);
var mapId = mapMan.CreateMap();
var map = mapMan.GetMapEntityId(mapId);
var gridComp = mapMan.CreateGridEntity(mapId);
var grid = gridComp.Owner;
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));

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

@@ -74,13 +74,11 @@ public sealed class AudioOverlay : Overlay
output.Clear();
output.AppendLine("Audio Source");
output.AppendLine("Runtime:");
output.AppendLine($"- Distance: {_audio.GetAudioDistance(distance.Length()):0.00}");
output.AppendLine($"- Occlusion: {posOcclusion:0.0000}");
output.AppendLine("Params:");
output.AppendLine($"- RolloffFactor: {comp.RolloffFactor:0.0000}");
output.AppendLine($"- Volume: {comp.Volume:0.0000}");
output.AppendLine($"- Reference distance: {comp.ReferenceDistance:0.00}");
output.AppendLine($"- Max distance: {comp.MaxDistance:0.00}");
output.AppendLine($"- Reference distance: {comp.ReferenceDistance}");
output.AppendLine($"- Max distance: {comp.MaxDistance}");
var outputText = output.ToString().Trim();
var dimensions = screenHandle.GetDimensions(_font, outputText, 1f);
var buffer = new Vector2(3f, 3f);

View File

@@ -388,7 +388,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var distance = delta.Length();
// Out of range so just clip it for us.
if (GetAudioDistance(distance) > component.MaxDistance)
if (distance > component.MaxDistance)
{
// Still keeps the source playing, just with no volume.
component.Gain = 0f;

View File

@@ -285,7 +285,6 @@ namespace Robust.Client
/// <summary>
/// Enumeration of the run levels of the BaseClient.
/// </summary>
/// <seealso cref="ClientRunLevelExt"/>
public enum ClientRunLevel : byte
{
Error = 0,
@@ -316,21 +315,6 @@ namespace Robust.Client
SinglePlayerGame,
}
/// <summary>
/// Helper functions for working with <see cref="ClientRunLevel"/>.
/// </summary>
public static class ClientRunLevelExt
{
/// <summary>
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
/// </summary>
public static bool IsInGameLike(this ClientRunLevel runLevel)
{
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
}
}
/// <summary>
/// Event arguments for when something changed with the player.
/// </summary>

View File

@@ -6,7 +6,6 @@ using System.Linq;
using System.Numerics;
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Animations;
@@ -29,7 +28,6 @@ using static Robust.Client.ComponentTrees.SpriteTreeSystem;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
using Direction = Robust.Shared.Maths.Direction;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.GameObjects
{
@@ -772,7 +770,15 @@ namespace Robust.Client.GameObjects
{
foreach (var keyString in layerDatum.MapKeys)
{
var key = ParseKey(keyString);
object key;
if (reflection.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.TryGetValue(key, out var mappedIndex))
{
@@ -798,30 +804,9 @@ namespace Robust.Client.GameObjects
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = layerDatum.Visible ?? layer.Visible;
if (layerDatum.CopyToShaderParameters is { } copyParameters)
{
layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey))
{
ParameterTexture = copyParameters.ParameterTexture,
ParameterUV = copyParameters.ParameterUV
};
}
else
{
layer.CopyToShaderParameters = null;
}
RebuildBounds();
}
private object ParseKey(string keyString)
{
if (reflection.TryParseEnumReference(keyString, out var @enum))
return @enum;
return keyString;
}
public void LayerSetData(object layerKey, PrototypeLayerData data)
{
if (!LayerMapTryGet(layerKey, out var layer, true))
@@ -1650,9 +1635,6 @@ namespace Robust.Client.GameObjects
[ViewVariables]
public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy;
[ViewVariables(VVAccess.ReadWrite)]
public CopyToShaderParameters? CopyToShaderParameters;
public Layer(SpriteComponent parent)
{
_parent = parent;
@@ -1681,8 +1663,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()
@@ -2027,6 +2007,8 @@ namespace Robust.Client.GameObjects
// Set the drawing transform for this layer
GetLayerDrawMatrix(dir, out var layerMatrix);
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
// due to direction overrides or offsets.
@@ -2036,41 +2018,7 @@ namespace Robust.Client.GameObjects
// Get the correct directional texture from the state, and draw it!
var texture = GetRenderTexture(_actualState, dir);
if (CopyToShaderParameters == null)
{
// Set the drawing transform for this layer
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
RenderTexture(drawingHandle, texture);
}
else
{
// Multiple atrocities to god being committed right here.
var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!];
var otherLayer = _parent.Layers[otherLayerIdx];
if (otherLayer.Shader is not { } shader)
{
// No shader set apparently..?
return;
}
if (!shader.Mutable)
otherLayer.Shader = shader = shader.Duplicate();
var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr);
var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr);
if (CopyToShaderParameters.ParameterTexture is { } paramTexture)
shader.SetParameter(paramTexture, clydeTexture);
if (CopyToShaderParameters.ParameterUV is { } paramUV)
{
var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top);
shader.SetParameter(paramUV, uv);
}
}
RenderTexture(drawingHandle, texture);
}
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
@@ -2148,23 +2096,6 @@ namespace Robust.Client.GameObjects
}
}
/// <summary>
/// Instantiated version of <see cref="PrototypeCopyToShaderParameters"/>.
/// Has <see cref="LayerKey"/> actually resolved to a a real key.
/// </summary>
public sealed class CopyToShaderParameters(object layerKey)
{
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)
{
if (!name.StartsWith("layer/"))

View File

@@ -1,9 +1,12 @@
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.Physics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.GameObjects;
@@ -13,17 +16,6 @@ public sealed class MapSystem : SharedMapSystem
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
protected override MapId GetNextMapId()
{
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
var id = new MapId(--LastMapId);
while (MapManager.MapExists(id))
{
id = new MapId(--LastMapId);
}
return id;
}
public override void Initialize()
{
base.Initialize();
@@ -35,4 +27,9 @@ public sealed class MapSystem : SharedMapSystem
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
}

View File

@@ -1,8 +1,84 @@
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using System;
using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent;
namespace Robust.Client.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
namespace Robust.Client.GameObjects
{
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
}
private void MessageReceived(BoundUIWrapMessage ev)
{
var uid = GetEntity(ev.Entity);
if (!TryComp<UserInterfaceComponent>(uid, out var cmp))
return;
var uiKey = ev.UiKey;
var message = ev.Message;
message.Session = _playerManager.LocalSession!;
message.Entity = GetNetEntity(uid);
message.UiKey = uiKey;
// Raise as object so the correct type is used.
RaiseLocalEvent(uid, (object)message, true);
switch (message)
{
case OpenBoundInterfaceMessage _:
TryOpenUi(uid, uiKey, cmp);
break;
case CloseBoundInterfaceMessage _:
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
break;
default:
if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui))
bui.InternalReceiveMessage(message);
break;
}
}
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
return false;
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
return false;
var data = uiComp.MappedInterfaceData[uiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface =
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey});
boundInterface.Open();
uiComp.OpenInterfaces[uiKey] = boundInterface;
if (_playerManager.LocalSession is { } playerSession)
{
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
}
return true;
}
}
}

View File

@@ -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()
@@ -669,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);
@@ -887,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;
@@ -941,7 +917,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, metas, xformSys);
ProcessDeletions(delSpan, xforms, xformSys);
}
catch (Exception e)
{
@@ -986,7 +962,6 @@ namespace Robust.Client.GameStates
}
var xforms = _entities.GetEntityQuery<TransformComponent>();
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_toDelete.Clear();
@@ -1015,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
@@ -1039,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.
@@ -1068,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.
@@ -1169,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)
@@ -1182,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,
@@ -1349,8 +1329,23 @@ namespace Robust.Client.GameStates
foreach (var (comp, cur, next) in _compStateWork.Values)
{
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
try
{
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (Exception e)
#pragma warning restore CS0168 // Variable is declared but never used
{
#if EXCEPTION_TOLERANCE
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
#else
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
throw;
#endif
}
}
}
@@ -1422,7 +1417,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);

View File

@@ -6,8 +6,7 @@ namespace Robust.Client.Graphics.Clyde
{
("aPos", 0),
("tCoord", 1),
("tCoord2", 2),
("modulate", 3)
("modulate", 2)
};
private const int UniIModUV = 0;

View File

@@ -124,7 +124,7 @@ namespace Robust.Client.Graphics.Clyde
{
foreach (var (grid, chunks) in _mapChunkData)
{
var gridComp = _entityManager.GetComponent<MapGridComponent>(grid);
var gridComp = _mapManager.GetGridComp(grid);
foreach (var (index, chunk) in chunks)
{
if (!chunk.Dirty || gridComp.Chunks.ContainsKey(index))

View File

@@ -251,8 +251,10 @@ namespace Robust.Client.Graphics.Clyde
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace)
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
{
return;
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities);

View File

@@ -23,12 +23,9 @@ namespace Robust.Client.Graphics.Clyde
// Texture Coords.
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
GL.EnableVertexAttribArray(1);
// Texture Coords (2).
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
GL.EnableVertexAttribArray(2);
// Colour Modulation.
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float));
GL.EnableVertexAttribArray(3);
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
GL.EnableVertexAttribArray(2);
}
// NOTE: This is:
@@ -40,7 +37,6 @@ namespace Robust.Client.Graphics.Clyde
{
public readonly Vector2 Position;
public readonly Vector2 TextureCoordinates;
public readonly Vector2 TextureCoordinates2;
// Note that this color is in linear space.
public readonly Color Modulate;
@@ -52,15 +48,6 @@ namespace Robust.Client.Graphics.Clyde
Modulate = modulate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate)
{
Position = position;
TextureCoordinates = textureCoordinates;
TextureCoordinates2 = textureCoordinates2;
Modulate = modulate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a)
: this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a))

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Graphics.Clyde
{
private RenderHandle _renderHandle = default!;
internal sealed class RenderHandle : IRenderHandle
private sealed class RenderHandle : IRenderHandle
{
private readonly Clyde _clyde;
private readonly IEntityManager _entities;
@@ -88,21 +88,16 @@ namespace Robust.Client.Graphics.Clyde
{
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
var (w, h) = clydeTexture.Size;
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
}
internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
{
var (w, h) = texture.Size;
return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
}
/// <summary>
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
/// </summary>
internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
{
if (texture is AtlasTexture atlas)
{

View File

@@ -578,10 +578,10 @@ namespace Robust.Client.Graphics.Clyde
// TODO: split batch if necessary.
var vIdx = BatchVertexIndex;
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate);
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, modulate);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate);
BatchVertexIndex += 4;
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);

View File

@@ -601,7 +601,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
internal sealed class ClydeTexture : OwnedTexture
private sealed class ClydeTexture : OwnedTexture
{
private readonly Clyde _clyde;
public readonly bool IsSrgb;

View File

@@ -1,5 +1,4 @@
varying highp vec2 UV;
varying highp vec2 UV2;
varying highp vec2 Pos;
varying highp vec4 VtxModulate;

View File

@@ -2,12 +2,10 @@
/*layout (location = 0)*/ attribute vec2 aPos;
// Texture coordinates.
/*layout (location = 1)*/ attribute vec2 tCoord;
/*layout (location = 2)*/ attribute vec2 tCoord2;
// Colour modulation.
/*layout (location = 3)*/ attribute vec4 modulate;
/*layout (location = 2)*/ attribute vec4 modulate;
varying vec2 UV;
varying vec2 UV2;
varying vec2 Pos;
varying vec4 VtxModulate;
@@ -38,6 +36,5 @@ void main()
gl_Position = vec4(VERTEX, 0.0, 1.0);
Pos = (VERTEX + 1.0) / 2.0;
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
UV2 = tCoord2;
VtxModulate = zFromSrgb(modulate);
}

View File

@@ -1,5 +1,4 @@
varying highp vec2 UV;
varying highp vec2 UV2;
uniform sampler2D lightMap;

View File

@@ -2,12 +2,10 @@
/*layout (location = 0)*/ attribute vec2 aPos;
// Texture coordinates.
/*layout (location = 1)*/ attribute vec2 tCoord;
/*layout (location = 2)*/ attribute vec2 tCoord2;
// Colour modulation.
/*layout (location = 3)*/ attribute vec4 modulate;
/*layout (location = 2)*/ attribute vec4 modulate;
varying vec2 UV;
varying vec2 UV2;
// Maybe we should merge these CPU side.
// idk yet.
@@ -42,7 +40,6 @@ void main()
vec2 VERTEX = aPos;
UV = tCoord;
UV2 = tCoord2;
// [SHADER_CODE]

View File

@@ -114,12 +114,43 @@ namespace Robust.Client.Graphics
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
}
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
{
Color colorLinear = Color.FromSrgb(color);
for (var i = 0; i < output.Length; i++)
if (input.Length == 0)
return;
if (input.Length != output.Length)
{
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
throw new InvalidOperationException("Invalid lengths!");
}
var colorLinear = Color.FromSrgb(color);
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
var simdVectors = (nuint)(input.Length / 2);
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
for (nuint i = 0; i < simdVectors; i++)
{
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
var posColorLower = (positions & maskVec) | uvVec;
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
posColorLower.StoreUnsafe(ref dstBase, i * 16);
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
}
var lastPos = (int)simdVectors * 2;
if (lastPos != output.Length)
{
// Odd number of vertices. Handle the last manually.
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
}
}
@@ -237,8 +268,6 @@ namespace Robust.Client.Graphics
{
public Vector2 Position;
public Vector2 UV;
public Vector2 UV2;
/// <summary>
/// Modulation colour for this vertex.
/// Note that this color is in linear space.

View File

@@ -17,7 +17,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
{
[ViewVariables]
[IdDataField]

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

@@ -88,6 +88,7 @@ public sealed partial class ReplayLoadManager
if (initMessages != null)
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
var entSpan = state0.EntityStates.Value;
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
@@ -97,8 +98,6 @@ public sealed partial class ReplayLoadManager
entStates.Add(entState.NetEntity, modifiedState);
}
ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates);
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
var playerSpan = state0.PlayerStates.Value;
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
@@ -145,7 +144,7 @@ public sealed partial class ReplayLoadManager
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
ProcessQueue(curState.ToSequence, detachQueue, detached);
UpdateDeletions(curState.EntityDeletions, entStates, detached);
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
@@ -177,28 +176,14 @@ public sealed partial class ReplayLoadManager
private void ProcessQueue(
GameTick curTick,
Dictionary<GameTick, List<NetEntity>> detachQueue,
HashSet<NetEntity> detached,
Dictionary<NetEntity, EntityState> entStates)
HashSet<NetEntity> detached)
{
foreach (var (tick, ents) in detachQueue)
{
if (tick > curTick)
continue;
detachQueue.Remove(tick);
foreach (var e in ents)
{
if (entStates.ContainsKey(e))
detached.Add(e);
else
{
// AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss
// I.e., entity was created on tick n, then leaves PVS range on the tick n+1
// If the n-th tick gets dropped, the client only ever receives the pvs-leave message.
// In that case we should just ignore it.
_sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?");
}
}
detached.UnionWith(ents);
}
}

View File

@@ -79,14 +79,13 @@ internal sealed partial class ReplayPlaybackManager
if (checkpoint.DetachedStates == null)
return;
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length);
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ;
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
foreach (var es in checkpoint.DetachedStates)
{
if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta))
{
DebugTools.Assert(!meta.EntityDeleted);
var uid = _entMan.GetEntity(es.NetEntity);
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
continue;
}
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
@@ -94,16 +93,18 @@ internal sealed partial class ReplayPlaybackManager
if (metaState == null)
throw new MissingMetadataException(es.NetEntity);
uid = _entMan.CreateEntity(metaState.PrototypeId, out meta);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
meta = metas.GetComponent(uid);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(meta.NetEntity);
_entMan.SetNetEntity(uid.Value, es.NetEntity, meta);
_entMan.InitializeEntity(uid.Value, meta);
_entMan.StartEntity(uid.Value);
_entMan.SetNetEntity(uid, es.NetEntity, meta);
_entMan.InitializeEntity(uid, meta);
_entMan.StartEntity(uid);
meta.LastStateApplied = checkpoint.Tick;
}
}

View File

@@ -10,7 +10,6 @@ using Robust.Shared;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
@@ -143,26 +142,6 @@ namespace Robust.Client.ResourceManagement
}
});
// Do not meta-atlas RSIs with custom load parameters.
var atlasList = rsiList.Where(x => x.LoadParameters == TextureLoadParameters.Default).ToArray();
var nonAtlasList = rsiList.Where(x => x.LoadParameters != TextureLoadParameters.Default).ToArray();
foreach (var data in nonAtlasList)
{
if (data.Bad)
continue;
try
{
RSIResource.LoadTexture(Clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
// This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY
// lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should
// try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own
@@ -176,7 +155,7 @@ namespace Robust.Client.ResourceManagement
// TODO allow RSIs to opt out (useful for very big & rare RSIs)
// TODO combine with (non-rsi) texture atlas?
Array.Sort(atlasList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
Array.Sort(rsiList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
// Each RSI sub atlas has a different size.
// Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size.
@@ -188,9 +167,9 @@ namespace Robust.Client.ResourceManagement
Vector2i offset = default;
int finalized = -1;
int atlasCount = 0;
for (int i = 0; i < atlasList.Length; i++)
for (int i = 0; i < rsiList.Length; i++)
{
var rsi = atlasList[i];
var rsi = rsiList[i];
if (rsi.Bad)
continue;
@@ -221,14 +200,14 @@ namespace Robust.Client.ResourceManagement
var height = offset.Y + deltaY;
var croppedSheet = new Image<Rgba32>(maxSize, height);
sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default);
FinalizeMetaAtlas(atlasList.Length - 1, croppedSheet);
FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet);
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
{
var atlas = Clyde.LoadTextureFromImage(sheet);
for (int i = finalized + 1; i <= toIndex; i++)
{
var rsi = atlasList[i];
var rsi = rsiList[i];
rsi.AtlasTexture = atlas;
}
@@ -276,10 +255,9 @@ namespace Robust.Client.ResourceManagement
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountNotAtlas} not atlassed, {CountErrored} errored) in {LoadTime}",
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}",
rsiList.Length,
atlasCount,
nonAtlasList.Length,
errors,
sw.Elapsed);

View File

@@ -40,21 +40,17 @@ namespace Robust.Client.ResourceManagement
var loadStepData = new LoadStepData {Path = path};
var manager = dependencies.Resolve<IResourceManager>();
LoadPreTexture(manager, loadStepData);
LoadTexture(dependencies.Resolve<IClyde>(), loadStepData);
loadStepData.AtlasTexture = dependencies.Resolve<IClyde>().LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
LoadPostTexture(loadStepData);
LoadFinish(dependencies.Resolve<IResourceCacheInternal>(), loadStepData);
loadStepData.AtlasSheet.Dispose();
}
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString(),
loadStepData.LoadParameters);
}
internal static void LoadPreTexture(IResourceManager manager, LoadStepData data)
{
var manifestPath = data.Path / "meta.json";
@@ -182,7 +178,6 @@ namespace Robust.Client.ResourceManagement
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
data.LoadParameters = metadata.LoadParameters;
}
internal static void LoadPostTexture(LoadStepData data)
@@ -385,7 +380,6 @@ namespace Robust.Client.ResourceManagement
public Texture AtlasTexture = default!;
public Vector2i AtlasOffset;
public RSI Rsi = default!;
public TextureLoadParameters LoadParameters;
}
internal struct StateReg

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

@@ -5,6 +5,7 @@ using Robust.Shared.Timing;
namespace Robust.Client.UserInterface.Controllers;
// Notices your UIController, *UwU Whats this?*
/// <summary>
/// Each <see cref="UIController"/> is instantiated as a singleton by <see cref="UserInterfaceManager"/>
/// <see cref="UIController"/> can use <see cref="DependencyAttribute"/> for regular IoC dependencies

View File

@@ -20,7 +20,6 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
private readonly StringBuilder _textBuilder = new();
private readonly char[] _textBuffer = new char[1024];
@@ -59,36 +58,30 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
_textBuilder.Clear();
var isInGame = _baseClient.RunLevel.IsInGameLike();
var mouseScreenPos = _inputManager.MouseScreenPosition;
var screenSize = _displayManager.ScreenSize;
var screenScale = _displayManager.MainWindow.ContentScale;
EntityCoordinates mouseGridPos = default;
TileRef tile = default;
MapCoordinates mouseWorldMap = default;
EntityCoordinates mouseGridPos;
TileRef tile;
if (isInGame)
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
if (mouseWorldMap == MapCoordinates.Nullspace)
return;
var mapSystem = _entityManager.System<SharedMapSystem>();
var xformSystem = _entityManager.System<SharedTransformSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
if (mouseWorldMap != MapCoordinates.Nullspace)
{
var mapSystem = _entityManager.System<SharedMapSystem>();
var xformSystem = _entityManager.System<SharedTransformSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(EntityUid.Invalid,
mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
}
}
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
}
var controlHovered = UserInterfaceManager.CurrentlyHovered;
@@ -102,37 +95,32 @@ Mouse Pos:
{tile}
GUI: {controlHovered}");
if (isInGame)
_textBuilder.AppendLine("\nAttached NetEntity:");
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
if (controlledEntity == EntityUid.Invalid)
{
var xformSystem = _entityManager.System<SharedTransformSystem>();
_textBuilder.AppendLine("No attached netentity.");
}
else
{
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
_textBuilder.AppendLine("\nAttached NetEntity:");
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
var gridRotation = entityTransform.GridUid != null
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
: Angle.Zero;
if (controlledEntity == EntityUid.Invalid)
{
_textBuilder.AppendLine("No attached netentity.");
}
else
{
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
var gridRotation = entityTransform.GridUid != null
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
: Angle.Zero;
_textBuilder.Append($@" Screen: {playerScreen}
_textBuilder.Append($@" Screen: {playerScreen}
{playerWorldOffset}
{_entityManager.GetNetCoordinates(playerCoordinates)}
Rotation: {playerRotation.Degrees:F2}°
NEntId: {_entityManager.GetNetEntity(controlledEntity)}
Grid NEntId: {_entityManager.GetNetEntity(entityTransform.GridUid)}
Grid Rotation: {gridRotation.Degrees:F2}°");
}
}
_contents.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);

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

@@ -5,7 +5,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
[Prototype("font")]
public sealed partial class FontPrototype : IPrototype
public sealed class FontPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.ContentPack;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
@@ -16,7 +18,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Themes;
[Prototype("uiTheme")]
public sealed partial class UITheme : IPrototype
public sealed class UITheme : IPrototype
{
private IResourceCache? _cache;
private IUserInterfaceManager? _uiMan;

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,7 +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 static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

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

@@ -70,11 +70,6 @@ namespace Robust.Server.Console.Commands
}
var mapId = new MapId(mapInt);
if (!_map.MapExists(mapId))
{
shell.WriteError($"map {args[0]} does not exist");
return;
}
if (shell.Player == null)
{
@@ -115,6 +110,13 @@ namespace Robust.Server.Console.Commands
private void SetupPlayer(MapId mapId, IConsoleShell shell)
{
if (mapId == MapId.Nullspace) return;
if (!_map.MapExists(mapId))
{
_map.CreateMap(mapId);
}
_map.SetMapPaused(mapId, false);
var mapUid = _map.GetMapEntityIdOrThrow(mapId);
_ent.System<Gravity2DController>().SetGravity(mapUid, new Vector2(0, -9.8f));

View File

@@ -1,12 +1,12 @@
using Robust.Shared.GameStates;
using Robust.Shared.GameObjects;
namespace Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects;
/// <summary>
/// Lets any entities with this component ignore user interface range checks that would normally
/// close the UI automatically.
/// </summary>
[RegisterComponent, NetworkedComponent]
[RegisterComponent]
public sealed partial class IgnoreUIRangeComponent : Component
{
}

View File

@@ -49,6 +49,7 @@ public sealed class MapLoaderSystem : EntitySystem
private ISawmill _logLoader = default!;
private ISawmill _logWriter = default!;
private static readonly MapLoadOptions DefaultLoadOptions = new();
private const int MapFormatVersion = 6;
private const int BackwardsVersion = 2;
@@ -131,7 +132,7 @@ public sealed class MapLoaderSystem : EntitySystem
public bool TryLoad(MapId mapId, string path, [NotNullWhen(true)] out IReadOnlyList<EntityUid>? rootUids,
MapLoadOptions? options = null)
{
options ??= new();
options ??= DefaultLoadOptions;
var resPath = new ResPath(path).ToRootedPath();
@@ -657,13 +658,11 @@ public sealed class MapLoaderSystem : EntitySystem
var xformQuery = GetEntityQuery<TransformComponent>();
// We just need to cache the old mapuid and point to the new mapuid.
if (TryComp(rootNode, out MapComponent? mapComp))
if (HasComp<MapComponent>(rootNode))
{
// If map exists swap out
if (_mapSystem.TryGetMap(data.TargetMap, out var existing))
if (_mapManager.MapExists(data.TargetMap))
{
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
data.MapIsPaused = _mapSystem.IsPaused(existing.Value);
// Map exists but we also have a map file with stuff on it soooo swap out the old map.
if (data.Options.LoadMap)
{
@@ -676,28 +675,26 @@ public sealed class MapLoaderSystem : EntitySystem
data.Options.Rotation = Angle.Zero;
}
Del(existing);
_mapManager.SetMapEntity(data.TargetMap, rootNode);
EnsureComp<LoadedMapComponent>(rootNode);
mapComp.MapId = data.TargetMap;
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
}
// Otherwise just ignore the map in the file.
else
{
var oldRootUid = data.Entities[0];
data.Entities[0] = existing.Value;
var newRootUid = _mapManager.GetMapEntityId(data.TargetMap);
data.Entities[0] = newRootUid;
foreach (var ent in data.Entities)
{
if (ent == existing)
if (ent == newRootUid)
continue;
var xform = xformQuery.GetComponent(ent);
if (!xform.ParentUid.IsValid() || xform.ParentUid.Equals(oldRootUid))
{
_transform.SetParent(ent, xform, existing.Value);
_transform.SetParent(ent, xform, newRootUid);
}
}
@@ -706,9 +703,16 @@ public sealed class MapLoaderSystem : EntitySystem
}
else
{
data.MapIsPaused = !data.MapIsPostInit;
mapComp.MapId = data.TargetMap;
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
// If we're loading a file with a map then swap out the entityuid
// TODO: Mapmanager nonsense
var AAAAA = _mapManager.CreateMap(data.TargetMap);
if (!data.MapIsPostInit)
{
_mapManager.AddUninitializedMap(data.TargetMap);
}
_mapManager.SetMapEntity(data.TargetMap, rootNode);
EnsureComp<LoadedMapComponent>(rootNode);
// Nothing should have invalid uid except for the root node.
@@ -717,15 +721,17 @@ public sealed class MapLoaderSystem : EntitySystem
else
{
// No map file root, in that case create a new map / get the one we're loading onto.
if (!_mapSystem.TryGetMap(data.TargetMap, out var mapNode))
var mapNode = _mapManager.GetMapEntityId(data.TargetMap);
if (!mapNode.IsValid())
{
// Map doesn't exist so we'll start it up now so we can re-attach the preinit entities to it for later.
mapNode = _mapSystem.CreateMap(data.TargetMap, false);
_mapManager.CreateMap(data.TargetMap);
_mapManager.AddUninitializedMap(data.TargetMap);
mapNode = _mapManager.GetMapEntityId(data.TargetMap);
DebugTools.Assert(mapNode.IsValid());
}
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
data.MapIsPaused = _mapSystem.IsPaused(mapNode.Value);
// If anything has an invalid parent (e.g. it's some form of root node) then parent it to the map.
foreach (var ent in data.Entities)
{
@@ -737,11 +743,12 @@ public sealed class MapLoaderSystem : EntitySystem
if (!xform.ParentUid.IsValid())
{
_transform.SetParent(ent, xform, mapNode.Value);
_transform.SetParent(ent, xform, mapNode);
}
}
}
data.MapIsPaused = _mapManager.IsMapPaused(data.TargetMap);
_logLoader.Debug($"Swapped out root node in {_stopwatch.Elapsed}");
}
@@ -889,7 +896,7 @@ public sealed class MapLoaderSystem : EntitySystem
{
EntityManager.SetLifeStage(metadata, EntityLifeStage.MapInitialized);
}
else if (data.Options.DoMapInit)
else if (_mapManager.IsMapInitialized(data.TargetMap))
{
_serverEntityManager.RunMapInit(uid, metadata);
}
@@ -957,7 +964,7 @@ public sealed class MapLoaderSystem : EntitySystem
// Yes, post-init maps do not have EntityLifeStage >= EntityLifeStage.MapInitialized
bool postInit;
if (TryComp(uid, out MapComponent? mapComp))
postInit = mapComp.MapInitialized;
postInit = !mapComp.MapPreInit;
else
postInit = metadata.EntityLifeStage >= EntityLifeStage.MapInitialized;
@@ -1091,17 +1098,17 @@ public sealed class MapLoaderSystem : EntitySystem
}
}
private bool IsSaveable(EntityUid uid)
private bool IsSaveable(EntityUid uid, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> transformQuery)
{
// Don't serialize things parented to un savable things.
// For example clothes inside a person.
while (uid.IsValid())
{
var meta = MetaData(uid);
var meta = metaQuery.GetComponent(uid);
if (meta.EntityDeleted || meta.EntityPrototype?.MapSavable == false) break;
uid = Transform(uid).ParentUid;
uid = transformQuery.GetComponent(uid).ParentUid;
}
// If we manage to get up to the map (root node) then it's saveable.
@@ -1116,7 +1123,7 @@ public sealed class MapLoaderSystem : EntitySystem
EntityQuery<TransformComponent> transformQuery,
EntityQuery<MapSaveIdComponent> saveCompQuery)
{
if (!IsSaveable(uid))
if (!IsSaveable(uid, metaQuery, transformQuery))
return;
entities.Add(uid);

View File

@@ -5,9 +5,9 @@ using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Server.GameObjects
{
@@ -18,16 +18,6 @@ namespace Robust.Server.GameObjects
private bool _deleteEmptyGrids;
protected override MapId GetNextMapId()
{
var id = new MapId(++LastMapId);
while (MapManager.MapExists(id))
{
id = new MapId(++LastMapId);
}
return id;
}
protected override void UpdatePvsChunks(Entity<TransformComponent, MetaDataComponent> grid)
{
_pvs.GridParentChanged(grid);
@@ -41,6 +31,11 @@ namespace Robust.Server.GameObjects
Subs.CVar(_cfg, CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
private void SetGridDeletion(bool value)
{
_deleteEmptyGrids = value;

View File

@@ -1,9 +1,416 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
namespace Robust.Server.GameObjects
{
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly TransformSystem _xformSys = default!;
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
private readonly List<ICommonSession> _sessionCache = new();
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(OnMessageReceived);
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
}
public override void Shutdown()
{
base.Shutdown();
_playerMan.PlayerStatusChanged -= OnPlayerStatusChanged;
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
{
if (args.NewStatus != SessionStatus.Disconnected)
return;
if (!OpenInterfaces.TryGetValue(args.Session, out var buis))
return;
foreach (var bui in buis.ToArray())
{
CloseShared(bui, args.Session);
}
}
/// <inheritdoc />
public override void Update(float frameTime)
{
var xformQuery = GetEntityQuery<TransformComponent>();
var query = AllEntityQuery<ActiveUserInterfaceComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var activeUis, out var xform))
{
foreach (var ui in activeUis.Interfaces)
{
CheckRange(uid, activeUis, ui, xform, xformQuery);
if (!ui.StateDirty)
continue;
ui.StateDirty = false;
foreach (var (player, state) in ui.PlayerStateOverrides)
{
RaiseNetworkEvent(state, player.Channel);
}
if (ui.LastStateMsg == null)
continue;
foreach (var session in ui.SubscribedSessions)
{
if (!ui.PlayerStateOverrides.ContainsKey(session))
RaiseNetworkEvent(ui.LastStateMsg, session.Channel);
}
}
}
}
/// <summary>
/// Verify that the subscribed clients are still in range of the interface.
/// </summary>
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
{
if (ui.InteractionRange <= 0)
return;
// We have to cache the set of sessions because Unsubscribe modifies the original.
_sessionCache.Clear();
_sessionCache.AddRange(ui.SubscribedSessions);
var uiPos = _xformSys.GetWorldPosition(transform, query);
var uiMap = transform.MapID;
foreach (var session in _sessionCache)
{
// The component manages the set of sessions, so this invalid session should be removed soon.
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
continue;
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
continue;
// Handle pluggable BoundUserInterfaceCheckRangeEvent
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session);
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
continue;
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
{
CloseUi(ui, session, activeUis);
continue;
}
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
if (uiMap != xform.MapID)
{
CloseUi(ui, session, activeUis);
continue;
}
var distanceSquared = (uiPos - _xformSys.GetWorldPosition(xform, query)).LengthSquared();
if (distanceSquared > ui.InteractionRangeSqrd)
CloseUi(ui, session, activeUis);
}
}
#region Get BUI
public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!Resolve(uid, ref ui))
return false;
return ui.Interfaces.ContainsKey(uiKey);
}
public PlayerBoundUserInterface GetUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!Resolve(uid, ref ui))
throw new InvalidOperationException($"Cannot get {typeof(PlayerBoundUserInterface)} from an entity without {typeof(UserInterfaceComponent)}!");
return ui.Interfaces[uiKey];
}
public PlayerBoundUserInterface? GetUiOrNull(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
return TryGetUi(uid, uiKey, out var bui, ui)
? bui
: null;
}
/// <summary>
/// Return UIs a session has open.
/// Null if empty.
/// </summary>
public List<PlayerBoundUserInterface>? GetAllUIsForSession(ICommonSession session)
{
OpenInterfaces.TryGetValue(session, out var value);
return value;
}
#endregion
public bool IsUiOpen(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return bui.SubscribedSessions.Count > 0;
}
public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return bui.SubscribedSessions.Contains(session);
}
/// <summary>
/// Sets a state. This can be used for stateful UI updating.
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
/// Pretty much how NanoUI did it back in ye olde BYOND.
/// </summary>
/// <param name="state">
/// The state object that will be sent to all current and future client.
/// This can be null.
/// </param>
/// <param name="session">
/// The player session to send this new state to.
/// Set to null for sending it to every subscribed player session.
/// </param>
public bool TrySetUiState(EntityUid uid,
Enum uiKey,
BoundUserInterfaceState state,
ICommonSession? session = null,
UserInterfaceComponent? ui = null,
bool clearOverrides = true)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
SetUiState(bui, state, session, clearOverrides);
return true;
}
/// <summary>
/// Sets a state. This can be used for stateful UI updating.
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
/// Pretty much how NanoUI did it back in ye olde BYOND.
/// </summary>
/// <param name="state">
/// The state object that will be sent to all current and future client.
/// This can be null.
/// </param>
/// <param name="session">
/// The player session to send this new state to.
/// Set to null for sending it to every subscribed player session.
/// </param>
public void SetUiState(PlayerBoundUserInterface bui, BoundUserInterfaceState state, ICommonSession? session = null, bool clearOverrides = true)
{
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey);
if (session == null)
{
bui.LastStateMsg = msg;
if (clearOverrides)
bui.PlayerStateOverrides.Clear();
}
else
{
bui.PlayerStateOverrides[session] = msg;
}
bui.StateDirty = true;
}
#region Close
protected override void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null)
{
var owner = bui.Owner;
bui._subscribedSessions.Remove(session);
bui.PlayerStateOverrides.Remove(session);
if (OpenInterfaces.TryGetValue(session, out var buis))
buis.Remove(bui);
RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session));
if (bui._subscribedSessions.Count == 0)
DeactivateInterface(bui.Owner, bui, activeUis);
}
/// <summary>
/// Closes this all interface for any clients that have any open.
/// </summary>
public bool TryCloseAll(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent? aui = null)
{
if (!Resolve(uid, ref aui, false))
return false;
foreach (var ui in aui.Interfaces)
{
CloseAll(ui);
}
return true;
}
/// <summary>
/// Closes this specific interface for any clients that have it open.
/// </summary>
public bool TryCloseAll(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
CloseAll(bui);
return true;
}
/// <summary>
/// Closes this interface for any clients that have it open.
/// </summary>
public void CloseAll(PlayerBoundUserInterface bui)
{
foreach (var session in bui.SubscribedSessions.ToArray())
{
CloseUi(bui, session);
}
}
#endregion
#region SendMessage
/// <summary>
/// Send a BUI message to all connected player sessions.
/// </summary>
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
SendUiMessage(bui, message);
return true;
}
/// <summary>
/// Send a BUI message to all connected player sessions.
/// </summary>
public void SendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message)
{
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey);
foreach (var session in bui.SubscribedSessions)
{
RaiseNetworkEvent(msg, session.Channel);
}
}
/// <summary>
/// Send a BUI message to a specific player session.
/// </summary>
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return TrySendUiMessage(bui, message, session);
}
/// <summary>
/// Send a BUI message to a specific player session.
/// </summary>
public bool TrySendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message, ICommonSession session)
{
if (!bui.SubscribedSessions.Contains(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.Channel);
return true;
}
#endregion
}
/// <summary>
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
/// </summary>
[ByRefEvent]
[PublicAPI]
public struct BoundUserInterfaceCheckRangeEvent
{
/// <summary>
/// The entity owning the UI being checked for.
/// </summary>
public readonly EntityUid Target;
/// <summary>
/// The UI itself.
/// </summary>
/// <returns></returns>
public readonly PlayerBoundUserInterface UserInterface;
/// <summary>
/// The player for which the UI is being checked.
/// </summary>
public readonly ICommonSession Player;
/// <summary>
/// The result of the range check.
/// </summary>
public BoundUserInterfaceRangeResult Result;
public BoundUserInterfaceCheckRangeEvent(
EntityUid target,
PlayerBoundUserInterface userInterface,
ICommonSession player)
{
Target = target;
UserInterface = userInterface;
Player = player;
}
}
/// <summary>
/// Possible results for a <see cref="BoundUserInterfaceCheckRangeEvent"/>.
/// </summary>
public enum BoundUserInterfaceRangeResult : byte
{
/// <summary>
/// Run built-in range check.
/// </summary>
Default,
/// <summary>
/// Range check passed, UI is accessible.
/// </summary>
Pass,
/// <summary>
/// Range check failed, UI is inaccessible.
/// </summary>
Fail
}
}

View File

@@ -86,7 +86,7 @@ namespace Robust.Server.GameObjects
StartEntity(entity);
}
internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return base.CreateEntity(prototypeName, out metadata, context);

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

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

@@ -53,7 +53,5 @@ namespace Robust.Server.Maps
/// This should be set to false if you want to load a map file onto an existing map and do not wish to overwrite the existing entity.
/// </remarks>
public bool LoadMap { get; set; } = true;
public bool DoMapInit = false;
}
}

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;
}

View File

@@ -10,7 +10,7 @@ namespace Robust.Shared.Audio;
/// to allow the server to know audio lengths without shipping the large audio files themselves.
/// </summary>
[Prototype(ProtoName)]
public sealed partial class AudioMetadataPrototype : IPrototype
public sealed class AudioMetadataPrototype : IPrototype
{
public const string ProtoName = "audioMetadata";

View File

@@ -9,7 +9,7 @@ namespace Robust.Shared.Audio;
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
/// </summary>
[Prototype("audioPreset")]
public sealed partial class AudioPresetPrototype : IPrototype
public sealed class AudioPresetPrototype : IPrototype
{
[IdDataField]
public string ID { get; } = default!;

View File

@@ -1,13 +1,13 @@
using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
namespace Robust.Shared.Audio;
[Prototype("soundCollection")]
public sealed partial class SoundCollectionPrototype : IPrototype
public sealed class SoundCollectionPrototype : IPrototype
{
[ViewVariables]
[IdDataField]

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
*/
@@ -866,7 +851,7 @@ namespace Robust.Shared
/// See the documentation of the <see cref="Network.AuthMode"/> enum for values.
/// </summary>
public static readonly CVarDef<int> AuthMode =
CVarDef.Create("auth.mode", (int) Network.AuthMode.Required, CVar.SERVERONLY);
CVarDef.Create("auth.mode", (int) Network.AuthMode.Optional, CVar.SERVERONLY);
/// <summary>
/// Allow unauthenticated localhost connections, even if the auth mode is set to required.

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

@@ -10,8 +10,7 @@ namespace Robust.Shared.Console.Commands;
sealed class AddMapCommand : LocalizedCommands
{
[Dependency] private readonly IMapManagerInternal _map = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "addmap";
public override bool RequireServerOrSingleplayer => true;
@@ -25,8 +24,11 @@ sealed class AddMapCommand : LocalizedCommands
if (!_map.MapExists(mapId))
{
var init = args.Length < 2 || !bool.Parse(args[1]);
_entMan.System<SharedMapSystem>().CreateMap(mapId, runMapInit: init);
_map.CreateMap(mapId);
if (args.Length >= 2 && args[1] == "false")
{
_map.AddUninitializedMap(mapId);
}
shell.WriteLine($"Map with ID {mapId} created.");
return;

View File

@@ -576,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:
@@ -911,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

@@ -140,7 +140,7 @@ namespace Robust.Shared.ContentPack
{
Process.Start(new ProcessStartInfo
{
FileName = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\explorer.exe",
FileName = "explorer.exe",
Arguments = ".",
WorkingDirectory = fullPath,
});

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

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

@@ -29,50 +29,9 @@ public sealed partial class PrototypeLayerData
[DataField("map")] public HashSet<string>? MapKeys;
[DataField("renderingStrategy")] public LayerRenderingStrategy? RenderingStrategy;
/// <summary>
/// If set, indicates that this sprite layer should instead be used to copy into shader parameters on another layer.
/// </summary>
/// <remarks>
/// <para>
/// If set, this sprite layer is not rendered. Instead, the "result" of rendering it (exact sprite layer and such)
/// are copied into the shader parameters of another object,
/// specified by the <see cref="PrototypeCopyToShaderParameters"/>.
/// </para>
/// <para>
/// The specified layer must have a shader set. When it does, the shader's
/// </para>
/// <para>
/// Note that sprite layers are processed in-order, so to avoid 1-frame delays,
/// the layer doing the copying should occur BEFORE the layer being copied into.
/// </para>
/// </remarks>
[DataField] public PrototypeCopyToShaderParameters? CopyToShaderParameters;
[DataField] public bool Cycle;
}
/// <summary>
/// Stores parameters for <see cref="PrototypeLayerData.CopyToShaderParameters"/>.
/// </summary>
[Serializable, NetSerializable, DataDefinition]
public sealed partial class PrototypeCopyToShaderParameters
{
/// <summary>
/// The map key of the layer that will have its shader modified.
/// </summary>
[DataField(required: true)] public string LayerKey;
/// <summary>
/// The name of the shader parameter that will receive the actual selected texture.
/// </summary>
[DataField] public string? ParameterTexture;
/// <summary>
/// The name of the shader parameter that will receive UVs to select the sprite in <see cref="ParameterTexture"/>.
/// </summary>
[DataField] public string? ParameterUV;
}
[Serializable, NetSerializable]
public enum LayerRenderingStrategy
{

View File

@@ -101,6 +101,7 @@ namespace Robust.Shared.GameObjects
internal bool _mapIdInitialized;
internal bool _gridInitialized;
// TODO: Cache this.
/// <summary>
/// The EntityUid of the map which this object is on, if any.
/// </summary>
@@ -157,7 +158,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 +335,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 +344,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 +603,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 +616,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)
{
@@ -41,14 +41,14 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Invoked when the server uses <c>SetState</c>.
/// </summary>
protected internal virtual void UpdateState(BoundUserInterfaceState state)
protected virtual void UpdateState(BoundUserInterfaceState state)
{
}
/// <summary>
/// Invoked when the server sends an arbitrary message.
/// </summary>
protected internal virtual void ReceiveMessage(BoundUserInterfaceMessage message)
protected virtual void ReceiveMessage(BoundUserInterfaceMessage message)
{
}
@@ -57,7 +57,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public void Close()
{
UiSystem.CloseUi(Owner, UiKey, _playerManager.LocalEntity, predicted: true);
UiSystem.TryCloseUi(_playerManager.LocalSession, Owner, UiKey);
}
/// <summary>
@@ -65,7 +65,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public void SendMessage(BoundUserInterfaceMessage message)
{
UiSystem.ClientSendUiMessage(Owner, UiKey, message);
UiSystem.SendUiMessage(this, message);
}
public void SendPredictedMessage(BoundUserInterfaceMessage message)
@@ -73,6 +73,20 @@ namespace Robust.Shared.GameObjects
UiSystem.SendPredictedUiMessage(this, message);
}
internal void InternalReceiveMessage(BoundUserInterfaceMessage message)
{
switch (message)
{
case UpdateBoundStateMessage updateBoundStateMessage:
State = updateBoundStateMessage.State;
UpdateState(State);
break;
default:
ReceiveMessage(message);
break;
}
}
~BoundUserInterface()
{
Dispose(false);

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects;
/// <summary>
/// Represents an entity-bound interface that can be opened by multiple players at once.
/// </summary>
[PublicAPI]
public sealed class PlayerBoundUserInterface
{
[ViewVariables]
public float InteractionRange;
[ViewVariables]
public float InteractionRangeSqrd => InteractionRange * InteractionRange;
[ViewVariables]
public Enum UiKey { get; }
[ViewVariables]
public EntityUid Owner { get; }
internal readonly HashSet<ICommonSession> _subscribedSessions = new();
[ViewVariables]
internal BoundUIWrapMessage? LastStateMsg;
[ViewVariables(VVAccess.ReadWrite)]
public bool RequireInputValidation;
[ViewVariables]
internal bool StateDirty;
[ViewVariables]
internal readonly Dictionary<ICommonSession, BoundUIWrapMessage> PlayerStateOverrides =
new();
/// <summary>
/// All of the sessions currently subscribed to this UserInterface.
/// </summary>
[ViewVariables]
public IReadOnlySet<ICommonSession> SubscribedSessions => _subscribedSessions;
public PlayerBoundUserInterface(PrototypeData data, EntityUid owner)
{
RequireInputValidation = data.RequireInputValidation;
UiKey = data.UiKey;
Owner = owner;
InteractionRange = data.InteractionRange;
}
}

View File

@@ -1,26 +1,29 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects;
[RegisterComponent, NetworkedComponent]
public sealed partial class ActiveUserInterfaceComponent : Component
namespace Robust.Shared.GameObjects
{
}
[PublicAPI]
public sealed class ServerBoundUserInterfaceMessage
{
[ViewVariables]
public BoundUserInterfaceMessage Message { get; }
[ViewVariables]
public ICommonSession Session { get; }
public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session)
[RegisterComponent]
public sealed partial class ActiveUserInterfaceComponent : Component
{
Message = message;
Session = session;
[ViewVariables]
public HashSet<PlayerBoundUserInterface> Interfaces = new();
}
[PublicAPI]
public sealed class ServerBoundUserInterfaceMessage
{
[ViewVariables]
public BoundUserInterfaceMessage Message { get; }
[ViewVariables]
public ICommonSession Session { get; }
public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session)
{
Message = message;
Session = session;
}
}
}

View File

@@ -8,51 +8,36 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[RegisterComponent, NetworkedComponent, Access(typeof(SharedUserInterfaceSystem))]
[RegisterComponent, NetworkedComponent]
public sealed partial class UserInterfaceComponent : Component
{
/// <summary>
/// The currently open interfaces. Used clientside to store the UI.
/// </summary>
[ViewVariables, Access(Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.ReadWriteExecute)]
public readonly Dictionary<Enum, BoundUserInterface> ClientOpenInterfaces = new();
// TODO: Obviously clean this shit up, I just moved it into shared.
[DataField]
internal Dictionary<Enum, InterfaceData> Interfaces = new();
[ViewVariables] public readonly Dictionary<Enum, BoundUserInterface> OpenInterfaces = new();
[ViewVariables] public readonly Dictionary<Enum, PlayerBoundUserInterface> Interfaces = new();
public Dictionary<Enum, PrototypeData> MappedInterfaceData = new();
/// <summary>
/// Actors that currently have interfaces open.
/// Loaded on Init from serialized data.
/// </summary>
[DataField]
public Dictionary<Enum, List<EntityUid>> Actors = new();
/// <summary>
/// Legacy data, new BUIs should be using comp states.
/// </summary>
public Dictionary<Enum, BoundUserInterfaceState> States = new();
[Serializable, NetSerializable]
internal sealed class UserInterfaceComponentState(
Dictionary<Enum, List<NetEntity>> actors,
Dictionary<Enum, BoundUserInterfaceState> states)
: IComponentState
{
public Dictionary<Enum, List<NetEntity>> Actors = actors;
public Dictionary<Enum, BoundUserInterfaceState> States = states;
}
[DataField("interfaces")] internal List<PrototypeData> InterfaceData = new();
}
[DataDefinition]
public sealed partial class InterfaceData
public sealed partial class PrototypeData
{
[DataField("key", required: true)]
public Enum UiKey { get; private set; } = default!;
[DataField("type", required: true)]
public string ClientType { get; private set; } = default!;
/// <summary>
/// Maximum range before a BUI auto-closes. A non-positive number means there is no limit.
/// </summary>
[DataField]
[DataField("range")]
public float InteractionRange = 2f;
// TODO BUI move to content?
@@ -63,7 +48,7 @@ namespace Robust.Shared.GameObjects
/// <remarks>
/// Avoids requiring each system to individually validate client inputs. However, perhaps some BUIs are supposed to be bypass accessibility checks
/// </remarks>
[DataField]
[DataField("requireInputValidation")]
public bool RequireInputValidation = true;
}
@@ -71,12 +56,18 @@ 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)
: CancellableEntityEventArgs
public sealed class BoundUserInterfaceMessageAttempt : CancellableEntityEventArgs
{
public readonly EntityUid Actor = actor;
public readonly EntityUid Target = target;
public readonly Enum UiKey = uiKey;
public readonly ICommonSession Sender;
public readonly EntityUid Target;
public readonly Enum UiKey;
public BoundUserInterfaceMessageAttempt(ICommonSession sender, EntityUid target, Enum uiKey)
{
Sender = sender;
Target = target;
UiKey = uiKey;
}
}
[NetSerializable, Serializable]
@@ -113,7 +104,7 @@ namespace Robust.Shared.GameObjects
/// Only set when the message is raised as a directed event.
/// </summary>
[NonSerialized]
public EntityUid Actor = default!;
public ICommonSession Session = default!;
}
/// <summary>
@@ -129,6 +120,17 @@ namespace Robust.Shared.GameObjects
public NetEntity Entity { get; set; } = NetEntity.Invalid;
}
[NetSerializable, Serializable]
internal sealed class UpdateBoundStateMessage : BoundUserInterfaceMessage
{
public readonly BoundUserInterfaceState State;
public UpdateBoundStateMessage(BoundUserInterfaceState state)
{
State = state;
}
}
[NetSerializable, Serializable]
internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
{
@@ -140,38 +142,59 @@ namespace Robust.Shared.GameObjects
}
[Serializable, NetSerializable]
internal abstract class BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
: EntityEventArgs
internal abstract class BaseBoundUIWrapMessage : EntityEventArgs
{
public readonly NetEntity Entity = entity;
public readonly BoundUserInterfaceMessage Message = message;
public readonly Enum UiKey = uiKey;
public readonly NetEntity Entity;
public readonly BoundUserInterfaceMessage Message;
public readonly Enum UiKey;
public BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
{
Message = message;
UiKey = uiKey;
Entity = entity;
}
}
/// <summary>
/// Helper message raised from client to server.
/// </summary>
[Serializable, NetSerializable]
internal sealed class BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
: BaseBoundUIWrapMessage(entity, message, uiKey);
internal sealed class BoundUIWrapMessage : BaseBoundUIWrapMessage
{
public BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey)
{
}
}
/// <summary>
/// Helper message raised from client to server.
/// </summary>
[Serializable, NetSerializable]
internal sealed class PredictedBoundUIWrapMessage : BaseBoundUIWrapMessage
{
public PredictedBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey)
{
}
}
public sealed class BoundUIOpenedEvent : BaseLocalBoundUserInterfaceEvent
{
public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, EntityUid actor)
public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, ICommonSession session)
{
UiKey = uiKey;
Entity = uid;
Actor = actor;
Session = session;
}
}
public sealed class BoundUIClosedEvent : BaseLocalBoundUserInterfaceEvent
{
public BoundUIClosedEvent(Enum uiKey, EntityUid uid, EntityUid actor)
public BoundUIClosedEvent(Enum uiKey, EntityUid uid, ICommonSession session)
{
UiKey = uiKey;
Entity = uid;
Actor = actor;
Session = session;
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects;
/// <summary>
/// Stores data about this entity and what BUIs they have open.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class UserInterfaceUserComponent : Component
{
public override bool SessionSpecific => true;
[DataField]
public Dictionary<EntityUid, List<Enum>> OpenInterfaces = new();
}
[Serializable, NetSerializable]
internal sealed class UserInterfaceUserComponentState : IComponentState
{
public Dictionary<NetEntity, List<Enum>> OpenInterfaces = new();
}

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

@@ -114,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);
@@ -158,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);
}
@@ -293,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);
}
@@ -301,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)
@@ -321,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 ||
@@ -645,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;
@@ -1017,11 +1013,6 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Internal variant of <see cref="GetComponents"/> that directly returns the actual component set.
/// </summary>
internal IReadOnlyCollection<IComponent> GetComponentsInternal(EntityUid uid) => _entCompIndex[uid];
/// <inheritdoc />
public int ComponentCount(EntityUid uid)
{
@@ -1523,7 +1514,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)]
@@ -1586,13 +1577,6 @@ namespace Robust.Shared.GameObjects
return default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 Comp(EntityUid uid)
{
return GetComponent(uid);
}
#region Internal
/// <summary>

View File

@@ -5,17 +5,16 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Containers;
using Robust.Shared.Maths;
namespace Robust.Shared.GameObjects;
public partial class EntityManager
{
// This method will soon(TM) be marked as obsolete.
// This method will soon be marked as obsolete.
public EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> SpawnAttachedTo(protoName, coordinates, overrides);
// This method will soon(TM) be marked as obsolete.
// This method will soon be marked as obsolete.
public EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
=> Spawn(protoName, coordinates, overrides);
@@ -84,16 +83,12 @@ public partial class EntityManager
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
{
var entity = CreateEntityUninitialized(protoName, MapCoordinates.Nullspace, overrides);
InitializeAndStartEntity(entity, doMapInit);
return entity;
}
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null)
=> Spawn(protoName, MapCoordinates.Nullspace, overrides);
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation);
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
InitializeAndStartEntity(entity, coordinates.MapId);
return entity;
}
@@ -122,8 +117,7 @@ public partial class EntityManager
return true;
}
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
uid = Spawn(protoName, overrides, doMapInit);
uid = Spawn(protoName, overrides);
if (_containers.Insert(uid.Value, container))
return true;
@@ -147,8 +141,7 @@ public partial class EntityManager
if (!containerComp.Containers.TryGetValue(containerId, out var container))
return false;
var doMapInit = _mapSystem.IsInitialized(TransformQuery.GetComponent(containerUid).MapUid);
uid = Spawn(protoName, overrides, doMapInit);
uid = Spawn(protoName, overrides);
if (_containers.Insert(uid.Value, container))
return true;
@@ -164,8 +157,7 @@ public partial class EntityManager
if (!xform.ParentUid.IsValid())
return Spawn(protoName);
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
var uid = Spawn(protoName, overrides, doMapInit);
var uid = Spawn(protoName, overrides);
_xforms.DropNextTo(uid, target);
return uid;
}
@@ -190,16 +182,16 @@ public partial class EntityManager
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
var uid = Spawn(protoName, overrides);
inserted = true;
xform ??= TransformQuery.GetComponent(containerUid);
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
var uid = Spawn(protoName, overrides, doMapInit);
if ((containerComp == null && !TryGetComponent(containerUid, out containerComp))
|| !containerComp.Containers.TryGetValue(containerId, out var container)
|| !_containers.Insert(uid, container))
{
inserted = false;
xform ??= TransformQuery.GetComponent(containerUid);
if (xform.ParentUid.IsValid())
_xforms.DropNextTo(uid, (containerUid, xform));
}

View File

@@ -9,13 +9,10 @@ using Robust.Shared.GameStates;
using Robust.Shared.Log;
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 +38,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 +46,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 +122,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 +209,6 @@ namespace Robust.Shared.GameObjects
_containers = System<SharedContainerSystem>();
MetaQuery = GetEntityQuery<MetaDataComponent>();
TransformQuery = GetEntityQuery<TransformComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_actorQuery = GetEntityQuery<ActorComponent>();
}
@@ -283,7 +277,6 @@ namespace Robust.Shared.GameObjects
#region Entity Management
/// <inheritdoc />
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, overrides);
@@ -304,13 +297,14 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, out _, overrides);
var transform = TransformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
DebugTools.Assert(_mapManager.GetMapEntityId(coordinates.MapId) == EntityUid.Invalid);
transform._parent = EntityUid.Invalid;
transform.Anchored = false;
return newEntity;
@@ -329,7 +323,7 @@ namespace Robust.Shared.GameObjects
else
{
coords = new EntityCoordinates(mapEnt, coordinates.Position);
_xforms.SetCoordinates(newEntity, transform, coords, rotation, newParent: mapXform);
_xforms.SetCoordinates(newEntity, transform, coords, null, newParent: mapXform);
}
return newEntity;
@@ -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)
{
@@ -772,7 +776,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
internal virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return AllocEntity(out metadata);
@@ -817,22 +821,15 @@ namespace Robust.Shared.GameObjects
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
{
var doMapInit = _mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
InitializeAndStartEntity(entity, doMapInit);
}
public void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit)
{
if (!MetaQuery.Resolve(entity.Owner, ref entity.Comp))
return;
try
{
InitializeEntity(entity.Owner, entity.Comp);
StartEntity(entity.Owner);
var meta = MetaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
if (doMapInit)
RunMapInit(entity.Owner, entity.Comp);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID))
RunMapInit(entity, meta);
}
catch (Exception e)
{
@@ -862,7 +859,7 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(meta.EntityLifeStage == EntityLifeStage.Initialized, $"Expected entity {ToPrettyString(entity)} to be initialized, was {meta.EntityLifeStage}");
SetLifeStage(meta, EntityLifeStage.MapInitialized);
EventBus.RaiseLocalEvent(entity, MapInitEventInstance);
EventBus.RaiseLocalEvent(entity, MapInitEventInstance, false);
}
/// <inheritdoc />

View File

@@ -5,10 +5,8 @@ using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using TerraFX.Interop.Windows;
namespace Robust.Shared.GameObjects;
@@ -701,32 +699,32 @@ public partial class EntitySystem
#region Entity Spawning
// This method will be obsoleted soon(TM).
// This method will be obsoleted soon.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype, EntityCoordinates coordinates)
{
return ((IEntityManager)EntityManager).SpawnEntity(prototype, coordinates);
}
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?, Angle)" />
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
=> EntityManager.Spawn(prototype, coordinates, overrides, rotation);
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates)
=> EntityManager.Spawn(prototype, coordinates);
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?, bool)" />
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype = null, ComponentRegistry? overrides = null, bool doMapInit = true)
=> EntityManager.Spawn(prototype, overrides, doMapInit);
protected EntityUid Spawn(string? prototype = null)
=> EntityManager.Spawn(prototype);
/// <inheritdoc cref="IEntityManager.SpawnAttachedTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> EntityManager.SpawnAttachedTo(prototype, coordinates, overrides);
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates)
=> EntityManager.SpawnAttachedTo(prototype, coordinates);
/// <inheritdoc cref="IEntityManager.SpawnAtPosition" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> EntityManager.SpawnAtPosition(prototype, coordinates, overrides);
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates)
=> EntityManager.SpawnAtPosition(prototype, coordinates);
/// <inheritdoc cref="IEntityManager.TrySpawnInContainer" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

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

@@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Robust.Shared.GameObjects;
@@ -28,12 +27,12 @@ public partial interface IEntityManager
/// <summary>
/// Spawns an entity in nullspace.
/// </summary>
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true);
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null);
/// <summary>
/// Spawns an entity at a specific world position. The entity will either be parented to the map or a grid.
/// </summary>
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Spawns an entity and then parents it to the entity that the given entity coordinates are relative to.

View File

@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Prometheus;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -71,49 +70,16 @@ 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!);
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);
void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
void StartEntity(EntityUid entity);

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

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