mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3caffa04da | ||
|
|
06b377d1d5 | ||
|
|
41fb191dda | ||
|
|
d4bcc1dc05 |
@@ -57,7 +57,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,98 +54,10 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 262.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Toolshed commands will now validate that each non-generic command argument is parseable (i.e., has a corresponding type parser). This check can be disabled by explicitly marking the argument as unparseable via `CommandArgumentAttribute.Unparseable`.
|
||||
|
||||
### New features
|
||||
|
||||
* `ToolshedManager.TryParse` now also supports nullable value types.
|
||||
* Add an ignoredComponents arg to IsDefault.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `SpriteComponent.Layer.Visible` setter not marking a sprite's bounding box as dirty.
|
||||
* The audio params in the passed SoundSpecifier for PlayStatic(SoundSpecifier, Filter, ...) will now be used as a default like other PlayStatic overrides.
|
||||
* Fix windows not saving their positions correctly when their x position is <= 0.
|
||||
* Fix transform state handling overriding PVS detachment.
|
||||
## 260.2.2
|
||||
|
||||
|
||||
## 261.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Implement IEquatable for ResolvedPathSpecifier & ResolvedCollectionSpecifier.
|
||||
* Add NearestChunkEnumerator.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix static entities not having the center of mass updated.
|
||||
* Fix TryQueueDelete.
|
||||
* Fix tpto potentially parenting grids to non-map entities.
|
||||
|
||||
### Other
|
||||
|
||||
* TileChangedEvent is now raised once in clientside grid state handling rather than per tile.
|
||||
* Removed ITileDefinition.ID as it was redundant.
|
||||
* Change the lifestage checks on predicted entity deletion to check for terminating.
|
||||
|
||||
### Internal
|
||||
|
||||
* Update some `GetComponentName<T>` uses to generic.
|
||||
|
||||
|
||||
## 261.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Automatically create logger sawmills for `UIController`s similar to `EntitySystem`s.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix physics forces not auto-clearing / respecting the cvar.
|
||||
|
||||
### Internal
|
||||
|
||||
* Cleanup more compiler warnings in unit tests.
|
||||
|
||||
|
||||
## 261.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove unused TryGetContainingContainer override.
|
||||
* Stop recursive FrameUpdates for controls that are not visible.
|
||||
* Initialize LocMgr earlier in the callstack for GameController.
|
||||
* Fix FastNoiseLise fractal bounding and remove its DataField property as it should be derived on other properties updating.
|
||||
* Make RaiseMoveEvent internal.
|
||||
* MovedGridsComponent and PhysicsMapComponent are now purged and properties on `SharedPhysicsSystem`. Additionally the TransformComponent for Awake entities is stored alongside the PhysicsComponent for them.
|
||||
* TransformComponent is now stored on physics contacts.
|
||||
* Gravity2DComponent and Gravity2DController were moved to SharedPhysicsSystem.
|
||||
|
||||
### New features
|
||||
|
||||
* `IFileDialogManager` now allows specifying `FileAccess` and `FileShare` modes.
|
||||
* Add Intersects and Enlarged to Box2i in line with Box2.
|
||||
* Make `KeyFrame`s on `AnimationTrackProperty` public settable.
|
||||
* Add the spawned entities to a returned array from `SpawnEntitiesAttachedTo`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed SDL3 file dialog implementation having a memory leak and not opening files read-write.
|
||||
* Fix GetMapLinearVelocity.
|
||||
|
||||
### Other
|
||||
|
||||
* `uploadfile` and `loadprototype` commands now only open files with read access.
|
||||
* Optimize `ToMapCoordinates`.
|
||||
|
||||
### Internal
|
||||
|
||||
* Cleanup on internals of `IFileDialogManager`, removing duplicate code.
|
||||
* Fix Contacts not correctly being marked as `Touching` while contact is ongoing.
|
||||
## 260.2.1
|
||||
|
||||
|
||||
## 260.2.0
|
||||
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public abstract class AnimationTrackProperty : AnimationTrack
|
||||
{
|
||||
public List<KeyFrame> KeyFrames { get; set; } = new();
|
||||
public List<KeyFrame> KeyFrames { get; protected set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// How to interpolate values when between two keyframes.
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class SpriteTreeSystem : ComponentTreeSystem<SpriteTreeComponent,
|
||||
protected override Box2 ExtractAabb(in ComponentTreeEntry<SpriteComponent> entry, Vector2 pos, Angle rot)
|
||||
{
|
||||
// TODO SPRITE optimize this
|
||||
// Because the just take the BB of the rotated BB, I'm pretty sure we do a lot of unnecessary maths.
|
||||
// Because the just take the BB of the rotated BB, I'mt pretty sure we do a lot of unnecessary maths.
|
||||
return _sprite.CalculateBounds((entry.Uid, entry.Component), pos, rot, default).CalcBoundingBox();
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,6 @@ namespace Robust.Client
|
||||
}
|
||||
|
||||
_serializationManager.Initialize();
|
||||
_loc.Initialize();
|
||||
|
||||
// Call Init in game assemblies.
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
|
||||
@@ -183,6 +182,7 @@ namespace Robust.Client
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_loc.Initialize();
|
||||
|
||||
// Make sure this is done before we try to load prototypes,
|
||||
// avoid any possibility of race conditions causing the check to not finish
|
||||
@@ -387,7 +387,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -296,7 +296,7 @@ namespace Robust.Client.GameObjects
|
||||
public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
||||
{
|
||||
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|
||||
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|
||||
|| ent.Comp1.EntityDeleted
|
||||
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
|
||||
{
|
||||
return;
|
||||
@@ -322,7 +322,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
if (IsQueuedForDeletion(ent.Owner)
|
||||
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|
||||
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|
||||
|| ent.Comp1.EntityDeleted
|
||||
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public sealed class SpriteBoundsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
|
||||
|
||||
private SpriteBoundsOverlay? _overlay;
|
||||
|
||||
@@ -40,7 +42,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.AssertNull(_overlay);
|
||||
_overlay = new SpriteBoundsOverlay(EntityManager);
|
||||
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
|
||||
_overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
@@ -55,13 +57,18 @@ namespace Robust.Client.GameObjects
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
public sealed class SpriteBoundsOverlay(IEntityManager entMan) : Overlay
|
||||
public sealed class SpriteBoundsOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly SharedTransformSystem _xformSystem = entMan.System<SharedTransformSystem>();
|
||||
private readonly SpriteSystem _spriteSystem = entMan.System<SpriteSystem>();
|
||||
private readonly SpriteTreeSystem _renderTree = entMan.System<SpriteTreeSystem>();
|
||||
private readonly SharedTransformSystem _xformSystem;
|
||||
private SpriteTreeSystem _renderTree;
|
||||
|
||||
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
|
||||
{
|
||||
_renderTree = renderTree;
|
||||
_xformSystem = xformSystem;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
@@ -69,11 +76,10 @@ namespace Robust.Client.GameObjects
|
||||
var currentMap = args.MapId;
|
||||
var viewport = args.WorldBounds;
|
||||
|
||||
foreach (var entry in _renderTree.QueryAabb(currentMap, viewport))
|
||||
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
|
||||
{
|
||||
var (sprite, xform) = entry;
|
||||
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
|
||||
var bounds = _spriteSystem.CalculateBounds((entry.Uid, sprite), worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
|
||||
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
|
||||
|
||||
// Get scaled down bounds used to indicate the "south" of a sprite.
|
||||
var localBound = bounds.Box;
|
||||
|
||||
@@ -1224,8 +1224,6 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
_visible = value;
|
||||
|
||||
Owner.Comp.BoundsDirty = true;
|
||||
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
if (_parent.Owner != EntityUid.Invalid)
|
||||
Owner.Comp.Sys?.QueueUpdateIsInert(Owner);
|
||||
@@ -1793,15 +1791,76 @@ namespace Robust.Client.GameObjects
|
||||
[Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")]
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
return sys.GetPrototypeTextures(prototype, out noRot);
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
|
||||
// TODO when moving to a non-static method in a system, pass in IComponentFactory
|
||||
if (prototype.TryGetComponent(out IconComponent? icon))
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
results.Add(sys.GetIcon(icon));
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!prototype.Components.TryGetValue("Sprite", out _))
|
||||
{
|
||||
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
|
||||
return results;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
EntitySystem.Get<AppearanceSystem>().OnChangeData(dummy, spriteComponent);
|
||||
|
||||
foreach (var layer in spriteComponent.AllLayers)
|
||||
{
|
||||
if (!layer.Visible) continue;
|
||||
|
||||
if (layer.Texture != null)
|
||||
{
|
||||
results.Add(layer.Texture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!layer.RsiState.IsValid) continue;
|
||||
|
||||
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
|
||||
if (rsi == null ||
|
||||
!rsi.TryGetState(layer.RsiState, out var state))
|
||||
continue;
|
||||
|
||||
results.Add(state);
|
||||
}
|
||||
|
||||
noRot = spriteComponent.NoRotation;
|
||||
|
||||
entityManager.DeleteEntity(dummy);
|
||||
|
||||
if (results.Count == 0)
|
||||
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem.GetPrototypeIcon() instead")]
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
return sys.GetPrototypeIcon(prototype);
|
||||
// TODO when moving to a non-static method in a system, pass in IComponentFactory
|
||||
if (prototype.TryGetComponent(out IconComponent? icon))
|
||||
return sys.GetIcon(icon);
|
||||
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
return sys.GetFallbackState();
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
var result = spriteComponent.Icon ?? sys.GetFallbackState();
|
||||
entityManager.DeleteEntity(dummy);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ public sealed partial class SpriteSystem
|
||||
/// </summary>
|
||||
public IRsiStateLike GetPrototypeIcon(string prototype)
|
||||
{
|
||||
// Check if this prototype has been cached before, and if so return the result.
|
||||
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
|
||||
return cachedResult;
|
||||
|
||||
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
|
||||
{
|
||||
// The specified prototype doesn't exist, return the fallback "error" sprite.
|
||||
@@ -63,7 +67,11 @@ public sealed partial class SpriteSystem
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
return GetPrototypeIcon(entityPrototype);
|
||||
// Generate the icon and cache it in case it's ever needed again.
|
||||
var result = GetPrototypeIcon(entityPrototype);
|
||||
_cachedPrototypeIcons[prototype] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,19 +79,13 @@ public sealed partial class SpriteSystem
|
||||
/// This method does NOT cache the result.
|
||||
/// </summary>
|
||||
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
|
||||
{
|
||||
// This method may spawn & delete an entity to get an accruate RSI state, hence we cache the results
|
||||
if (_cachedPrototypeIcons.TryGetValue(prototype.ID, out var cachedResult))
|
||||
return cachedResult;
|
||||
|
||||
return _cachedPrototypeIcons[prototype.ID] = GetPrototypeIconInternal(prototype);
|
||||
}
|
||||
|
||||
private IRsiStateLike GetPrototypeIconInternal(EntityPrototype prototype)
|
||||
{
|
||||
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
|
||||
if (prototype.TryGetComponent(out IconComponent? icon, _factory))
|
||||
if (prototype.Components.TryGetValue("Icon", out var compData)
|
||||
&& compData.Component is IconComponent icon)
|
||||
{
|
||||
return GetIcon(icon);
|
||||
}
|
||||
|
||||
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
@@ -100,63 +102,6 @@ public sealed partial class SpriteSystem
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto) =>
|
||||
GetPrototypeTextures(proto, out _);
|
||||
|
||||
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto, out bool noRot)
|
||||
{
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
|
||||
if (proto.TryGetComponent(out IconComponent? icon, _factory))
|
||||
{
|
||||
results.Add(GetIcon(icon));
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!proto.Components.ContainsKey("Sprite"))
|
||||
{
|
||||
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
|
||||
return results;
|
||||
}
|
||||
|
||||
var dummy = Spawn(proto.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
|
||||
|
||||
// TODO SPRITE is this needed?
|
||||
// And if it is, shouldn't GetPrototypeIconInternal also use this?
|
||||
_appearance.OnChangeData(dummy, spriteComponent);
|
||||
|
||||
foreach (var layer in spriteComponent.AllLayers)
|
||||
{
|
||||
if (!layer.Visible)
|
||||
continue;
|
||||
|
||||
if (layer.Texture != null)
|
||||
{
|
||||
results.Add(layer.Texture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!layer.RsiState.IsValid)
|
||||
continue;
|
||||
|
||||
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
|
||||
continue;
|
||||
|
||||
results.Add(state);
|
||||
}
|
||||
|
||||
noRot = spriteComponent.NoRotation;
|
||||
Del(dummy);
|
||||
|
||||
if (results.Count == 0)
|
||||
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public RSI.State GetFallbackState()
|
||||
{
|
||||
|
||||
@@ -34,12 +34,8 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
|
||||
// Note that any new system dependencies have to be added to RobustUnitTest.BaseSetup()
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
[Dependency] private readonly SpriteTreeSystem _tree = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
|
||||
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
@@ -631,7 +631,7 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
if ((meta.Flags & MetaDataFlags.Detached) == 0 && compState != null)
|
||||
if (compState != null)
|
||||
{
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(entity, comp, ref handleState);
|
||||
|
||||
@@ -467,7 +467,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowing!.RunOnWindowThread(a);
|
||||
}
|
||||
|
||||
public IFileDialogManagerImplementation? FileDialogImpl => _windowing as IFileDialogManagerImplementation;
|
||||
public IFileDialogManager? FileDialogImpl => _windowing as IFileDialogManager;
|
||||
|
||||
private abstract class WindowReg
|
||||
{
|
||||
|
||||
@@ -307,7 +307,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
action();
|
||||
}
|
||||
|
||||
public IFileDialogManagerImplementation? FileDialogImpl => null;
|
||||
public IFileDialogManager? FileDialogImpl => null;
|
||||
|
||||
private sealed class DummyCursor : ICursor
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -11,16 +12,31 @@ namespace Robust.Client.Graphics.Clyde;
|
||||
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed partial class Sdl3WindowingImpl : IFileDialogManagerImplementation
|
||||
private sealed partial class Sdl3WindowingImpl : IFileDialogManager
|
||||
{
|
||||
public async Task<string?> OpenFile(FileDialogFilters? filters)
|
||||
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
|
||||
{
|
||||
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
|
||||
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
|
||||
if (fileName == null)
|
||||
return null;
|
||||
|
||||
return File.OpenRead(fileName);
|
||||
}
|
||||
|
||||
public async Task<string?> SaveFile(FileDialogFilters? filters)
|
||||
public async Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
|
||||
{
|
||||
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
|
||||
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
|
||||
if (fileName == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return (File.Open(fileName, truncate ? FileMode.Truncate : FileMode.Open), true);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return (File.Open(fileName, FileMode.Create), false);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe Task<string?> ShowFileDialogOfType(int type, FileDialogFilters? filters)
|
||||
@@ -58,8 +74,6 @@ internal partial class Clyde
|
||||
NativeMemory.Free(filter.name);
|
||||
NativeMemory.Free(filter.pattern);
|
||||
}
|
||||
|
||||
NativeMemory.Free(filtersAlloc);
|
||||
}
|
||||
|
||||
return task;
|
||||
|
||||
@@ -71,6 +71,6 @@ namespace Robust.Client.Graphics
|
||||
|
||||
void RunOnWindowThread(Action action);
|
||||
|
||||
IFileDialogManagerImplementation? FileDialogImpl { get; }
|
||||
IFileDialogManager? FileDialogImpl { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.Physics
|
||||
* This will draw above every body involved in a particular island solve.
|
||||
*/
|
||||
|
||||
public readonly Queue<(TimeSpan Time, List<Entity<PhysicsComponent, TransformComponent>> Bodies)> IslandSolve = new();
|
||||
public readonly Queue<(TimeSpan Time, List<PhysicsComponent> Bodies)> IslandSolve = new();
|
||||
public const float SolveDuration = 0.1f;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -90,10 +90,9 @@ public sealed partial class PhysicsSystem
|
||||
// existing contacts for predicted entities before performing any actual prediction.
|
||||
|
||||
var contacts = new List<Contact>();
|
||||
var maps = new HashSet<EntityUid>();
|
||||
|
||||
var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
|
||||
_broadphase.FindNewContacts();
|
||||
|
||||
while (enumerator.MoveNext(out _, out var physics, out var xform))
|
||||
{
|
||||
DebugTools.Assert(physics.Predict);
|
||||
@@ -101,6 +100,10 @@ public sealed partial class PhysicsSystem
|
||||
if (xform.MapUid is not { } map)
|
||||
continue;
|
||||
|
||||
if (maps.Add(map) && PhysMapQuery.TryGetComponent(map, out var physMap) &&
|
||||
MapQuery.TryGetComponent(map, out var mapComp))
|
||||
_broadphase.FindNewContacts(physMap, mapComp.MapId);
|
||||
|
||||
contacts.AddRange(physics.Contacts);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -24,23 +23,21 @@ namespace Robust.Client.Physics
|
||||
SimulateWorld(frameTime, _gameTiming.InPrediction);
|
||||
}
|
||||
|
||||
protected override void Cleanup(float frameTime)
|
||||
protected override void Cleanup(PhysicsMapComponent component, float frameTime)
|
||||
{
|
||||
var toRemove = new ValueList<Entity<PhysicsComponent, TransformComponent>>();
|
||||
var toRemove = new List<Entity<PhysicsComponent>>();
|
||||
|
||||
// Because we're not predicting 99% of bodies its sleep timer never gets incremented so we'll just do it ourselves.
|
||||
// (and serializing it over the network isn't necessary?)
|
||||
// This is a client-only problem.
|
||||
// Also need to suss out having the client build the island anyway and just... not solving it?
|
||||
foreach (var ent in AwakeBodies)
|
||||
foreach (var body in component.AwakeBodies)
|
||||
{
|
||||
var body = ent.Comp1;
|
||||
|
||||
if (!body.SleepingAllowed || body.LinearVelocity.Length() > LinearToleranceSqr / 2f || body.AngularVelocity * body.AngularVelocity > AngularToleranceSqr / 2f) continue;
|
||||
body.SleepTime += frameTime;
|
||||
if (body.SleepTime > TimeToSleep)
|
||||
{
|
||||
toRemove.Add(ent);
|
||||
toRemove.Add(new Entity<PhysicsComponent>(body.Owner, body));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,38 +46,37 @@ namespace Robust.Client.Physics
|
||||
SetAwake(body, false);
|
||||
}
|
||||
|
||||
base.Cleanup(frameTime);
|
||||
base.Cleanup(component, frameTime);
|
||||
}
|
||||
|
||||
protected override void UpdateLerpData(List<Entity<PhysicsComponent, TransformComponent>> bodies)
|
||||
protected override void UpdateLerpData(PhysicsMapComponent component, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
foreach (var bodyEnt in bodies)
|
||||
foreach (var body in bodies)
|
||||
{
|
||||
var body = bodyEnt.Comp1;
|
||||
var xform = bodyEnt.Comp2;
|
||||
|
||||
if (body.BodyType == BodyType.Static ||
|
||||
LerpData.TryGetValue(bodyEnt, out var lerpData) ||
|
||||
lerpData == xform.ParentUid)
|
||||
component.LerpData.TryGetValue(body.Owner, out var lerpData) ||
|
||||
!xformQuery.TryGetComponent(body.Owner, out var xform) ||
|
||||
lerpData.ParentUid == xform.ParentUid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LerpData[bodyEnt.Owner] = xform.ParentUid;
|
||||
component.LerpData[xform.Owner] = (xform.ParentUid, xform.LocalPosition, xform.LocalRotation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush all of our lerping data.
|
||||
/// </summary>
|
||||
protected override void FinalStep()
|
||||
protected override void FinalStep(PhysicsMapComponent component)
|
||||
{
|
||||
base.FinalStep();
|
||||
base.FinalStep(component);
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var (uid, parentUid) in LerpData)
|
||||
foreach (var (uid, (parentUid, position, rotation)) in component.LerpData)
|
||||
{
|
||||
// Can't just re-use xform from before as movement events may cause event subs to fire.
|
||||
if (!XformQuery.TryGetComponent(uid, out var xform) || !parentUid.IsValid())
|
||||
if (!xformQuery.TryGetComponent(uid, out var xform) ||
|
||||
!parentUid.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -89,7 +85,7 @@ namespace Robust.Client.Physics
|
||||
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
|
||||
}
|
||||
|
||||
LerpData.Clear();
|
||||
component.LerpData.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class LoadPrototypeCommand : IConsoleCommand
|
||||
var dialogManager = IoCManager.Resolve<IFileDialogManager>();
|
||||
var loadManager = IoCManager.Resolve<IGamePrototypeLoadManager>();
|
||||
|
||||
var stream = await dialogManager.OpenFile(access: FileAccess.Read);
|
||||
var stream = await dialogManager.OpenFile();
|
||||
if (stream is null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.IO;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -37,7 +36,7 @@ public sealed class UploadFileCommand : IConsoleCommand
|
||||
var path = new ResPath(args[0]).ToRelativePath();
|
||||
|
||||
var filters = new FileDialogFilters(new FileDialogFilters.Group(path.Extension));
|
||||
await using var file = await _dialog.OpenFile(filters, FileAccess.Read);
|
||||
await using var file = await _dialog.OpenFile(filters);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
|
||||
@@ -994,9 +994,6 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
internal int DoFrameUpdateRecursive(FrameEventArgs args)
|
||||
{
|
||||
if (!Visible)
|
||||
return 0;
|
||||
|
||||
var total = 1;
|
||||
FrameUpdate(args);
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controllers;
|
||||
@@ -14,14 +11,11 @@ namespace Robust.Client.UserInterface.Controllers;
|
||||
/// and <see cref="UISystemDependencyAttribute"/> to depend on <see cref="EntitySystem"/>s, which will be automatically
|
||||
/// injected once they are created.
|
||||
/// </summary>
|
||||
public abstract partial class UIController : IPostInjectInit
|
||||
public abstract partial class UIController
|
||||
{
|
||||
[Dependency] protected readonly IUserInterfaceManager UIManager = default!;
|
||||
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
[Dependency] protected readonly IEntityManager EntityManager = default!;
|
||||
[Dependency] protected readonly ILogManager LogManager = default!;
|
||||
|
||||
public ISawmill Log { get; protected set; } = default!;
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
@@ -30,39 +24,4 @@ public abstract partial class UIController : IPostInjectInit
|
||||
public virtual void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual string SawmillName
|
||||
{
|
||||
get
|
||||
{
|
||||
var name = GetType().Name;
|
||||
|
||||
// Strip trailing "UIController"
|
||||
if (name.EndsWith("UIController"))
|
||||
name = name.Substring(0, name.Length - "UIController".Length);
|
||||
|
||||
// Convert CamelCase to snake_case
|
||||
// Ignore if all uppercase, assume acronym (e.g. NPC or HTN)
|
||||
if (name.All(char.IsUpper))
|
||||
{
|
||||
name = name.ToLower(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
|
||||
name = name.Trim('_');
|
||||
}
|
||||
|
||||
return $"ui.{name}";
|
||||
}
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
Log = LogManager.GetSawmill(SawmillName);
|
||||
|
||||
#if !DEBUG
|
||||
Log.Level = LogLevel.Info;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +145,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// This is to avoid unnecessarily setting a position where our size isn't yet fully updated.
|
||||
// This most commonly happens with saved window positions if your window position is <= 0.
|
||||
if (!IsMeasureValid)
|
||||
return;
|
||||
|
||||
var (spaceX, spaceY) = Parent!.Size;
|
||||
|
||||
var maxX = spaceX - ((AllowOffScreen & DirectionFlag.West) == 0 ? Size.X : WindowEdgeSeparation);
|
||||
|
||||
@@ -8,19 +8,12 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
internal sealed class DummyFileDialogManager : IFileDialogManager
|
||||
{
|
||||
public Task<Stream?> OpenFile(
|
||||
FileDialogFilters? filters = null,
|
||||
FileAccess access = FileAccess.ReadWrite,
|
||||
FileShare? share = null)
|
||||
public Task<Stream?> OpenFile(FileDialogFilters? filters = null)
|
||||
{
|
||||
return Task.FromResult<Stream?>(null);
|
||||
}
|
||||
|
||||
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
|
||||
FileDialogFilters? filters = null,
|
||||
bool truncate = true,
|
||||
FileAccess access = FileAccess.ReadWrite,
|
||||
FileShare share = FileShare.None)
|
||||
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
|
||||
{
|
||||
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -28,28 +30,18 @@ namespace Robust.Client.UserInterface
|
||||
private bool _kDialogAvailable;
|
||||
private bool _checkedKDialogAvailable;
|
||||
|
||||
public async Task<Stream?> OpenFile(
|
||||
FileDialogFilters? filters = null,
|
||||
FileAccess access = FileAccess.ReadWrite,
|
||||
FileShare? share = null)
|
||||
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
|
||||
{
|
||||
if ((access & FileAccess.ReadWrite) != access)
|
||||
throw new ArgumentException("Invalid file access specified");
|
||||
|
||||
var realShare = share ?? (access == FileAccess.Read ? FileShare.Read : FileShare.None);
|
||||
if ((realShare & (FileShare.ReadWrite | FileShare.Delete)) != realShare)
|
||||
throw new ArgumentException("Invalid file share specified");
|
||||
|
||||
string? name;
|
||||
if (_clyde.FileDialogImpl is { } clydeImpl)
|
||||
name = await clydeImpl.OpenFile(filters);
|
||||
else
|
||||
name = await GetOpenFileName(filters);
|
||||
return await clydeImpl.OpenFile(filters);
|
||||
|
||||
var name = await GetOpenFileName(filters);
|
||||
if (name == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return File.Open(name, FileMode.Open, access, realShare);
|
||||
return File.Open(name, FileMode.Open);
|
||||
}
|
||||
|
||||
private async Task<string?> GetOpenFileName(FileDialogFilters? filters)
|
||||
@@ -62,34 +54,24 @@ namespace Robust.Client.UserInterface
|
||||
return await OpenFileNfd(filters);
|
||||
}
|
||||
|
||||
public async Task<(Stream, bool)?> SaveFile(
|
||||
FileDialogFilters? filters,
|
||||
bool truncate = true,
|
||||
FileAccess access = FileAccess.ReadWrite,
|
||||
FileShare share = FileShare.None)
|
||||
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
|
||||
{
|
||||
if ((access & FileAccess.ReadWrite) != access)
|
||||
throw new ArgumentException("Invalid file access specified");
|
||||
|
||||
if ((share & (FileShare.ReadWrite | FileShare.Delete)) != share)
|
||||
throw new ArgumentException("Invalid file share specified");
|
||||
|
||||
string? name;
|
||||
if (_clyde.FileDialogImpl is { } clydeImpl)
|
||||
name = await clydeImpl.SaveFile(filters);
|
||||
else
|
||||
name = await GetSaveFileName(filters);
|
||||
return await clydeImpl.SaveFile(filters);
|
||||
|
||||
var name = await GetSaveFileName(filters);
|
||||
if (name == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open, access, share), true);
|
||||
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return (File.Open(name, FileMode.Create, access, share), false);
|
||||
return (File.Open(name, FileMode.Create), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
{
|
||||
@@ -20,17 +19,7 @@ namespace Robust.Client.UserInterface
|
||||
/// The file stream for the file the user opened.
|
||||
/// <see langword="null" /> if the user cancelled the action.
|
||||
/// </returns>
|
||||
/// <param name="filters">Filters for file types that the user can select.</param>
|
||||
/// <param name="access">What access is desired from the file operation.</param>
|
||||
/// <param name="share">
|
||||
/// What sharing mode is desired from the file operation.
|
||||
/// If null is provided and <paramref name="access"/> is <see cref="FileAccess.Read"/>,
|
||||
/// <see cref="FileShare.Read"/> is selected, otherwise <see cref="FileShare.None"/>.
|
||||
/// </param>
|
||||
Task<Stream?> OpenFile(
|
||||
FileDialogFilters? filters = null,
|
||||
FileAccess access = FileAccess.ReadWrite,
|
||||
FileShare? share = null);
|
||||
Task<Stream?> OpenFile(FileDialogFilters? filters = null);
|
||||
|
||||
/// <summary>
|
||||
/// Open a file dialog used for saving a single file.
|
||||
@@ -40,21 +29,6 @@ namespace Robust.Client.UserInterface
|
||||
/// 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>
|
||||
/// <param name="access">What access is desired from the file operation.</param>
|
||||
/// <param name="share">Sharing mode for the opened file.</param>
|
||||
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
|
||||
FileDialogFilters? filters = null,
|
||||
bool truncate = true,
|
||||
FileAccess access = FileAccess.ReadWrite,
|
||||
FileShare share = FileShare.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal implementation interface used to connect <see cref="FileDialogManager"/> and <see cref="IClydeInternal"/>.
|
||||
/// </summary>
|
||||
internal interface IFileDialogManagerImplementation
|
||||
{
|
||||
Task<string?> OpenFile(FileDialogFilters? filters);
|
||||
Task<string?> SaveFile(FileDialogFilters? filters);
|
||||
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Robust.Server.Physics.Commands;
|
||||
|
||||
public sealed class BoxStackCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "boxstack";
|
||||
public string Description => string.Empty;
|
||||
public string Help => "boxstack [mapid] [columns] [rows]";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 3 ||
|
||||
!int.TryParse(args[0], out var mapInt) ||
|
||||
!int.TryParse(args[1], out var columns) ||
|
||||
!int.TryParse(args[2], out var rows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
var fixtureSystem = _entManager.System<FixtureSystem>();
|
||||
var physSystem = _entManager.System<SharedPhysicsSystem>();
|
||||
physSystem.SetGravity(new Vector2(0f, -9.8f));
|
||||
|
||||
var groundUid = _entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = _entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
var groundManager = _entManager.EnsureComponent<FixturesComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtureSystem.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 1, 1, true), manager: groundManager, body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
|
||||
fixtureSystem.CreateFixture(groundUid, "fix2", new Fixture(vertical, 1, 1, true), manager: groundManager, body: ground);
|
||||
|
||||
physSystem.WakeBody(groundUid, manager: groundManager, body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
for (var j = 0; j < columns; j++)
|
||||
{
|
||||
for (var i = 0; i < rows; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = _entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
|
||||
var box = _entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
var manager = _entManager.EnsureComponent<FixturesComponent>(boxUid);
|
||||
|
||||
physSystem.SetBodyType(boxUid, BodyType.Dynamic, manager: manager, body: box);
|
||||
var poly = new PolygonShape(0.001f);
|
||||
poly.Set(new List<Vector2>()
|
||||
{
|
||||
new(0.5f, -0.5f),
|
||||
new(0.5f, 0.5f),
|
||||
new(-0.5f, 0.5f),
|
||||
new(-0.5f, -0.5f),
|
||||
});
|
||||
|
||||
fixtureSystem.CreateFixture(boxUid, "fix1", new Fixture(poly, 1, 1, true), manager: manager, body: box);
|
||||
physSystem.WakeBody(boxUid, manager: manager, body: box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ namespace Robust.Server.Placement
|
||||
{
|
||||
// Replace existing entities if relevant.
|
||||
if (msg.Replacement && _prototype.Index<EntityPrototype>(entityTemplateName).Components.TryGetValue(
|
||||
_factory.GetComponentName<PlacementReplacementComponent>(), out var compRegistry))
|
||||
_factory.GetComponentName(typeof(PlacementReplacementComponent)), out var compRegistry))
|
||||
{
|
||||
var key = ((PlacementReplacementComponent)compRegistry.Component).Key;
|
||||
var gridUid = _xformSystem.GetGrid(coordinates);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -102,7 +101,6 @@ namespace Robust.Shared.Maths
|
||||
/// <summary>
|
||||
/// Returns the smallest rectangle that contains both of the rectangles.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public readonly Box2i Union(in Box2i other)
|
||||
{
|
||||
var botLeft = Vector2i.ComponentMin(BottomLeft, other.BottomLeft);
|
||||
@@ -209,7 +207,6 @@ namespace Robust.Shared.Maths
|
||||
/// <summary>
|
||||
/// Multiplies each side of the box by the scalar.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public Box2i Scale(int scalar)
|
||||
{
|
||||
return new Box2i(
|
||||
@@ -218,21 +215,6 @@ namespace Robust.Shared.Maths
|
||||
Right * scalar,
|
||||
Top * scalar);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool Intersects(in Box2i other)
|
||||
{
|
||||
return other.Bottom <= this.Top && other.Top >= this.Bottom && other.Right >= this.Left &&
|
||||
other.Left <= this.Right;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public readonly Box2i Enlarged(int size)
|
||||
{
|
||||
return new(Left - size, Bottom - size, Right + size, Top + size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,8 +13,7 @@ namespace Robust.Shared.Audio;
|
||||
/// <seealso cref="ResolvedPathSpecifier"/>
|
||||
/// <seealso cref="ResolvedCollectionSpecifier"/>
|
||||
[Serializable, NetSerializable]
|
||||
public abstract partial class ResolvedSoundSpecifier
|
||||
{
|
||||
public abstract partial class ResolvedSoundSpecifier {
|
||||
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
|
||||
public static implicit operator ResolvedSoundSpecifier(string s) => new ResolvedPathSpecifier(s);
|
||||
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
|
||||
@@ -23,10 +22,8 @@ public abstract partial class ResolvedSoundSpecifier
|
||||
/// <summary>
|
||||
/// Returns whether <c>s</c> is null, or if it contains an empty path/collection ID.
|
||||
/// </summary>
|
||||
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s)
|
||||
{
|
||||
return s switch
|
||||
{
|
||||
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s) {
|
||||
return s switch {
|
||||
null => true,
|
||||
ResolvedPathSpecifier path => path.Path.ToString() == "",
|
||||
ResolvedCollectionSpecifier collection => string.IsNullOrEmpty(collection.Collection),
|
||||
@@ -40,8 +37,7 @@ public abstract partial class ResolvedSoundSpecifier
|
||||
/// </summary>
|
||||
/// <seealso cref="ResolvedCollectionSpecifier"/>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier, IEquatable<ResolvedPathSpecifier>
|
||||
{
|
||||
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
|
||||
/// <summary>
|
||||
/// The resource path of the sound.
|
||||
/// </summary>
|
||||
@@ -61,21 +57,6 @@ public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier, IEqu
|
||||
public ResolvedPathSpecifier(string path) : this(new ResPath(path))
|
||||
{
|
||||
}
|
||||
|
||||
public bool Equals(ResolvedPathSpecifier? other)
|
||||
{
|
||||
return Path.Equals(other?.Path);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as ResolvedPathSpecifier);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Path.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,9 +64,7 @@ public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier, IEqu
|
||||
/// </summary>
|
||||
/// <seealso cref="ResolvedPathSpecifier"/>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier, IEquatable<ResolvedCollectionSpecifier>
|
||||
{
|
||||
|
||||
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier {
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="SoundCollectionPrototype">sound collection</see> to look up.
|
||||
/// </summary>
|
||||
@@ -108,19 +87,4 @@ public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier
|
||||
Collection = collection;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public bool Equals(ResolvedCollectionSpecifier? other)
|
||||
{
|
||||
return Collection.Equals(other?.Collection) && Index.Equals(other?.Index);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as ResolvedCollectionSpecifier);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Collection, Index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,7 +665,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(ResolveSound(sound), playerFilter, coordinates, recordReplay, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayStatic(ResolveSound(sound), playerFilter, coordinates, recordReplay, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -137,10 +137,9 @@ public sealed class TeleportToCommand : LocalizedEntityCommands
|
||||
}
|
||||
}
|
||||
|
||||
var targetMapCoords = _transform.ToMapCoordinates(targetCoords);
|
||||
foreach (var victim in victims)
|
||||
{
|
||||
_transform.SetMapCoordinates(victim.Entity, targetMapCoords);
|
||||
_transform.SetCoordinates(victim.Entity, targetCoords);
|
||||
_transform.AttachToGridOrMap(victim.Entity, victim.Transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +279,16 @@ namespace Robust.Shared.Containers
|
||||
|
||||
#region Container Helpers
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public bool TryGetContainingContainer(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
MetaDataComponent? meta = null,
|
||||
TransformComponent? transform = null)
|
||||
{
|
||||
return TryGetContainingContainer((uid, transform, meta), out container);
|
||||
}
|
||||
|
||||
public bool TryGetContainingContainer(
|
||||
Entity<TransformComponent?, MetaDataComponent?> ent,
|
||||
[NotNullWhen(true)] out BaseContainer? container)
|
||||
@@ -596,7 +606,7 @@ namespace Robust.Shared.Containers
|
||||
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
|
||||
/// <param name="wasInContainer">Whether the entity was actually inside a container or not.</param>
|
||||
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
|
||||
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force, out bool wasInContainer)
|
||||
public bool TryRemoveFromContainer(EntityUid entity, bool force, out bool wasInContainer)
|
||||
{
|
||||
DebugTools.Assert(Exists(entity));
|
||||
|
||||
@@ -621,7 +631,7 @@ namespace Robust.Shared.Containers
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
|
||||
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
|
||||
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force = false)
|
||||
public bool TryRemoveFromContainer(EntityUid entity, bool force = false)
|
||||
{
|
||||
return TryRemoveFromContainer(entity, force, out _);
|
||||
}
|
||||
@@ -668,8 +678,9 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
// TODO make this check upwards for any container, and parent to that.
|
||||
// Currently this just checks the direct parent, so entities will still teleport through containers.
|
||||
|
||||
if (!transform.Comp.ParentUid.IsValid()
|
||||
|| !TryGetContainingContainer((transform.Comp.ParentUid, Transform(transform.Comp.ParentUid)), out var container)
|
||||
|| !TryGetContainingContainer(transform.Comp.ParentUid, out var container)
|
||||
|| !TryInsertIntoContainer(transform, container))
|
||||
{
|
||||
_transform.AttachToGridOrMap(transform, transform.Comp);
|
||||
@@ -681,9 +692,8 @@ namespace Robust.Shared.Containers
|
||||
if (Insert((transform.Owner, transform.Comp, null, null), container))
|
||||
return true;
|
||||
|
||||
var ownerXform = Transform(container.Owner);
|
||||
if (ownerXform.ParentUid.IsValid()
|
||||
&& TryGetContainingContainer((container.Owner, ownerXform), out var newContainer))
|
||||
if (Transform(container.Owner).ParentUid.IsValid()
|
||||
&& TryGetContainingContainer(container.Owner, out var newContainer))
|
||||
return TryInsertIntoContainer(transform, newContainer);
|
||||
|
||||
return false;
|
||||
|
||||
@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
|
||||
// Sanitise platform-specific path and standardize it for engine use.
|
||||
.Replace(Path.DirectorySeparatorChar, '/');
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData)
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -379,6 +379,10 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
var rootDir = loader.GetPath(new ResPath(@"/"));
|
||||
|
||||
// TODO: GET RID OF THIS.
|
||||
// This code shouldn't be passing OS disk paths through ResPath.
|
||||
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
yield return new ResPath(rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
public string RootDir { get; }
|
||||
|
||||
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="WritableDirProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootDir">Root file system directory to allow writing.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo);
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return GetFullPath(RootDir, path);
|
||||
}
|
||||
|
||||
private static string GetFullPath(string root, ResPath path)
|
||||
{
|
||||
var relPath = path.ToRelativeSystemPath();
|
||||
if (relPath.Contains("\\..") || relPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, relPath));
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,20 +547,19 @@ public sealed class EntityDeserializer :
|
||||
_stopwatch.Restart();
|
||||
foreach (var (entity, data) in Entities)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
CurrentReadingEntity = data;
|
||||
LoadEntity(entity, _metaQuery.Comp(entity), data.Components, data.MissingComponents);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
ToDelete.Add(entity);
|
||||
_log.Error($"Encountered error while loading entity. Yaml uid: {data.YamlId}. Loaded loaded entity: {EntMan.ToPrettyString(entity)}. Error:\n{e}.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CurrentReadingEntity = null;
|
||||
|
||||
@@ -162,8 +162,8 @@ public sealed class EntitySerializer : ISerializationContext,
|
||||
_log = _logMan.GetSawmill("entity_serializer");
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
|
||||
_metaName = _factory.GetComponentName<MetaDataComponent>();
|
||||
_xformName = _factory.GetComponentName<TransformComponent>();
|
||||
_metaName = _factory.GetComponentName(typeof(MetaDataComponent));
|
||||
_xformName = _factory.GetComponentName(typeof(TransformComponent));
|
||||
_emptyMetaNode = _serialization.WriteValueAs<MappingDataNode>(typeof(MetaDataComponent), new MetaDataComponent(), alwaysWrite: true, context: this);
|
||||
|
||||
CurrentComponent = _xformName;
|
||||
|
||||
@@ -633,7 +633,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// An invalid entity UID indicates that this entity has intentionally been removed from broadphases and should
|
||||
/// not automatically be re-added by movement events.
|
||||
/// </remarks>
|
||||
internal record struct BroadphaseData(EntityUid Uid, bool CanCollide, bool Static)
|
||||
internal record struct BroadphaseData(EntityUid Uid, EntityUid PhysicsMap, bool CanCollide, bool Static)
|
||||
{
|
||||
public bool IsValid() => Uid.IsValid();
|
||||
public bool Valid => IsValid();
|
||||
|
||||
@@ -4,7 +4,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -85,17 +84,12 @@ public partial class EntityManager
|
||||
return ents;
|
||||
}
|
||||
|
||||
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
|
||||
public void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
|
||||
{
|
||||
var ents = new ValueList<EntityUid>();
|
||||
|
||||
foreach (var protoName in protoNames)
|
||||
{
|
||||
var uid = SpawnAttachedTo(protoName, coordinates);
|
||||
ents.Add(uid);
|
||||
SpawnAttachedTo(protoName, coordinates);
|
||||
}
|
||||
|
||||
return ents.ToArray();
|
||||
}
|
||||
|
||||
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Returns true if the entity's data (apart from transform) is default.
|
||||
/// </summary>
|
||||
public bool IsDefault(EntityUid uid, ICollection<string>? ignoredComps = null)
|
||||
public bool IsDefault(EntityUid uid)
|
||||
{
|
||||
if (!MetaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null)
|
||||
return false;
|
||||
@@ -195,9 +195,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var compName = _componentFactory.GetComponentName(compType);
|
||||
|
||||
if (ignoredComps?.Contains(compName) == true)
|
||||
continue;
|
||||
|
||||
// If the component isn't on the prototype then it's custom.
|
||||
if (!protoData.TryGetValue(compName, out var protoMapping))
|
||||
return false;
|
||||
@@ -501,7 +498,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (Deleted(uid.Value))
|
||||
return false;
|
||||
|
||||
if (QueuedDeletionsSet.Contains(uid.Value))
|
||||
if (!QueuedDeletionsSet.Add(uid.Value))
|
||||
return false;
|
||||
|
||||
QueueDeleteEntity(uid);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -23,7 +22,7 @@ public partial interface IEntityManager
|
||||
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
|
||||
EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count);
|
||||
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
|
||||
void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);
|
||||
|
||||
@@ -89,6 +89,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<PhysicsMapComponent> _physMapQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
/// <summary>
|
||||
@@ -118,6 +119,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_physMapQuery = GetEntityQuery<PhysicsMapComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<BroadphaseComponent, EntityTerminatingEvent>(OnBroadphaseTerminating);
|
||||
@@ -151,12 +153,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
private void OnBroadphaseTerminating(EntityUid uid, BroadphaseComponent component, ref EntityTerminatingEvent args)
|
||||
{
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
RemoveChildrenFromTerminatingBroadphase(xform, component);
|
||||
var map = xform.MapUid;
|
||||
_physMapQuery.TryGetComponent(map, out var physMap);
|
||||
RemoveChildrenFromTerminatingBroadphase(xform, component, physMap);
|
||||
RemComp(uid, component);
|
||||
}
|
||||
|
||||
private void RemoveChildrenFromTerminatingBroadphase(TransformComponent xform,
|
||||
BroadphaseComponent component)
|
||||
BroadphaseComponent component,
|
||||
PhysicsMapComponent? map)
|
||||
{
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
@@ -174,15 +179,19 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
|
||||
if (childXform.Broadphase.Value.CanCollide && _fixturesQuery.TryGetComponent(child, out var fixtures))
|
||||
{
|
||||
if (map == null)
|
||||
_physMapQuery.TryGetComponent(childXform.Broadphase.Value.PhysicsMap, out map);
|
||||
|
||||
DebugTools.Assert(map == null || childXform.Broadphase.Value.PhysicsMap == map.Owner);
|
||||
var tree = childXform.Broadphase.Value.Static ? component.StaticTree : component.DynamicTree;
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
{
|
||||
DestroyProxies(fixture, tree);
|
||||
DestroyProxies(fixture, tree, map);
|
||||
}
|
||||
}
|
||||
|
||||
childXform.Broadphase = null;
|
||||
RemoveChildrenFromTerminatingBroadphase(childXform, component);
|
||||
RemoveChildrenFromTerminatingBroadphase(childXform, component, map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,18 +227,26 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
if (xform.MapUid == null)
|
||||
return;
|
||||
|
||||
if (!_physMapQuery.TryGetComponent(xform.MapUid, out var physMap))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
|
||||
}
|
||||
|
||||
var ent = new Entity<TransformComponent, BroadphaseComponent>(broadphase, xform, broadphase);
|
||||
var map = new Entity<PhysicsMapComponent>(xform.MapUid.Value, physMap);
|
||||
var enumerator = xform.ChildEnumerator;
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
if (!_broadQuery.HasComp(child))
|
||||
InitializeChild(child, ent);
|
||||
InitializeChild(child, ent, map);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeChild(
|
||||
EntityUid child,
|
||||
Entity<TransformComponent, BroadphaseComponent> broadphase)
|
||||
Entity<TransformComponent, BroadphaseComponent> broadphase,
|
||||
Entity<PhysicsMapComponent> map)
|
||||
{
|
||||
if (LifeStage(child) <= EntityLifeStage.PreInit)
|
||||
return;
|
||||
@@ -241,6 +258,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
if (!xform.Broadphase.Value.IsValid())
|
||||
return; // Entity is intentionally not on a broadphase (deferred updating?).
|
||||
|
||||
_physMapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out var oldPhysMap);
|
||||
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out var oldBroadphase))
|
||||
{
|
||||
DebugTools.Assert("Encountered deleted broadphase.");
|
||||
@@ -257,15 +275,16 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
}
|
||||
else if (oldBroadphase != broadphase.Comp2)
|
||||
{
|
||||
RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase,child, xform);
|
||||
RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase, ref oldPhysMap, child, xform);
|
||||
}
|
||||
}
|
||||
|
||||
DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && !x.CanCollide);
|
||||
DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && (!x.CanCollide || x.PhysicsMap == map.Owner));
|
||||
AddOrUpdateEntityTree(
|
||||
broadphase.Owner,
|
||||
broadphase.Comp2,
|
||||
broadphase.Comp1,
|
||||
map.Comp,
|
||||
child,
|
||||
xform);
|
||||
}
|
||||
@@ -296,6 +315,9 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
if (!TryGetCurrentBroadphase(xform, out var broadphase))
|
||||
return;
|
||||
|
||||
if (!_physMapQuery.TryGetComponent(xform.MapUid, out var physMap))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
|
||||
var mapTransform = new Transform(worldPos, worldRot);
|
||||
|
||||
@@ -304,10 +326,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
|
||||
DebugTools.Assert(fixture.ProxyCount == 0);
|
||||
|
||||
AddOrMoveProxies((uid, body, xform), fixtureId, fixture, tree, broadphaseTransform);
|
||||
AddOrMoveProxies(uid, fixtureId, fixture, body, tree, broadphaseTransform, mapTransform, physMap.MoveBuffer);
|
||||
}
|
||||
|
||||
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase)
|
||||
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase, PhysicsMapComponent? physicsMap)
|
||||
{
|
||||
DebugTools.AssertNotNull(xform.Broadphase);
|
||||
DebugTools.Assert(xform.Broadphase!.Value.Uid == broadphase.Owner);
|
||||
@@ -322,7 +344,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
}
|
||||
|
||||
var tree = xform.Broadphase.Value.Static ? broadphase.StaticTree : broadphase.DynamicTree;
|
||||
DestroyProxies(fixture, tree);
|
||||
DestroyProxies(fixture, tree, physicsMap);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -373,7 +395,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
var fixtures = Comp<FixturesComponent>(uid);
|
||||
if (old.CanCollide)
|
||||
{
|
||||
RemoveBroadTree(broadphase, fixtures, old.Static);
|
||||
_physMapQuery.TryGetComponent(old.PhysicsMap, out var physicsMap);
|
||||
RemoveBroadTree(broadphase, fixtures, old.Static, physicsMap);
|
||||
}
|
||||
else
|
||||
(old.Static ? broadphase.StaticSundriesTree : broadphase.SundriesTree).Remove(uid);
|
||||
@@ -385,23 +408,23 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
AddOrUpdateSundriesTree(old.Uid, broadphase, uid, xform, body.BodyType == BodyType.Static);
|
||||
}
|
||||
|
||||
private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody)
|
||||
private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody, PhysicsMapComponent? map)
|
||||
{
|
||||
var tree = staticBody ? lookup.StaticTree : lookup.DynamicTree;
|
||||
foreach (var fixture in manager.Fixtures.Values)
|
||||
{
|
||||
DestroyProxies(fixture, tree);
|
||||
DestroyProxies(fixture, tree, map);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DestroyProxies(Fixture fixture, IBroadPhase tree)
|
||||
internal void DestroyProxies(Fixture fixture, IBroadPhase tree, PhysicsMapComponent? map)
|
||||
{
|
||||
var buffer = _physics.MoveBuffer;
|
||||
var buffer = map?.MoveBuffer;
|
||||
for (var i = 0; i < fixture.ProxyCount; i++)
|
||||
{
|
||||
var proxy = fixture.Proxies[i];
|
||||
tree.RemoveProxy(proxy.ProxyId);
|
||||
buffer.Remove(proxy);
|
||||
buffer?.Remove(proxy);
|
||||
}
|
||||
|
||||
fixture.ProxyCount = 0;
|
||||
@@ -415,7 +438,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
if (broadphaseXform.MapID == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, xform, body, fixtures);
|
||||
if (!_physMapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
|
||||
throw new InvalidOperationException($"Physics Broadphase is missing physics map. {ToPrettyString(broadUid)}");
|
||||
|
||||
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, physMap, xform, body, fixtures);
|
||||
}
|
||||
|
||||
private void AddOrUpdatePhysicsTree(
|
||||
@@ -423,15 +449,16 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
EntityUid broadUid,
|
||||
BroadphaseComponent broadphase,
|
||||
TransformComponent broadphaseXform,
|
||||
PhysicsMapComponent physicsMap,
|
||||
TransformComponent xform,
|
||||
PhysicsComponent body,
|
||||
FixturesComponent manager)
|
||||
{
|
||||
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform));
|
||||
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, body.CanCollide, body.BodyType == BodyType.Static));
|
||||
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static));
|
||||
DebugTools.Assert(broadphase.Owner == broadUid);
|
||||
|
||||
xform.Broadphase ??= new(broadUid, body.CanCollide, body.BodyType == BodyType.Static);
|
||||
xform.Broadphase ??= new(broadUid, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static);
|
||||
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
|
||||
|
||||
// TOOD optimize this. This function iterates UP through parents, while we are currently iterating down.
|
||||
@@ -443,19 +470,20 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
|
||||
foreach (var (id, fixture) in manager.Fixtures)
|
||||
{
|
||||
AddOrMoveProxies((uid, body, xform), id, fixture, tree, broadphaseTransform);
|
||||
AddOrMoveProxies(uid, id, fixture, body, tree, broadphaseTransform, mapTransform, physicsMap.MoveBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddOrMoveProxies(
|
||||
Entity<PhysicsComponent, TransformComponent> ent,
|
||||
EntityUid uid,
|
||||
string fixtureId,
|
||||
Fixture fixture,
|
||||
PhysicsComponent body,
|
||||
IBroadPhase tree,
|
||||
Transform broadphaseTransform)
|
||||
Transform broadphaseTransform,
|
||||
Transform mapTransform,
|
||||
Dictionary<FixtureProxy, Box2> moveBuffer)
|
||||
{
|
||||
var moveBuffer = _physics.MoveBuffer;
|
||||
|
||||
// Moving
|
||||
if (fixture.ProxyCount > 0)
|
||||
{
|
||||
@@ -465,7 +493,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
var proxy = fixture.Proxies[i];
|
||||
tree.MoveProxy(proxy.ProxyId, bounds);
|
||||
proxy.AABB = bounds;
|
||||
moveBuffer.Add(proxy);
|
||||
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -477,11 +505,11 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i);
|
||||
var proxy = new FixtureProxy(ent.Owner, ent.Comp1, ent.Comp2, bounds, fixtureId, fixture, i);
|
||||
var proxy = new FixtureProxy(uid, body, bounds, fixtureId, fixture, i);
|
||||
proxy.ProxyId = tree.AddProxy(ref proxy);
|
||||
proxy.AABB = bounds;
|
||||
proxies[i] = proxy;
|
||||
moveBuffer.Add(proxy);
|
||||
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
|
||||
}
|
||||
|
||||
fixture.Proxies = proxies;
|
||||
@@ -491,8 +519,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
private void AddOrUpdateSundriesTree(EntityUid broadUid, BroadphaseComponent broadphase, EntityUid uid, TransformComponent xform, bool staticBody, Box2? aabb = null)
|
||||
{
|
||||
DebugTools.Assert(!_container.IsEntityOrParentInContainer(uid));
|
||||
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, false, staticBody));
|
||||
xform.Broadphase ??= new(broadUid, false, staticBody);
|
||||
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, default, false, staticBody));
|
||||
xform.Broadphase ??= new(broadUid, default, false, staticBody);
|
||||
(staticBody ? broadphase.StaticSundriesTree : broadphase.SundriesTree).AddOrUpdate(uid, aabb);
|
||||
}
|
||||
|
||||
@@ -510,12 +538,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
{
|
||||
if (args.Component.GridUid == args.Sender)
|
||||
{
|
||||
// If grid changes map MoveBuffer will have incorrect worldpositions for all children.
|
||||
if (args.ParentChanged)
|
||||
{
|
||||
if (args.ParentChanged) // grid changed maps, need to update children and clear the move buffer.
|
||||
OnGridChangedMap(args);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
DebugTools.Assert(!_gridQuery.HasComp(args.Sender));
|
||||
@@ -539,21 +563,84 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// We need to recursively update the cached data and remove children from the move buffer
|
||||
DebugTools.Assert(HasComp<MapGridComponent>(args.Sender));
|
||||
DebugTools.Assert(!newMap.IsValid() || HasComp<MapComponent>(newMap));
|
||||
DebugTools.Assert(!oldMap.IsValid() || HasComp<MapComponent>(oldMap));
|
||||
DebugTools.Assert(_gridQuery.HasComp(args.Sender));
|
||||
DebugTools.Assert(!newMap.IsValid() || _mapQuery.HasComp(newMap));
|
||||
DebugTools.Assert(!oldMap.IsValid() || _mapQuery.HasComp(oldMap));
|
||||
|
||||
var oldBuffer = _physMapQuery.CompOrNull(oldMap)?.MoveBuffer;
|
||||
var newBuffer = _physMapQuery.CompOrNull(newMap)?.MoveBuffer;
|
||||
|
||||
foreach (var child in args.Component._children)
|
||||
{
|
||||
RecursiveOnGridChangedMap(child, oldMap, newMap, oldBuffer, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecursiveOnGridChangedMap(
|
||||
EntityUid uid,
|
||||
EntityUid oldMap,
|
||||
EntityUid newMap,
|
||||
Dictionary<FixtureProxy, Box2>? oldBuffer,
|
||||
Dictionary<FixtureProxy, Box2>? newBuffer)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(uid, out var xform))
|
||||
return;
|
||||
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
RecursiveOnGridChangedMap(child, oldMap, newMap, oldBuffer, newBuffer);
|
||||
}
|
||||
|
||||
if (xform.Broadphase == null || !xform.Broadphase.Value.CanCollide)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(_netMan.IsClient || !xform.Broadphase.Value.PhysicsMap.IsValid() || xform.Broadphase.Value.PhysicsMap == oldMap);
|
||||
xform.Broadphase = xform.Broadphase.Value with { PhysicsMap = newMap };
|
||||
|
||||
if (!_fixturesQuery.TryGetComponent(uid, out var fixtures))
|
||||
return;
|
||||
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
foreach (var fix in fixtures.Fixtures.Values)
|
||||
foreach (var prox in fix.Proxies)
|
||||
{
|
||||
oldBuffer.Remove(prox);
|
||||
}
|
||||
}
|
||||
|
||||
if (newBuffer == null)
|
||||
return;
|
||||
|
||||
// TODO PERFORMANCE
|
||||
// track world position while recursively iterating down through children.
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
|
||||
var mapTransform = new Transform(worldPos, worldRot);
|
||||
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
{
|
||||
for (var i = 0; i < fixture.ProxyCount; i++)
|
||||
{
|
||||
var proxy = fixture.Proxies[i];
|
||||
newBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateParent(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
BroadphaseComponent? oldBroadphase = null;
|
||||
PhysicsMapComponent? oldPhysMap = null;
|
||||
if (xform.Broadphase != null)
|
||||
{
|
||||
if (!xform.Broadphase.Value.IsValid())
|
||||
return; // Entity is intentionally not on a broadphase (deferred updating?).
|
||||
|
||||
_physMapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out oldPhysMap);
|
||||
|
||||
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out oldBroadphase))
|
||||
{
|
||||
|
||||
DebugTools.Assert("Encountered deleted broadphase.");
|
||||
|
||||
// broadphase was probably deleted.
|
||||
@@ -574,18 +661,24 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
|
||||
if (oldBroadphase != null && oldBroadphase != newBroadphase)
|
||||
{
|
||||
RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, uid, xform);
|
||||
RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, ref oldPhysMap, uid, xform);
|
||||
}
|
||||
|
||||
if (newBroadphase == null)
|
||||
return;
|
||||
|
||||
var newBroadphaseXform = _xformQuery.GetComponent(newBroadphase.Owner);
|
||||
if (!_physMapQuery.TryGetComponent(newBroadphaseXform.MapUid, out var physMap))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(newBroadphase.Owner)}");
|
||||
}
|
||||
|
||||
AddOrUpdateEntityTree(
|
||||
newBroadphase.Owner,
|
||||
newBroadphase,
|
||||
newBroadphaseXform,
|
||||
physMap,
|
||||
uid,
|
||||
xform);
|
||||
}
|
||||
@@ -620,11 +713,17 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
bool recursive = true)
|
||||
{
|
||||
var broadphaseXform = _xformQuery.GetComponent(broadphase.Owner);
|
||||
if (!_physMapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
|
||||
}
|
||||
|
||||
AddOrUpdateEntityTree(
|
||||
broadUid,
|
||||
broadphase,
|
||||
broadphaseXform,
|
||||
physMap,
|
||||
uid,
|
||||
xform,
|
||||
recursive);
|
||||
@@ -634,6 +733,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
EntityUid broadUid,
|
||||
BroadphaseComponent broadphase,
|
||||
TransformComponent broadphaseXform,
|
||||
PhysicsMapComponent physicsMap,
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
bool recursive = true)
|
||||
@@ -658,7 +758,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, xform, body, _fixturesQuery.GetComponent(uid));
|
||||
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, physicsMap, xform, body, _fixturesQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
if (xform.ChildCount == 0 || !recursive)
|
||||
@@ -671,7 +771,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
var childXform = _xformQuery.GetComponent(child);
|
||||
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, child, childXform, recursive);
|
||||
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child, childXform, recursive);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -682,7 +782,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
var childXform = _xformQuery.GetComponent(child);
|
||||
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, child, childXform, recursive);
|
||||
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child, childXform, recursive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,10 +794,16 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
if (!TryGetCurrentBroadphase(xform, out var broadphase))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
|
||||
DebugTools.Assert(!HasComp<MapComponent>(uid));
|
||||
DebugTools.Assert(!_gridQuery.HasComp(uid));
|
||||
DebugTools.Assert(!_mapQuery.HasComp(uid));
|
||||
PhysicsMapComponent? physMap = null;
|
||||
if (xform.Broadphase!.Value.PhysicsMap is { Valid: true } map && !_physMapQuery.TryGetComponent(map, out physMap))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
|
||||
}
|
||||
|
||||
RemoveFromEntityTree(broadphase.Owner, broadphase, uid, xform);
|
||||
RemoveFromEntityTree(broadphase.Owner, broadphase, ref physMap, uid, xform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -706,6 +812,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
private void RemoveFromEntityTree(
|
||||
EntityUid broadUid,
|
||||
BroadphaseComponent broadphase,
|
||||
ref PhysicsMapComponent? physicsMap,
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
bool recursive = true)
|
||||
@@ -726,9 +833,16 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
broadUid = old.Uid;
|
||||
}
|
||||
|
||||
if (old.PhysicsMap.IsValid() && physicsMap?.Owner != old.PhysicsMap)
|
||||
{
|
||||
if (!_physMapQuery.TryGetComponent(old.PhysicsMap, out physicsMap))
|
||||
Log.Error($"Entity {ToPrettyString(uid)} has missing physics map?");
|
||||
}
|
||||
|
||||
if (old.CanCollide)
|
||||
{
|
||||
RemoveBroadTree(broadphase, _fixturesQuery.GetComponent(uid), old.Static);
|
||||
DebugTools.Assert(old.PhysicsMap == (physicsMap?.Owner ?? default));
|
||||
RemoveBroadTree(broadphase, _fixturesQuery.GetComponent(uid), old.Static, physicsMap);
|
||||
}
|
||||
else if (old.Static)
|
||||
broadphase.StaticSundriesTree.Remove(uid);
|
||||
@@ -744,6 +858,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
RemoveFromEntityTree(
|
||||
broadUid,
|
||||
broadphase,
|
||||
ref physicsMap,
|
||||
child,
|
||||
_xformQuery.GetComponent(child));
|
||||
}
|
||||
|
||||
@@ -166,7 +166,10 @@ public abstract partial class SharedMapSystem
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
||||
}
|
||||
|
||||
_physics.MovedGrids.Add(uid);
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
{
|
||||
movedGrids.MovedGrids.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridMove(EntityUid uid, MapGridComponent component, ref MoveEvent args)
|
||||
@@ -188,7 +191,10 @@ public abstract partial class SharedMapSystem
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
||||
}
|
||||
|
||||
_physics.MovedGrids.Add(uid);
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
{
|
||||
movedGrids.MovedGrids.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnParentChange(EntityUid uid, MapGridComponent component, ref MoveEvent args)
|
||||
@@ -213,23 +219,23 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
|
||||
{
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} is parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} is not parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we cleanup old map for moved grid stuff.
|
||||
var oldMap = _transform.ToMapCoordinates(args.OldPosition);
|
||||
var oldMapUid = GetMapOrInvalid(oldMap.MapId);
|
||||
if (component.MapProxy != DynamicTree.Proxy.Free)
|
||||
if (component.MapProxy != DynamicTree.Proxy.Free && TryComp<MovedGridsComponent>(oldMapUid, out var oldMovedGrids))
|
||||
{
|
||||
_physics.MovedGrids.Remove(uid);
|
||||
oldMovedGrids.MovedGrids.Remove(uid);
|
||||
RemoveGrid(uid, component, oldMapUid);
|
||||
}
|
||||
|
||||
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
|
||||
if (xform.MapUid != null)
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var newMovedGrids))
|
||||
{
|
||||
_physics.MovedGrids.Add(uid);
|
||||
newMovedGrids.MovedGrids.Add(uid);
|
||||
AddGrid(uid, component);
|
||||
}
|
||||
}
|
||||
@@ -338,8 +344,6 @@ public abstract partial class SharedMapSystem
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
|
||||
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
|
||||
var changedEntry = new ValueList<TileChangedEntry>();
|
||||
|
||||
for (ushort x = 0; x < component.ChunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < component.ChunkSize; y++)
|
||||
@@ -348,15 +352,12 @@ public abstract partial class SharedMapSystem
|
||||
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out _))
|
||||
continue;
|
||||
|
||||
|
||||
var chunkIndex = new Vector2i(x, y);
|
||||
var gridIndices = chunk.ChunkTileToGridTile(chunkIndex);
|
||||
changedEntry.Add(new TileChangedEntry(tile, oldTile, chunk.Indices, gridIndices));
|
||||
var gridIndices = chunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, tile);
|
||||
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
var ev = new TileChangedEvent(gridEnt, changedEntry.ToArray());
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Owner, ref ev, true);
|
||||
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
|
||||
|
||||
// These should never refer to the same object
|
||||
@@ -518,9 +519,9 @@ public abstract partial class SharedMapSystem
|
||||
component.MapProxy = proxy;
|
||||
}
|
||||
|
||||
if (xform.MapUid != null)
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
{
|
||||
_physics.MovedGrids.Add(uid);
|
||||
movedGrids.MovedGrids.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,9 +573,9 @@ public abstract partial class SharedMapSystem
|
||||
grid.MapProxy = proxy;
|
||||
}
|
||||
|
||||
if (xform.MapUid != null)
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
{
|
||||
_physics.MovedGrids.Add(uid);
|
||||
movedGrids.MovedGrids.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,9 +588,9 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
grid.MapProxy = DynamicTree.Proxy.Free;
|
||||
|
||||
if (mapUid.IsValid())
|
||||
if (TryComp<MovedGridsComponent>(mapUid, out var movedGrids))
|
||||
{
|
||||
_physics.MovedGrids.Remove(uid);
|
||||
movedGrids.MovedGrids.Remove(uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,9 @@ public abstract partial class SharedMapSystem
|
||||
private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
// ordered startups when
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
EnsureComp<GridTreeComponent>(uid);
|
||||
EnsureComp<MovedGridsComponent>(uid);
|
||||
}
|
||||
|
||||
internal void AssignMapId(Entity<MapComponent> map, MapId? id = null)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
@@ -217,10 +217,9 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new Exception("Transform is initialising before map ids have been assigned?");
|
||||
#else
|
||||
#endif
|
||||
Log.Error($"Transform is initialising before map ids have been assigned?");
|
||||
_map.AssignMapId((uid, mapComp));
|
||||
#endif
|
||||
}
|
||||
|
||||
xform.MapUid = uid;
|
||||
|
||||
@@ -53,14 +53,8 @@ public abstract partial class SharedTransformSystem
|
||||
return MapCoordinates.Nullspace;
|
||||
}
|
||||
|
||||
Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition;
|
||||
|
||||
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
|
||||
{
|
||||
xform = XformQuery.GetComponent(xform.ParentUid);
|
||||
pos = xform._localRotation.RotateVec(pos) + xform._localPosition;
|
||||
}
|
||||
return new MapCoordinates(pos, xform.MapID);
|
||||
var worldPos = Vector2.Transform(coordinates.Position, GetWorldMatrix(xform));
|
||||
return new MapCoordinates(worldPos, xform.MapID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,41 +67,6 @@ public abstract partial class SharedTransformSystem
|
||||
return ToMapCoordinates(eCoords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts entity-local coordinates into map terms.
|
||||
/// The same as ToMapCoordinates(coordinates, logError).Position, but doesn't have to construct the MapCoordinates first.
|
||||
/// </summary>
|
||||
public Vector2 ToWorldPosition(EntityCoordinates coordinates, bool logError = true)
|
||||
{
|
||||
if (!TryComp(coordinates.EntityId, out TransformComponent? xform))
|
||||
{
|
||||
if (logError)
|
||||
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}. Trace: {Environment.StackTrace}");
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition;
|
||||
|
||||
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
|
||||
{
|
||||
xform = XformQuery.GetComponent(xform.ParentUid);
|
||||
pos = xform._localRotation.RotateVec(pos) + xform._localPosition;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts entity-local coordinates into map terms.
|
||||
/// The same as ToMapCoordinates(coordinates).Position, but doesn't have to construct the MapCoordinates first.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 ToWorldPosition(NetCoordinates coordinates)
|
||||
{
|
||||
var eCoords = GetCoordinates(coordinates);
|
||||
return ToWorldPosition(eCoords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -263,7 +263,7 @@ namespace Robust.Shared.GameObjects
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void RaiseMoveEvent(
|
||||
public void RaiseMoveEvent(
|
||||
Entity<TransformComponent, MetaDataComponent> ent,
|
||||
EntityUid oldParent,
|
||||
Vector2 oldPosition,
|
||||
|
||||
16
Robust.Shared/Map/Components/MovedGridsComponent.cs
Normal file
16
Robust.Shared/Map/Components/MovedGridsComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Map.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores what grids moved in a tick.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class MovedGridsComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> MovedGrids = new();
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map.Enumerators;
|
||||
|
||||
/// <summary>
|
||||
/// Iterates chunk indices but prefers ones closer towards the center first.
|
||||
/// </summary>
|
||||
public record struct NearestChunkEnumerator
|
||||
{
|
||||
private readonly Vector2i _chunkLB;
|
||||
private readonly Vector2i _chunkRT;
|
||||
|
||||
private ValueList<Vector2i> _chunks = new();
|
||||
|
||||
private int _n;
|
||||
|
||||
public NearestChunkEnumerator(Box2 localAABB, int chunkSize)
|
||||
{
|
||||
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
|
||||
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
|
||||
InitializeChunks(new Vector2i(chunkSize, chunkSize));
|
||||
}
|
||||
|
||||
public NearestChunkEnumerator(Box2 localAABB, Vector2i chunkSize)
|
||||
{
|
||||
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
|
||||
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
|
||||
InitializeChunks(chunkSize);
|
||||
}
|
||||
|
||||
private void InitializeChunks(Vector2i chunkSize)
|
||||
{
|
||||
var bl = (Vector2)_chunkLB * chunkSize;
|
||||
var tr = (Vector2)_chunkRT * chunkSize;
|
||||
var halfChunk = new Vector2(chunkSize.X / 2f, chunkSize.Y / 2f);
|
||||
|
||||
var center = (tr - bl) / 2 + bl;
|
||||
|
||||
for (var x = _chunkLB.X; x < _chunkRT.X; x++)
|
||||
{
|
||||
for (var y = _chunkLB.Y; y < _chunkRT.Y; y++)
|
||||
{
|
||||
_chunks.Add(new Vector2i(x, y) * chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
_chunks.Sort((c1, c2) => ((c1 + halfChunk) - center).LengthSquared().CompareTo(((c2 + halfChunk) - center).LengthSquared()));
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
|
||||
{
|
||||
if (_n >= _chunks.Count)
|
||||
{
|
||||
indices = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
indices = _chunks[_n++] ;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,11 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal name of the definition.
|
||||
/// </summary>
|
||||
string ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the sprite to draw.
|
||||
/// </summary>
|
||||
|
||||
@@ -37,7 +37,7 @@ using FNLfloat = System.Single;
|
||||
//using FNLfloat = System.Double;
|
||||
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public sealed partial class FastNoiseLite : ISerializationHooks
|
||||
public sealed partial class FastNoiseLite
|
||||
{
|
||||
private const short INLINE = 256; // MethodImplOptions.AggressiveInlining;
|
||||
private const short OPTIMISE = 512; // MethodImplOptions.AggressiveOptimization;
|
||||
@@ -136,6 +136,7 @@ public sealed partial class FastNoiseLite : ISerializationHooks
|
||||
[DataField("pingPongStrength")]
|
||||
private float mPingPongStrength = 2.0f;
|
||||
|
||||
[DataField("fractalBounding")]
|
||||
private float mFractalBounding = 1 / 1.75f;
|
||||
|
||||
[DataField("cellularDistanceFunction")]
|
||||
@@ -169,11 +170,6 @@ public sealed partial class FastNoiseLite : ISerializationHooks
|
||||
SetSeed(1337);
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
CalculateFractalBounding();
|
||||
}
|
||||
|
||||
public int GetSeed() => mSeed;
|
||||
|
||||
/// <summary>
|
||||
|
||||
18
Robust.Shared/Physics/Components/Gravity2DComponent.cs
Normal file
18
Robust.Shared/Physics/Components/Gravity2DComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class Gravity2DComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies side-view gravity to the map.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("gravity")]
|
||||
public Vector2 Gravity;
|
||||
}
|
||||
@@ -154,6 +154,11 @@ public sealed partial class PhysicsComponent : Component, IComponentDelta
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal float _inertia;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this body ignores gravity
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool IgnoreGravity;
|
||||
|
||||
/// <summary>
|
||||
/// Inverse moment of inertia (1 / I).
|
||||
/// </summary>
|
||||
@@ -259,9 +264,6 @@ public sealed partial class PhysicsComponent : Component, IComponentDelta
|
||||
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
|
||||
public BodyStatus BodyStatus { get; set; }
|
||||
|
||||
[DataField, Access(typeof(SharedPhysicsSystem))]
|
||||
public bool IgnoreGravity;
|
||||
|
||||
[ViewVariables, Access(typeof(SharedPhysicsSystem))]
|
||||
public bool Predict;
|
||||
}
|
||||
|
||||
106
Robust.Shared/Physics/Controllers/Gravity2DController.cs
Normal file
106
Robust.Shared/Physics/Controllers/Gravity2DController.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Controllers;
|
||||
|
||||
public sealed class Gravity2DController : VirtualController
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill = Logger.GetSawmill("physics");
|
||||
SubscribeLocalEvent<Gravity2DComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<Gravity2DComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, Gravity2DComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new Gravity2DComponentState() { Gravity = component.Gravity };
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, Gravity2DComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not Gravity2DComponentState state)
|
||||
return;
|
||||
|
||||
component.Gravity = state.Gravity;
|
||||
}
|
||||
|
||||
public Vector2 GetGravity(EntityUid uid, Gravity2DComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return Vector2.Zero;
|
||||
|
||||
return component.Gravity;
|
||||
}
|
||||
|
||||
public void SetGravity(EntityUid uid, Vector2 value)
|
||||
{
|
||||
if (!HasComp<MapComponent>(uid))
|
||||
{
|
||||
_sawmill.Error($"Tried to set 2D gravity for an entity that isn't a map?");
|
||||
DebugTools.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var gravity = EnsureComp<Gravity2DComponent>(uid);
|
||||
|
||||
if (gravity.Gravity.Equals(value))
|
||||
return;
|
||||
|
||||
gravity.Gravity = value;
|
||||
WakeBodiesRecursive(uid, GetEntityQuery<TransformComponent>(), GetEntityQuery<PhysicsComponent>());
|
||||
Dirty(uid, gravity);
|
||||
}
|
||||
|
||||
public void SetGravity(MapId mapId, Vector2 value)
|
||||
{
|
||||
var mapUid = _mapSystem.GetMap(mapId);
|
||||
var gravity = EnsureComp<Gravity2DComponent>(mapUid);
|
||||
|
||||
if (gravity.Gravity.Equals(value))
|
||||
return;
|
||||
|
||||
gravity.Gravity = value;
|
||||
WakeBodiesRecursive(mapUid, GetEntityQuery<TransformComponent>(), GetEntityQuery<PhysicsComponent>());
|
||||
Dirty(mapUid, gravity);
|
||||
}
|
||||
|
||||
private void WakeBodiesRecursive(EntityUid uid, EntityQuery<TransformComponent> xformQuery, EntityQuery<PhysicsComponent> bodyQuery)
|
||||
{
|
||||
if (bodyQuery.TryGetComponent(uid, out var body) &&
|
||||
body.BodyType == BodyType.Dynamic)
|
||||
{
|
||||
_physics.WakeBody(uid, body: body);
|
||||
}
|
||||
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
WakeBodiesRecursive(child, xformQuery, bodyQuery);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
private sealed class Gravity2DComponentState : ComponentState
|
||||
{
|
||||
public Vector2 Gravity;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,9 @@ namespace Robust.Shared.Physics.Controllers
|
||||
|
||||
SubscribeLocalEvent<PhysicsUpdateBeforeSolveEvent>(OnBeforeSolve, updatesBefore, updatesAfter);
|
||||
SubscribeLocalEvent<PhysicsUpdateAfterSolveEvent>(OnAfterSolve, updatesBefore, updatesAfter);
|
||||
|
||||
SubscribeLocalEvent<PhysicsUpdateBeforeMapSolveEvent>(OnBeforeMapSolve, updatesBefore, updatesAfter);
|
||||
SubscribeLocalEvent<PhysicsUpdateAfterMapSolveEvent>(OnAfterMapSolve, updatesBefore, updatesAfter);
|
||||
}
|
||||
|
||||
private void OnBeforeSolve(ref PhysicsUpdateBeforeSolveEvent ev)
|
||||
@@ -59,6 +62,16 @@ namespace Robust.Shared.Physics.Controllers
|
||||
AfterMonitor.Observe(Stopwatch.Elapsed.TotalSeconds);
|
||||
}
|
||||
|
||||
private void OnBeforeMapSolve(ref PhysicsUpdateBeforeMapSolveEvent ev)
|
||||
{
|
||||
UpdateBeforeMapSolve(ev.Prediction, ev.MapComponent, ev.DeltaTime);
|
||||
}
|
||||
|
||||
private void OnAfterMapSolve(ref PhysicsUpdateAfterMapSolveEvent ev)
|
||||
{
|
||||
UpdateAfterMapSolve(ev.Prediction, ev.MapComponent, ev.DeltaTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -74,5 +87,21 @@ namespace Robust.Shared.Physics.Controllers
|
||||
/// <param name="prediction"></param>
|
||||
/// <param name="frameTime"></param>
|
||||
public virtual void UpdateAfterSolve(bool prediction, float frameTime) {}
|
||||
|
||||
/// <summary>
|
||||
/// Run before a particular map starts.
|
||||
/// </summary>
|
||||
/// <param name="prediction"></param>
|
||||
/// <param name="mapComponent"></param>
|
||||
/// <param name="frameTime"></param>
|
||||
public virtual void UpdateBeforeMapSolve(bool prediction, PhysicsMapComponent mapComponent, float frameTime) {}
|
||||
|
||||
/// <summary>
|
||||
/// Run after a particular map finishes.
|
||||
/// </summary>
|
||||
/// <param name="prediction"></param>
|
||||
/// <param name="mapComponent"></param>
|
||||
/// <param name="frameTime"></param>
|
||||
public virtual void UpdateAfterMapSolve(bool prediction, PhysicsMapComponent mapComponent, float frameTime) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
public PhysicsComponent? BodyA;
|
||||
public PhysicsComponent? BodyB;
|
||||
|
||||
public TransformComponent? XformA;
|
||||
public TransformComponent? XformB;
|
||||
|
||||
public Manifold Manifold;
|
||||
|
||||
internal ContactType Type;
|
||||
@@ -256,11 +253,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
status = ContactStatus.EndTouching;
|
||||
}
|
||||
// Still touching
|
||||
else
|
||||
{
|
||||
status = ContactStatus.Touching;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -450,17 +442,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public TransformComponent OtherTransform(EntityUid uid)
|
||||
{
|
||||
if (uid == EntityA)
|
||||
return XformB!;
|
||||
else if (uid == EntityB)
|
||||
return XformA!;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -25,45 +25,43 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics;
|
||||
|
||||
public sealed class FixtureProxy
|
||||
namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
public EntityUid Entity;
|
||||
public PhysicsComponent Body { get; internal set; }
|
||||
|
||||
public TransformComponent Xform { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grid-based AABB of this proxy.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Box2 AABB;
|
||||
|
||||
[ViewVariables]
|
||||
public int ChildIndex;
|
||||
|
||||
public string FixtureId;
|
||||
|
||||
/// <summary>
|
||||
/// Our parent fixture
|
||||
/// </summary>
|
||||
public Fixture Fixture;
|
||||
|
||||
/// <summary>
|
||||
/// ID of this proxy in the broadphase dynamictree.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public DynamicTree.Proxy ProxyId = DynamicTree.Proxy.Free;
|
||||
|
||||
internal FixtureProxy(EntityUid uid, PhysicsComponent body, TransformComponent xform, Box2 aabb, string fixtureId, Fixture fixture, int childIndex)
|
||||
public sealed class FixtureProxy
|
||||
{
|
||||
Entity = uid;
|
||||
Body = body;
|
||||
Xform = xform;
|
||||
AABB = aabb;
|
||||
FixtureId = fixtureId;
|
||||
Fixture = fixture;
|
||||
ChildIndex = childIndex;
|
||||
public EntityUid Entity;
|
||||
public PhysicsComponent Body;
|
||||
|
||||
/// <summary>
|
||||
/// Grid-based AABB of this proxy.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Box2 AABB;
|
||||
|
||||
[ViewVariables]
|
||||
public int ChildIndex;
|
||||
|
||||
public string FixtureId;
|
||||
|
||||
/// <summary>
|
||||
/// Our parent fixture
|
||||
/// </summary>
|
||||
public Fixture Fixture;
|
||||
|
||||
/// <summary>
|
||||
/// ID of this proxy in the broadphase dynamictree.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public DynamicTree.Proxy ProxyId = DynamicTree.Proxy.Free;
|
||||
|
||||
public FixtureProxy(EntityUid uid, PhysicsComponent body, Box2 aabb, string fixtureId, Fixture fixture, int childIndex)
|
||||
{
|
||||
Entity = uid;
|
||||
Body = body;
|
||||
AABB = aabb;
|
||||
FixtureId = fixtureId;
|
||||
Fixture = fixture;
|
||||
ChildIndex = childIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
Robust.Shared/Physics/Dynamics/PhysicsMapComponent.cs
Normal file
90
Robust.Shared/Physics/Dynamics/PhysicsMapComponent.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Farseer Physics Engine:
|
||||
* Copyright (c) 2012 Ian Qvist
|
||||
*
|
||||
* Original source Box2D:
|
||||
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using PhysicsComponent = Robust.Shared.Physics.Components.PhysicsComponent;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class PhysicsMapComponent : Component
|
||||
{
|
||||
public bool AutoClearForces;
|
||||
|
||||
/// <summary>
|
||||
/// When substepping the client needs to know about the first position to use for lerping.
|
||||
/// </summary>
|
||||
public readonly Dictionary<EntityUid, (EntityUid ParentUid, Vector2 LocalPosition, Angle LocalRotation)>
|
||||
LerpData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Keep a buffer of everything that moved in a tick. This will be used to check for physics contacts.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<FixtureProxy, Box2> MoveBuffer = new();
|
||||
|
||||
/// <summary>
|
||||
/// All awake bodies on this map.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly HashSet<PhysicsComponent> AwakeBodies = new();
|
||||
|
||||
/// <summary>
|
||||
/// Store last tick's invDT
|
||||
/// </summary>
|
||||
internal float _invDt0;
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly struct PhysicsUpdateBeforeMapSolveEvent
|
||||
{
|
||||
public readonly bool Prediction;
|
||||
public readonly PhysicsMapComponent MapComponent;
|
||||
public readonly float DeltaTime;
|
||||
|
||||
public PhysicsUpdateBeforeMapSolveEvent(bool prediction, PhysicsMapComponent mapComponent, float deltaTime)
|
||||
{
|
||||
Prediction = prediction;
|
||||
MapComponent = mapComponent;
|
||||
DeltaTime = deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly struct PhysicsUpdateAfterMapSolveEvent
|
||||
{
|
||||
public readonly bool Prediction;
|
||||
public readonly PhysicsMapComponent MapComponent;
|
||||
public readonly float DeltaTime;
|
||||
|
||||
public PhysicsUpdateAfterMapSolveEvent(bool prediction, PhysicsMapComponent mapComponent, float deltaTime)
|
||||
{
|
||||
Prediction = prediction;
|
||||
MapComponent = mapComponent;
|
||||
DeltaTime = deltaTime;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
internal sealed class IslandSolveMessage : EntityEventArgs
|
||||
{
|
||||
public List<Entity<PhysicsComponent, TransformComponent>> Bodies { get; }
|
||||
public List<PhysicsComponent> Bodies { get; }
|
||||
|
||||
public IslandSolveMessage(List<Entity<PhysicsComponent, TransformComponent>> bodies)
|
||||
public IslandSolveMessage(List<PhysicsComponent> bodies)
|
||||
{
|
||||
Bodies = bodies;
|
||||
}
|
||||
|
||||
@@ -21,10 +21,13 @@ namespace Robust.Shared.Physics.Systems
|
||||
/// </summary>
|
||||
public sealed partial class FixtureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
#pragma warning disable CS0414
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
#pragma warning restore CS0414
|
||||
private EntityQuery<PhysicsMapComponent> _mapQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<FixturesComponent> _fixtureQuery;
|
||||
|
||||
@@ -35,6 +38,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
SubscribeLocalEvent<FixturesComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<FixturesComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<FixturesComponent, ComponentHandleState>(OnHandleState);
|
||||
_mapQuery = GetEntityQuery<PhysicsMapComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_fixtureQuery = GetEntityQuery<FixturesComponent>();
|
||||
}
|
||||
@@ -202,7 +206,8 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (_lookup.TryGetCurrentBroadphase(xform, out var broadphase))
|
||||
{
|
||||
DebugTools.Assert(xform.MapUid == Transform(broadphase.Owner).MapUid);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
|
||||
_mapQuery.TryGetComponent(xform.MapUid, out var physicsMap);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
}
|
||||
|
||||
if (updates)
|
||||
|
||||
@@ -34,12 +34,10 @@ namespace Robust.Shared.Physics.Systems
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<PhysicsMapComponent> _mapQuery;
|
||||
|
||||
private float _broadphaseExpand;
|
||||
|
||||
private readonly Dictionary<EntityUid, Matrix3x2> _broadMatrices = new();
|
||||
private HashSet<FixtureProxy> _gridMoveBuffer = new();
|
||||
|
||||
/*
|
||||
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
|
||||
* Our problem is that we have nested broadphases (rather than being on separate maps) which makes this
|
||||
@@ -58,8 +56,6 @@ namespace Robust.Shared.Physics.Systems
|
||||
_mapManager = _mapManager,
|
||||
System = this,
|
||||
BroadphaseExpand = _broadphaseExpand,
|
||||
// TODO: EntityManager one isn't ready yet?
|
||||
XformQuery = GetEntityQuery<TransformComponent>(),
|
||||
};
|
||||
|
||||
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
@@ -67,6 +63,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_mapQuery = GetEntityQuery<PhysicsMapComponent>();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
UpdatesAfter.Add(typeof(SharedTransformSystem));
|
||||
@@ -101,75 +98,87 @@ namespace Robust.Shared.Physics.Systems
|
||||
/// <summary>
|
||||
/// Check the AABB for each moved broadphase fixture and add any colliding entities to the movebuffer in case.
|
||||
/// </summary>
|
||||
private void FindGridContacts(HashSet<EntityUid> movedGrids)
|
||||
private void FindGridContacts(
|
||||
PhysicsMapComponent component,
|
||||
MapId mapId,
|
||||
HashSet<EntityUid> movedGrids,
|
||||
Dictionary<FixtureProxy, Box2> gridMoveBuffer)
|
||||
{
|
||||
// None moved this tick
|
||||
if (movedGrids.Count == 0) return;
|
||||
|
||||
var mapBroadphase = _broadphaseQuery.GetComponent(_map.GetMapOrInvalid(mapId));
|
||||
|
||||
// This is so that if we're on a broadphase that's moving (e.g. a grid) we need to make sure anything
|
||||
// we move over is getting checked for collisions, and putting it on the movebuffer is the easiest way to do so.
|
||||
var moveBuffer = _physicsSystem.MoveBuffer;
|
||||
var moveBuffer = component.MoveBuffer;
|
||||
|
||||
foreach (var gridUid in movedGrids)
|
||||
{
|
||||
var grid = _gridQuery.GetComponent(gridUid);
|
||||
var xform = _xformQuery.GetComponent(gridUid);
|
||||
|
||||
// Moved to nullspace?
|
||||
if (!_broadphaseQuery.TryComp(xform.MapUid, out var mapBroadphase))
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(xform.MapID == mapId);
|
||||
var worldAABB = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB);
|
||||
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
|
||||
var state = (moveBuffer, _gridMoveBuffer);
|
||||
var state = (moveBuffer, gridMoveBuffer);
|
||||
|
||||
QueryMapBroadphase(mapBroadphase.DynamicTree, ref state, enlargedAABB);
|
||||
QueryMapBroadphase(mapBroadphase.StaticTree, ref state, enlargedAABB);
|
||||
}
|
||||
|
||||
foreach (var proxy in _gridMoveBuffer)
|
||||
foreach (var (proxy, worldAABB) in gridMoveBuffer)
|
||||
{
|
||||
moveBuffer.Add(proxy);
|
||||
moveBuffer[proxy] = worldAABB;
|
||||
// If something is in our AABB then try grid traversal for it
|
||||
_traversal.CheckTraverse((proxy.Entity, _xformQuery.GetComponent(proxy.Entity)));
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryMapBroadphase(IBroadPhase broadPhase,
|
||||
ref (HashSet<FixtureProxy>, HashSet<FixtureProxy>) state,
|
||||
ref (Dictionary<FixtureProxy, Box2>, Dictionary<FixtureProxy, Box2>) state,
|
||||
Box2 enlargedAABB)
|
||||
{
|
||||
// Easier to just not go over each proxy as we already unioned the fixture's worldaabb.
|
||||
broadPhase.QueryAabb(ref state, static (ref (
|
||||
HashSet<FixtureProxy> moveBuffer,
|
||||
HashSet<FixtureProxy> gridMoveBuffer) tuple,
|
||||
Dictionary<FixtureProxy, Box2> moveBuffer,
|
||||
Dictionary<FixtureProxy, Box2> gridMoveBuffer) tuple,
|
||||
in FixtureProxy value) =>
|
||||
{
|
||||
// 99% of the time it's just going to be the broadphase (for now the grid) itself.
|
||||
// hence this body check makes this run significantly better.
|
||||
// Also check if it's not already on the movebuffer.
|
||||
if (tuple.moveBuffer.Contains(value))
|
||||
if (tuple.moveBuffer.ContainsKey(value))
|
||||
return true;
|
||||
|
||||
// To avoid updating during iteration.
|
||||
// Don't need to transform as it's already in map terms.
|
||||
tuple.gridMoveBuffer.Add(value);
|
||||
tuple.gridMoveBuffer[value] = value.AABB;
|
||||
return true;
|
||||
}, enlargedAABB, true);
|
||||
}
|
||||
|
||||
[Obsolete("Use the overload with SharedPhysicsMapComponent")]
|
||||
internal void FindNewContacts(MapId mapId)
|
||||
{
|
||||
if (!TryComp<PhysicsMapComponent>(_map.GetMapOrInvalid(mapId), out var physicsMap))
|
||||
return;
|
||||
|
||||
FindNewContacts(physicsMap, mapId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through every single created, moved, or touched proxy on the map and try to find any new contacts that should be created.
|
||||
/// </summary>
|
||||
internal void FindNewContacts()
|
||||
internal void FindNewContacts(PhysicsMapComponent component, MapId mapId)
|
||||
{
|
||||
var moveBuffer = _physicsSystem.MoveBuffer;
|
||||
var movedGrids = _physicsSystem.MovedGrids;
|
||||
|
||||
_gridMoveBuffer.Clear();
|
||||
var moveBuffer = component.MoveBuffer;
|
||||
var mapUid = _map.GetMapOrInvalid(mapId);
|
||||
var movedGrids = Comp<MovedGridsComponent>(mapUid).MovedGrids;
|
||||
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
|
||||
|
||||
// Find any entities being driven over that might need to be considered
|
||||
FindGridContacts(movedGrids);
|
||||
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer);
|
||||
|
||||
// There is some mariana trench levels of bullshit going on.
|
||||
// We essentially need to re-create Box2D's FindNewContacts but in a way that allows us to check every
|
||||
@@ -181,29 +190,18 @@ namespace Robust.Shared.Physics.Systems
|
||||
// to cache a bunch of stuff to make up for it.
|
||||
|
||||
// Handle grids first as they're not stored on map broadphase at all.
|
||||
HandleGridCollisions(movedGrids);
|
||||
HandleGridCollisions(mapId, movedGrids);
|
||||
|
||||
// EZ
|
||||
if (moveBuffer.Count == 0)
|
||||
return;
|
||||
|
||||
_contactJob.MapUid = _mapManager.GetMapEntityIdOrThrow(mapId);
|
||||
_contactJob.MoveBuffer.Clear();
|
||||
|
||||
foreach (var proxy in moveBuffer)
|
||||
foreach (var (proxy, aabb) in moveBuffer)
|
||||
{
|
||||
DebugTools.Assert(_xformQuery.GetComponent(proxy.Entity).Broadphase?.Uid != null);
|
||||
_contactJob.MoveBuffer.Add(proxy);
|
||||
}
|
||||
|
||||
_broadMatrices.Clear();
|
||||
var broadQuery = AllEntityQuery<BroadphaseComponent>();
|
||||
|
||||
// Cache broadphase matrices up front.
|
||||
// We'll defer the proxy world AABBs until we get contacts rather than doing it on every single move.
|
||||
// This is because contacts are run in parallel so we can spread the work a bit more and also don't duplicate it per tick.
|
||||
while (broadQuery.MoveNext(out var bUid, out _))
|
||||
{
|
||||
_broadMatrices[bUid] = _transform.GetWorldMatrix(bUid);
|
||||
_contactJob.MoveBuffer.Add((proxy, aabb));
|
||||
}
|
||||
|
||||
for (var i = _contactJob.ContactBuffer.Count; i < _contactJob.MoveBuffer.Count; i++)
|
||||
@@ -222,7 +220,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (proxies.Count == 0)
|
||||
continue;
|
||||
|
||||
var proxyA = _contactJob.MoveBuffer[i];
|
||||
var proxyA = _contactJob.MoveBuffer[i].Proxy;
|
||||
var proxyABody = proxyA.Body;
|
||||
|
||||
_fixturesQuery.TryGetComponent(proxyA.Entity, out var manager);
|
||||
@@ -236,7 +234,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
// This is because we generate a contact across 2 different broadphases where both bodies aren't
|
||||
// moving locally but are moving in world-terms.
|
||||
if (proxyA.Fixture.Hard && other.Fixture.Hard &&
|
||||
(_gridMoveBuffer.Contains(proxyA) || _gridMoveBuffer.Contains(other)))
|
||||
(gridMoveBuffer.ContainsKey(proxyA) || gridMoveBuffer.ContainsKey(other)))
|
||||
{
|
||||
_physicsSystem.WakeBody(proxyA.Entity, force: true, manager: manager, body: proxyABody);
|
||||
_physicsSystem.WakeBody(other.Entity, force: true, body: otherBody);
|
||||
@@ -250,15 +248,15 @@ namespace Robust.Shared.Physics.Systems
|
||||
movedGrids.Clear();
|
||||
}
|
||||
|
||||
private void HandleGridCollisions(HashSet<EntityUid> movedGrids)
|
||||
private void HandleGridCollisions(
|
||||
MapId mapId,
|
||||
HashSet<EntityUid> movedGrids)
|
||||
{
|
||||
foreach (var gridUid in movedGrids)
|
||||
{
|
||||
var grid = _gridQuery.GetComponent(gridUid);
|
||||
var xform = _xformQuery.GetComponent(gridUid);
|
||||
|
||||
if (xform.MapID == MapId.Nullspace)
|
||||
continue;
|
||||
DebugTools.Assert(xform.MapID == mapId);
|
||||
|
||||
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform);
|
||||
|
||||
@@ -271,7 +269,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
var transform = _physicsSystem.GetPhysicsTransform(gridUid);
|
||||
var state = (
|
||||
new Entity<FixturesComponent, MapGridComponent, PhysicsComponent, TransformComponent>(gridUid, fixture, grid, physics, xform),
|
||||
new Entity<FixturesComponent, MapGridComponent, PhysicsComponent>(gridUid, fixture, grid, physics),
|
||||
transform,
|
||||
worldMatrix,
|
||||
invWorldMatrix,
|
||||
@@ -282,9 +280,9 @@ namespace Robust.Shared.Physics.Systems
|
||||
_physicsQuery,
|
||||
_xformQuery);
|
||||
|
||||
_mapManager.FindGridsIntersecting(xform.MapID, aabb, ref state,
|
||||
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
|
||||
static (EntityUid uid, MapGridComponent component,
|
||||
ref (Entity<FixturesComponent, MapGridComponent, PhysicsComponent, TransformComponent> grid,
|
||||
ref (Entity<FixturesComponent, MapGridComponent, PhysicsComponent> grid,
|
||||
Transform transform,
|
||||
Matrix3x2 worldMatrix,
|
||||
Matrix3x2 invWorldMatrix,
|
||||
@@ -301,7 +299,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, otherGridMatrix, otherGridInvMatrix) = tuple.xformSystem.GetWorldPositionRotationMatrixWithInv(collidingXform);
|
||||
var (_, _, otherGridMatrix, otherGridInvMatrix) = tuple.xformSystem.GetWorldPositionRotationMatrixWithInv(collidingXform, tuple.xformQuery);
|
||||
var otherGridBounds = otherGridMatrix.TransformBox(component.LocalAABB);
|
||||
var otherTransform = tuple._physicsSystem.GetPhysicsTransform(uid);
|
||||
|
||||
@@ -342,10 +340,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
var otherAABB = otherFixture.Shape.ComputeAABB(otherTransform, j);
|
||||
|
||||
if (!fixAABB.Intersects(otherAABB)) continue;
|
||||
|
||||
tuple._physicsSystem.AddPair(
|
||||
(tuple.grid.Owner, tuple.grid.Comp3, tuple.grid.Comp4),
|
||||
(uid, physicsB, collidingXform),
|
||||
tuple._physicsSystem.AddPair(tuple.grid.Owner, uid,
|
||||
ourId, otherId,
|
||||
fixture, i,
|
||||
otherFixture, j,
|
||||
@@ -456,24 +451,29 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
_physicsSystem.SetAwake(entity!, true);
|
||||
|
||||
var matrix = _transform.GetWorldMatrix(broadphase);
|
||||
foreach (var fixture in entity.Comp2.Fixtures.Values)
|
||||
{
|
||||
TouchProxies(fixture);
|
||||
TouchProxies(entity.Comp3.MapUid.Value, matrix, fixture);
|
||||
}
|
||||
}
|
||||
|
||||
internal void TouchProxies(Fixture fixture)
|
||||
internal void TouchProxies(EntityUid mapId, Matrix3x2 broadphaseMatrix, Fixture fixture)
|
||||
{
|
||||
foreach (var proxy in fixture.Proxies)
|
||||
{
|
||||
AddToMoveBuffer(proxy);
|
||||
AddToMoveBuffer(mapId, proxy, broadphaseMatrix.TransformBox(proxy.AABB));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToMoveBuffer(FixtureProxy proxy)
|
||||
private void AddToMoveBuffer(EntityUid mapId, FixtureProxy proxy, Box2 aabb)
|
||||
{
|
||||
if (!_mapQuery.TryGetComponent(mapId, out var physicsMap))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(proxy.Body.CanCollide);
|
||||
_physicsSystem.MoveBuffer.Add(proxy);
|
||||
|
||||
physicsMap.MoveBuffer[proxy] = aabb;
|
||||
}
|
||||
|
||||
public void Refilter(EntityUid uid, Fixture fixture, TransformComponent? xform = null)
|
||||
@@ -492,7 +492,8 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (!_xformQuery.TryGetComponent(xform.Broadphase?.Uid, out var broadphase))
|
||||
return;
|
||||
|
||||
TouchProxies(fixture);
|
||||
var matrix = _transform.GetWorldMatrix(broadphase);
|
||||
TouchProxies(xform.MapUid.Value, matrix, fixture);
|
||||
}
|
||||
|
||||
internal void GetBroadphases(MapId mapId, Box2 aabb, BroadphaseCallback callback)
|
||||
@@ -559,35 +560,30 @@ namespace Robust.Shared.Physics.Systems
|
||||
private record struct BroadphaseContactJob() : IParallelRobustJob
|
||||
{
|
||||
public SharedBroadphaseSystem System = default!;
|
||||
public SharedTransformSystem TransformSys = default!;
|
||||
public IMapManager _mapManager = default!;
|
||||
|
||||
public float BroadphaseExpand;
|
||||
|
||||
public EntityQuery<TransformComponent> XformQuery;
|
||||
public EntityUid MapUid;
|
||||
|
||||
public List<List<FixtureProxy>> ContactBuffer = new();
|
||||
public List<FixtureProxy> MoveBuffer = new();
|
||||
public List<(FixtureProxy Proxy, Box2 WorldAABB)> MoveBuffer = new();
|
||||
|
||||
public int BatchSize => 8;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var proxy = MoveBuffer[index];
|
||||
var broadphaseUid = XformQuery.GetComponent(proxy.Entity).Broadphase?.Uid;
|
||||
var worldAABB = System._broadMatrices[broadphaseUid!.Value].TransformBox(proxy.AABB);
|
||||
var (proxy, worldAABB) = MoveBuffer[index];
|
||||
var buffer = ContactBuffer[index];
|
||||
buffer.Clear();
|
||||
|
||||
var mapUid = XformQuery.GetComponent(proxy.Entity).MapUid ?? EntityUid.Invalid;
|
||||
|
||||
var proxyBody = proxy.Body;
|
||||
DebugTools.Assert(!proxyBody.Deleted);
|
||||
|
||||
var state = (System, proxy, worldAABB, buffer);
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
_mapManager.FindGridsIntersecting(mapUid, worldAABB.Enlarged(BroadphaseExpand), ref state,
|
||||
_mapManager.FindGridsIntersecting(MapUid, worldAABB.Enlarged(BroadphaseExpand), ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref (
|
||||
SharedBroadphaseSystem system,
|
||||
FixtureProxy proxy,
|
||||
@@ -603,7 +599,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
// Struct ref moment, I have no idea what's fastest.
|
||||
buffer = state.buffer;
|
||||
System.FindPairs(proxy, worldAABB, mapUid, buffer);
|
||||
System.FindPairs(proxy, worldAABB, MapUid, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
@@ -312,6 +314,13 @@ public partial class SharedPhysicsSystem
|
||||
body._inertia += data.I;
|
||||
}
|
||||
|
||||
// Update this after re-calculating mass as content may want to use the sum of fixture masses instead.
|
||||
if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) != 0)
|
||||
{
|
||||
body._localCenter = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (body._mass > 0.0f)
|
||||
{
|
||||
body._invMass = 1.0f / body._mass;
|
||||
@@ -341,23 +350,18 @@ public partial class SharedPhysicsSystem
|
||||
var oldCenter = body._localCenter;
|
||||
body._localCenter = localCenter;
|
||||
|
||||
if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) == 0)
|
||||
{
|
||||
// Update center of mass velocity.
|
||||
var comVelocityDiff = Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter);
|
||||
// Update center of mass velocity.
|
||||
var comVelocityDiff = Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter);
|
||||
|
||||
if (comVelocityDiff != Vector2.Zero)
|
||||
{
|
||||
body.LinearVelocity += comVelocityDiff;
|
||||
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
|
||||
}
|
||||
}
|
||||
if (comVelocityDiff != Vector2.Zero)
|
||||
{
|
||||
body.LinearVelocity += comVelocityDiff;
|
||||
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
|
||||
}
|
||||
|
||||
if (body._mass == oldMass && body._inertia == oldInertia && oldCenter == localCenter)
|
||||
return;
|
||||
|
||||
// we always do the full mass and COM calculation and raise this, even for static bodies as content may need the info
|
||||
// examples are stations anchored with the station anchor, shuttles landed on planets, or grids getting an atmosphere above a certain mass threshold
|
||||
var ev = new MassDataChangedEvent((uid, body, manager), oldMass, oldInertia, oldCenter);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
@@ -484,17 +488,14 @@ public partial class SharedPhysicsSystem
|
||||
body.Awake = true;
|
||||
}
|
||||
|
||||
if (body.Awake)
|
||||
AddAwakeBody((uid, body, Transform(uid)));
|
||||
else
|
||||
RemoveSleepBody((uid, body, Transform(uid)));
|
||||
UpdateMapAwakeState(uid, body);
|
||||
}
|
||||
|
||||
public void TrySetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
|
||||
{
|
||||
if (_fixturesQuery.Resolve(uid, ref manager, false) &&
|
||||
PhysicsQuery.Resolve(uid, ref body, false) &&
|
||||
XformQuery.Resolve(uid, ref xform, false))
|
||||
_xformQuery.Resolve(uid, ref xform, false))
|
||||
{
|
||||
SetBodyType(uid, value, manager, body, xform);
|
||||
}
|
||||
@@ -510,6 +511,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
var oldType = body.BodyType;
|
||||
body.BodyType = value;
|
||||
ResetMassData(uid, manager, body);
|
||||
|
||||
body.Force = Vector2.Zero;
|
||||
body.Torque = 0f;
|
||||
@@ -703,7 +705,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public Transform GetRelativePhysicsTransform(Transform worldTransform, Entity<TransformComponent?> relative)
|
||||
{
|
||||
if (!XformQuery.Resolve(relative.Owner, ref relative.Comp))
|
||||
if (!_xformQuery.Resolve(relative.Owner, ref relative.Comp))
|
||||
return Physics.Transform.Empty;
|
||||
|
||||
var (_, broadphaseRot, _, broadphaseInv) = _transform.GetWorldPositionRotationMatrixWithInv(relative.Comp);
|
||||
@@ -719,8 +721,8 @@ public partial class SharedPhysicsSystem
|
||||
Entity<TransformComponent?> entity,
|
||||
Entity<TransformComponent?> relative)
|
||||
{
|
||||
if (!XformQuery.Resolve(entity.Owner, ref entity.Comp) ||
|
||||
!XformQuery.Resolve(relative.Owner, ref relative.Comp))
|
||||
if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp) ||
|
||||
!_xformQuery.Resolve(relative.Owner, ref relative.Comp))
|
||||
{
|
||||
return Physics.Transform.Empty;
|
||||
}
|
||||
@@ -736,7 +738,7 @@ public partial class SharedPhysicsSystem
|
||||
/// </summary>
|
||||
public Transform GetLocalPhysicsTransform(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref xform) || xform.Broadphase == null)
|
||||
if (!_xformQuery.Resolve(uid, ref xform) || xform.Broadphase == null)
|
||||
return Physics.Transform.Empty;
|
||||
|
||||
var broadphase = xform.Broadphase.Value.Uid;
|
||||
@@ -751,7 +753,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public Transform GetPhysicsTransform(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return Physics.Transform.Empty;
|
||||
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
|
||||
|
||||
@@ -123,26 +123,24 @@ public abstract partial class SharedPhysicsSystem
|
||||
DebugTools.Assert(obj.Flags is ContactFlags.None or ContactFlags.Deleted);
|
||||
SetContact(obj,
|
||||
false,
|
||||
new Entity<PhysicsComponent?, TransformComponent?>(EntityUid.Invalid, null, null),
|
||||
new Entity<PhysicsComponent?, TransformComponent?>(EntityUid.Invalid, null, null),
|
||||
EntityUid.Invalid, EntityUid.Invalid,
|
||||
string.Empty, string.Empty,
|
||||
null, 0,
|
||||
null, 0);
|
||||
null, 0,
|
||||
null, null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetContact(Contact contact,
|
||||
bool enabled,
|
||||
Entity<PhysicsComponent?, TransformComponent?> entA,
|
||||
Entity<PhysicsComponent?, TransformComponent?> entB,
|
||||
EntityUid uidA, EntityUid uidB,
|
||||
string fixtureAId, string fixtureBId,
|
||||
Fixture? fixtureA, int indexA,
|
||||
Fixture? fixtureB, int indexB)
|
||||
Fixture? fixtureB, int indexB,
|
||||
PhysicsComponent? bodyA,
|
||||
PhysicsComponent? bodyB)
|
||||
{
|
||||
var uidA = entA.Owner;
|
||||
var uidB = entB.Owner;
|
||||
|
||||
contact.Enabled = enabled;
|
||||
contact.IsTouching = false;
|
||||
DebugTools.Assert(contact.Flags is ContactFlags.None or ContactFlags.PreInit or ContactFlags.Deleted);
|
||||
@@ -157,11 +155,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
contact.FixtureA = fixtureA;
|
||||
contact.FixtureB = fixtureB;
|
||||
|
||||
contact.BodyA = entA.Comp1;
|
||||
contact.BodyB = entB.Comp1;
|
||||
|
||||
contact.XformA = entA.Comp2;
|
||||
contact.XformB = entB.Comp2;
|
||||
contact.BodyA = bodyA;
|
||||
contact.BodyB = bodyB;
|
||||
|
||||
contact.ChildIndexA = indexA;
|
||||
contact.ChildIndexB = indexB;
|
||||
@@ -218,10 +213,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
private Contact CreateContact(
|
||||
Entity<PhysicsComponent?, TransformComponent?> entA, Entity<PhysicsComponent?, TransformComponent?> entB,
|
||||
EntityUid uidA, EntityUid uidB,
|
||||
string fixtureAId, string fixtureBId,
|
||||
Fixture fixtureA, int indexA,
|
||||
Fixture fixtureB, int indexB)
|
||||
Fixture fixtureB, int indexB,
|
||||
PhysicsComponent bodyA, PhysicsComponent bodyB)
|
||||
{
|
||||
var type1 = fixtureA.Shape.ShapeType;
|
||||
var type2 = fixtureB.Shape.ShapeType;
|
||||
@@ -237,11 +233,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Edge+Polygon is non-symmetrical due to the way Erin handles collision type registration.
|
||||
if ((type1 >= type2 || (type1 == ShapeType.Edge && type2 == ShapeType.Polygon)) && !(type2 == ShapeType.Edge && type1 == ShapeType.Polygon))
|
||||
{
|
||||
SetContact(contact, true, entA, entB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB);
|
||||
SetContact(contact, true, uidA, uidB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB, bodyA, bodyB);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetContact(contact, true, entB, entA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA);
|
||||
SetContact(contact, true, uidB, uidA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA, bodyB, bodyA);
|
||||
}
|
||||
|
||||
contact.Type = _registers[(int)type1, (int)type2];
|
||||
@@ -253,7 +249,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// Try to create a contact between these 2 fixtures.
|
||||
/// </summary>
|
||||
internal void AddPair(
|
||||
Entity<PhysicsComponent, TransformComponent> entA, Entity<PhysicsComponent, TransformComponent> entB,
|
||||
EntityUid uidA, EntityUid uidB,
|
||||
string fixtureAId, string fixtureBId,
|
||||
Fixture fixtureA, int indexA,
|
||||
Fixture fixtureB, int indexB,
|
||||
@@ -268,15 +264,16 @@ public abstract partial class SharedPhysicsSystem
|
||||
return;
|
||||
|
||||
DebugTools.Assert(!fixtureB.Contacts.ContainsKey(fixtureA));
|
||||
var xformA = entA.Comp2;
|
||||
var xformB = entB.Comp2;
|
||||
|
||||
var xformA = _xformQuery.GetComponent(uidA);
|
||||
var xformB = _xformQuery.GetComponent(uidB);
|
||||
|
||||
// Does a joint override collision? Is at least one body dynamic?
|
||||
if (!ShouldCollide(entA.Owner, entB.Owner, bodyA, bodyB, fixtureA, fixtureB, xformA, xformB))
|
||||
if (!ShouldCollide(uidA, uidB, bodyA, bodyB, fixtureA, fixtureB, xformA, xformB))
|
||||
return;
|
||||
|
||||
// Call the factory.
|
||||
var contact = CreateContact((entA.Owner, entA.Comp1, entA.Comp2), (entB.Owner, entB.Comp1, entB.Comp2), fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB);
|
||||
var contact = CreateContact(uidA, uidB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB, bodyA, bodyB);
|
||||
contact.Flags = flags;
|
||||
|
||||
// Contact creation may swap fixtures.
|
||||
@@ -303,7 +300,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Checking only bodyA should be okay because if bodyA is the other ent (i.e. dynamic / kinematic) then it should already be awake.
|
||||
if (bodyA.BodyType == BodyType.Static && !bodyB.Awake)
|
||||
{
|
||||
WakeBody(entB.Owner, body: bodyB);
|
||||
WakeBody(uidB, body: bodyB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,8 +309,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// </summary>
|
||||
internal void AddPair(string fixtureAId, string fixtureBId, in FixtureProxy proxyA, in FixtureProxy proxyB)
|
||||
{
|
||||
AddPair((proxyA.Entity, proxyA.Body, proxyA.Xform),
|
||||
(proxyB.Entity, proxyB.Body, proxyB.Xform),
|
||||
AddPair(proxyA.Entity, proxyB.Entity,
|
||||
fixtureAId, fixtureBId,
|
||||
proxyA.Fixture, proxyA.ChildIndex,
|
||||
proxyB.Fixture, proxyB.ChildIndex,
|
||||
@@ -433,8 +429,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
var xformA = contact.XformA!;
|
||||
var xformB = contact.XformB!;
|
||||
var xformA = _xformQuery.GetComponent(uidA);
|
||||
var xformB = _xformQuery.GetComponent(uidB);
|
||||
|
||||
if (xformA.MapID == MapId.Nullspace || xformB.MapID == MapId.Nullspace)
|
||||
{
|
||||
@@ -530,8 +526,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Instead of transforming both boxes (which enlarges both aabbs), maybe just transform one box.
|
||||
// I.e. use (matrixA * invMatrixB).TransformBox(). Or (invMatrixB * matrixA), whichever is correct.
|
||||
// Alternatively, maybe just directly construct the relative transform matrix?
|
||||
var proxyAWorldAABB = _transform.GetWorldMatrix(XformQuery.GetComponent(broadphaseA.Value)).TransformBox(proxyA.AABB);
|
||||
var proxyBWorldAABB = _transform.GetWorldMatrix(XformQuery.GetComponent(broadphaseB.Value)).TransformBox(proxyB.AABB);
|
||||
var proxyAWorldAABB = _transform.GetWorldMatrix(_xformQuery.GetComponent(broadphaseA.Value), _xformQuery).TransformBox(proxyA.AABB);
|
||||
var proxyBWorldAABB = _transform.GetWorldMatrix(_xformQuery.GetComponent(broadphaseB.Value), _xformQuery).TransformBox(proxyB.AABB);
|
||||
overlap = proxyAWorldAABB.Intersects(proxyBWorldAABB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
@@ -126,8 +125,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
*/
|
||||
private const int MaxIslands = 256;
|
||||
|
||||
private readonly ObjectPool<List<Entity<PhysicsComponent, TransformComponent>>> _islandBodyPool =
|
||||
new DefaultObjectPool<List<Entity<PhysicsComponent, TransformComponent>>>(new ListPolicy<Entity<PhysicsComponent, TransformComponent>>(), MaxIslands);
|
||||
private readonly ObjectPool<List<PhysicsComponent>> _islandBodyPool =
|
||||
new DefaultObjectPool<List<PhysicsComponent>>(new ListPolicy<PhysicsComponent>(), MaxIslands);
|
||||
|
||||
private readonly ObjectPool<List<Contact>> _islandContactPool =
|
||||
new DefaultObjectPool<List<Contact>>(new ListPolicy<Contact>(), MaxIslands);
|
||||
@@ -141,16 +140,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
internal record struct IslandData(
|
||||
int Index,
|
||||
bool LoneIsland,
|
||||
List<Entity<PhysicsComponent, TransformComponent>> Bodies,
|
||||
List<PhysicsComponent> Bodies,
|
||||
List<Contact> Contacts,
|
||||
List<(Joint Original, Joint Joint)> Joints,
|
||||
List<(Joint Joint, float Error)> BrokenJoints)
|
||||
{
|
||||
/// <summary>
|
||||
/// MapUid for the island.
|
||||
/// </summary>
|
||||
public EntityUid MapUid;
|
||||
|
||||
/// <summary>
|
||||
/// Island index to be used for bodies to identify which island they're in.
|
||||
/// </summary>
|
||||
@@ -167,7 +161,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// </summary>
|
||||
public int Offset = 0;
|
||||
|
||||
public readonly List<Entity<PhysicsComponent, TransformComponent>> Bodies = Bodies;
|
||||
public readonly List<PhysicsComponent> Bodies = Bodies;
|
||||
public readonly List<Contact> Contacts = Contacts;
|
||||
public readonly List<(Joint Original, Joint Joint)> Joints = Joints;
|
||||
public bool PositionSolved = false;
|
||||
@@ -175,9 +169,9 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
// Caching for island generation.
|
||||
private readonly HashSet<Entity<PhysicsComponent, TransformComponent>> _islandSet = new(64);
|
||||
private readonly Stack<Entity<PhysicsComponent, TransformComponent>> _bodyStack = new(64);
|
||||
private readonly List<Entity<PhysicsComponent, TransformComponent>> _awakeBodyList = new(256);
|
||||
private readonly HashSet<PhysicsComponent> _islandSet = new(64);
|
||||
private readonly Stack<PhysicsComponent> _bodyStack = new(64);
|
||||
private readonly List<PhysicsComponent> _awakeBodyList = new(256);
|
||||
|
||||
// Config
|
||||
private bool _warmStarting;
|
||||
@@ -266,50 +260,47 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// <summary>
|
||||
/// Where the magic happens.
|
||||
/// </summary>
|
||||
public void Step(float frameTime, bool prediction)
|
||||
public void Step(EntityUid uid, PhysicsMapComponent component, float frameTime, bool prediction)
|
||||
{
|
||||
var invDt = frameTime > 0.0f ? 1.0f / frameTime : 0.0f;
|
||||
var dtRatio = _invDt0 * frameTime;
|
||||
var dtRatio = component._invDt0 * frameTime;
|
||||
|
||||
// Integrate velocities, solve velocity constraints, and do integration.
|
||||
Solve(frameTime, dtRatio, invDt, prediction);
|
||||
Solve(uid, component, frameTime, dtRatio, invDt, prediction);
|
||||
|
||||
// TODO: SolveTOI
|
||||
|
||||
// Box2d recommends clearing (if you are) during fixed updates rather than variable if you are using it
|
||||
if (_autoClearForces)
|
||||
ClearForces();
|
||||
var updateAfterSolve = new PhysicsUpdateAfterMapSolveEvent(prediction, component, frameTime);
|
||||
RaiseLocalEvent(ref updateAfterSolve);
|
||||
|
||||
_invDt0 = invDt;
|
||||
// Box2d recommends clearing (if you are) during fixed updates rather than variable if you are using it
|
||||
if (!prediction && component.AutoClearForces)
|
||||
ClearForces(component);
|
||||
|
||||
component._invDt0 = invDt;
|
||||
}
|
||||
|
||||
private void ClearForces()
|
||||
private void ClearForces(PhysicsMapComponent component)
|
||||
{
|
||||
foreach (var ent in AwakeBodies)
|
||||
foreach (var body in component.AwakeBodies)
|
||||
{
|
||||
var uid = ent.Owner;
|
||||
var body = ent.Comp1;
|
||||
|
||||
if (body.Force != Vector2.Zero)
|
||||
{
|
||||
body.Force = Vector2.Zero;
|
||||
DirtyField(uid, body, nameof(PhysicsComponent.Force));
|
||||
}
|
||||
|
||||
if (body.Torque != 0f)
|
||||
{
|
||||
body.Torque = 0f;
|
||||
DirtyField(uid, body, nameof(PhysicsComponent.Torque));
|
||||
}
|
||||
// TODO: Netsync
|
||||
body.Force = Vector2.Zero;
|
||||
body.Torque = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void Solve(float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
private void Solve(EntityUid uid, PhysicsMapComponent component, float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
{
|
||||
// Build and simulated islands from awake bodies.
|
||||
_bodyStack.EnsureCapacity(AwakeBodies.Count);
|
||||
_islandSet.EnsureCapacity(AwakeBodies.Count);
|
||||
_awakeBodyList.AddRange(AwakeBodies);
|
||||
_bodyStack.EnsureCapacity(component.AwakeBodies.Count);
|
||||
_islandSet.EnsureCapacity(component.AwakeBodies.Count);
|
||||
_awakeBodyList.AddRange(component.AwakeBodies);
|
||||
|
||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
var jointQuery = GetEntityQuery<JointComponent>();
|
||||
var jointRelayQuery = GetEntityQuery<JointRelayTargetComponent>();
|
||||
|
||||
var islandIndex = 0;
|
||||
var loneIsland = new IslandData(
|
||||
@@ -324,28 +315,18 @@ public abstract partial class SharedPhysicsSystem
|
||||
var islandJoints = new List<(Joint Original, Joint Joint)>();
|
||||
|
||||
// Build the relevant islands / graphs for all bodies.
|
||||
foreach (var ent in _awakeBodyList)
|
||||
foreach (var seed in _awakeBodyList)
|
||||
{
|
||||
var xform = ent.Comp2;
|
||||
var seed = ent.Comp1;
|
||||
|
||||
// I tried not running prediction for non-contacted entities but unfortunately it looked like shit
|
||||
// when contact broke so if you want to try that then GOOD LUCK.
|
||||
if (seed.Island) continue;
|
||||
|
||||
var seedUid = seed.Owner;
|
||||
var mapUid = xform.MapUid;
|
||||
|
||||
// TODO: Handle this on client.
|
||||
if (mapUid == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!EntityManager.MetaQuery.TryGetComponent(seedUid, out var metadata))
|
||||
if (!metaQuery.TryGetComponent(seedUid, out var metadata))
|
||||
{
|
||||
Log.Error($"Found deleted entity {ToPrettyString(seedUid)} on map!");
|
||||
RemoveSleepBody(ent);
|
||||
RemoveSleepBody(seedUid, seed, component);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -361,18 +342,16 @@ public abstract partial class SharedPhysicsSystem
|
||||
var bodies = _islandBodyPool.Get();
|
||||
var contacts = _islandContactPool.Get();
|
||||
var joints = _islandJointPool.Get();
|
||||
_bodyStack.Push(ent);
|
||||
_bodyStack.Push(seed);
|
||||
|
||||
seed.Island = true;
|
||||
|
||||
while (_bodyStack.TryPop(out var bodyEnt))
|
||||
while (_bodyStack.TryPop(out var body))
|
||||
{
|
||||
var bodyUid = bodyEnt.Owner;
|
||||
var body = bodyEnt.Comp1;
|
||||
var bodyUid = body.Owner;
|
||||
|
||||
bodies.Add(bodyEnt);
|
||||
|
||||
_islandSet.Add(bodyEnt);
|
||||
bodies.Add(body);
|
||||
_islandSet.Add(body);
|
||||
|
||||
// Static bodies don't propagate islands
|
||||
if (body.BodyType == BodyType.Static) continue;
|
||||
@@ -398,24 +377,24 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
contacts.Add(contact);
|
||||
contact.Flags |= ContactFlags.Island;
|
||||
var other = contact.OtherBody(bodyUid);
|
||||
var bodyA = contact.BodyA!;
|
||||
var bodyB = contact.BodyB!;
|
||||
|
||||
var other = bodyA == body ? bodyB : bodyA;
|
||||
|
||||
// Was the other body already added to this island?
|
||||
if (other.Island) continue;
|
||||
|
||||
var otherEnt = contact.OtherEnt(bodyUid);
|
||||
var otherXform = contact.OtherTransform(bodyUid);
|
||||
// TODO: Store this transform on the component directly.
|
||||
_bodyStack.Push(new Entity<PhysicsComponent, TransformComponent>(otherEnt, other, otherXform));
|
||||
_bodyStack.Push(other);
|
||||
other.Island = true;
|
||||
}
|
||||
|
||||
// Handle joints
|
||||
if (RelayTargetQuery.TryGetComponent(bodyUid, out var relayComp))
|
||||
if (jointRelayQuery.TryGetComponent(bodyUid, out var relayComp))
|
||||
{
|
||||
foreach (var relay in relayComp.Relayed)
|
||||
{
|
||||
if (!JointQuery.TryGetComponent(relay, out var jointComp))
|
||||
if (!jointQuery.TryGetComponent(relay, out var jointComp))
|
||||
continue;
|
||||
|
||||
foreach (var joint in jointComp.GetJoints.Values)
|
||||
@@ -427,14 +406,14 @@ public abstract partial class SharedPhysicsSystem
|
||||
var uidB = joint.BodyBUid;
|
||||
DebugTools.AssertNotEqual(uidA, uidB);
|
||||
|
||||
if (JointQuery.TryGetComponent(uidA, out var jointCompA) &&
|
||||
if (jointQuery.TryGetComponent(uidA, out var jointCompA) &&
|
||||
jointCompA.Relay != null)
|
||||
{
|
||||
DebugTools.AssertNotEqual(uidB, jointCompA.Relay.Value);
|
||||
uidA = jointCompA.Relay.Value;
|
||||
}
|
||||
|
||||
if (JointQuery.TryGetComponent(uidB, out var jointCompB) &&
|
||||
if (jointQuery.TryGetComponent(uidB, out var jointCompB) &&
|
||||
jointCompB.Relay != null)
|
||||
{
|
||||
DebugTools.AssertNotEqual(uidA, jointCompB.Relay.Value);
|
||||
@@ -448,7 +427,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
}
|
||||
|
||||
if (JointQuery.TryGetComponent(bodyUid, out var jointComponent) &&
|
||||
if (jointQuery.TryGetComponent(bodyUid, out var jointComponent) &&
|
||||
jointComponent.Relay == null)
|
||||
{
|
||||
foreach (var joint in jointComponent.Joints.Values)
|
||||
@@ -459,13 +438,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
var uidA = joint.BodyAUid;
|
||||
var uidB = joint.BodyBUid;
|
||||
|
||||
if (JointQuery.TryGetComponent(uidA, out var jointCompA) &&
|
||||
if (jointQuery.TryGetComponent(uidA, out var jointCompA) &&
|
||||
jointCompA.Relay != null)
|
||||
{
|
||||
uidA = jointCompA.Relay.Value;
|
||||
}
|
||||
|
||||
if (JointQuery.TryGetComponent(uidB, out var jointCompB) &&
|
||||
if (jointQuery.TryGetComponent(uidB, out var jointCompB) &&
|
||||
jointCompB.Relay != null)
|
||||
{
|
||||
uidB = jointCompB.Relay.Value;
|
||||
@@ -479,9 +458,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
foreach (var (original, joint) in islandJoints)
|
||||
{
|
||||
// TODO: Same here store physicscomp + transform on the joint, the savings are worth it.
|
||||
var bodyA = PhysicsQuery.GetComponent(joint.BodyAUid);
|
||||
var bodyB = PhysicsQuery.GetComponent(joint.BodyBUid);
|
||||
var bodyA = bodyQuery.GetComponent(joint.BodyAUid);
|
||||
var bodyB = bodyQuery.GetComponent(joint.BodyBUid);
|
||||
|
||||
if (!bodyA.CanCollide || !bodyB.CanCollide)
|
||||
continue;
|
||||
@@ -490,13 +468,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
if (!bodyA.Island)
|
||||
{
|
||||
_bodyStack.Push(new Entity<PhysicsComponent, TransformComponent>(joint.BodyAUid, bodyA, XformQuery.GetComponent(joint.BodyAUid)));
|
||||
_bodyStack.Push(bodyA);
|
||||
bodyA.Island = true;
|
||||
}
|
||||
|
||||
if (!bodyB.Island)
|
||||
{
|
||||
_bodyStack.Push(new Entity<PhysicsComponent, TransformComponent>(joint.BodyBUid, bodyB, XformQuery.GetComponent(joint.BodyBUid)));
|
||||
_bodyStack.Push(bodyB);
|
||||
bodyB.Island = true;
|
||||
}
|
||||
}
|
||||
@@ -509,17 +487,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Bodies not touching anything, hence we can just add it to the lone island.
|
||||
if (contacts.Count == 0 && joints.Count == 0)
|
||||
{
|
||||
DebugTools.Assert(bodies.Count == 1 && bodies[0].Comp1.BodyType != BodyType.Static);
|
||||
loneIsland.MapUid = mapUid.Value;
|
||||
DebugTools.Assert(bodies.Count == 1 && bodies[0].BodyType != BodyType.Static);
|
||||
loneIsland.Bodies.Add(bodies[0]);
|
||||
idx = loneIsland.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = new IslandData(islandIndex++, false, bodies, contacts, joints, new List<(Joint Joint, float Error)>())
|
||||
{
|
||||
MapUid = mapUid.Value
|
||||
};
|
||||
var data = new IslandData(islandIndex++, false, bodies, contacts, joints, new List<(Joint Joint, float Error)>());
|
||||
islands.Add(data);
|
||||
idx = data.Index;
|
||||
}
|
||||
@@ -527,7 +501,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Allow static bodies to be re-used in other islands
|
||||
for (var i = 0; i < bodies.Count; i++)
|
||||
{
|
||||
var body = bodies[i].Comp1;
|
||||
var body = bodies[i];
|
||||
|
||||
// Static bodies can participate in other islands
|
||||
if (body.BodyType == BodyType.Static)
|
||||
@@ -549,21 +523,20 @@ public abstract partial class SharedPhysicsSystem
|
||||
ReturnIsland(loneIsland);
|
||||
}
|
||||
|
||||
SolveIslands(islands, frameTime, dtRatio, invDt, prediction);
|
||||
SolveIslands(uid, component, islands, frameTime, dtRatio, invDt, prediction);
|
||||
|
||||
foreach (var island in islands)
|
||||
{
|
||||
ReturnIsland(island);
|
||||
}
|
||||
|
||||
Cleanup(frameTime);
|
||||
Cleanup(component, frameTime);
|
||||
}
|
||||
|
||||
private void ReturnIsland(IslandData island)
|
||||
{
|
||||
foreach (var bodyEnt in island.Bodies)
|
||||
foreach (var body in island.Bodies)
|
||||
{
|
||||
var body = bodyEnt.Comp1;
|
||||
DebugTools.Assert(body.IslandIndex.ContainsKey(island.Index));
|
||||
body.IslandIndex.Remove(island.Index);
|
||||
}
|
||||
@@ -586,12 +559,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
island.BrokenJoints.Clear();
|
||||
}
|
||||
|
||||
protected virtual void Cleanup(float frameTime)
|
||||
protected virtual void Cleanup(PhysicsMapComponent component, float frameTime)
|
||||
{
|
||||
foreach (var bodyEnt in _islandSet)
|
||||
foreach (var body in _islandSet)
|
||||
{
|
||||
var body = bodyEnt.Comp1;
|
||||
|
||||
if (!body.Island || body.Deleted)
|
||||
{
|
||||
continue;
|
||||
@@ -603,14 +574,15 @@ public abstract partial class SharedPhysicsSystem
|
||||
// So Box2D would update broadphase here buutttt we'll just wait until MoveEvent queue is used.
|
||||
}
|
||||
|
||||
_islandSet.Clear();
|
||||
_islandSet.Clear();
|
||||
_awakeBodyList.Clear();
|
||||
}
|
||||
|
||||
private void SolveIslands(List<IslandData> islands, float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
private void SolveIslands(EntityUid uid, PhysicsMapComponent component, List<IslandData> islands, float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
{
|
||||
var iBegin = 0;
|
||||
var gravity = _gravity.GetGravity(uid);
|
||||
|
||||
var data = new SolverData(
|
||||
frameTime,
|
||||
dtRatio,
|
||||
@@ -637,12 +609,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
var totalBodies = 0;
|
||||
var actualIslands = islands.ToArray();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
for (var i = 0; i < islands.Count; i++)
|
||||
{
|
||||
ref var island = ref actualIslands[i];
|
||||
island.Offset = totalBodies;
|
||||
UpdateLerpData(island.Bodies);
|
||||
UpdateLerpData(component, island.Bodies, xformQuery);
|
||||
|
||||
#if DEBUG
|
||||
RaiseLocalEvent(new IslandSolveMessage(island.Bodies));
|
||||
@@ -675,14 +648,14 @@ public abstract partial class SharedPhysicsSystem
|
||||
if (!InternalParallel(island))
|
||||
break;
|
||||
|
||||
SolveIsland(ref island, in data, options, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
|
||||
SolveIsland(ref island, in data, options, gravity, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
|
||||
iBegin++;
|
||||
}
|
||||
|
||||
Parallel.For(iBegin, actualIslands.Length, options, i =>
|
||||
{
|
||||
ref var island = ref actualIslands[i];
|
||||
SolveIsland(ref island, in data, null, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
|
||||
SolveIsland(ref island, in data, null, gravity, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
|
||||
});
|
||||
|
||||
// Update data sequentially
|
||||
@@ -690,7 +663,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
var island = actualIslands[i];
|
||||
|
||||
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities);
|
||||
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery);
|
||||
SleepBodies(in island, sleepStatus);
|
||||
}
|
||||
|
||||
@@ -706,7 +679,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
/// If this is the first time a body has been updated this tick update its position for lerping.
|
||||
/// Due to substepping we have to check it every time.
|
||||
/// </summary>
|
||||
protected virtual void UpdateLerpData(List<Entity<PhysicsComponent, TransformComponent>> bodies)
|
||||
protected virtual void UpdateLerpData(PhysicsMapComponent component, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -729,6 +702,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
ref IslandData island,
|
||||
in SolverData data,
|
||||
ParallelOptions? options,
|
||||
Vector2 gravity,
|
||||
bool prediction,
|
||||
Vector2[] solvedPositions,
|
||||
float[] solvedAngles,
|
||||
@@ -740,16 +714,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
var positions = ArrayPool<Vector2>.Shared.Rent(bodyCount);
|
||||
var angles = ArrayPool<float>.Shared.Rent(bodyCount);
|
||||
var offset = island.Offset;
|
||||
var gravity = Gravity;
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
for (var i = 0; i < island.Bodies.Count; i++)
|
||||
{
|
||||
var bodyEnt = island.Bodies[i];
|
||||
var body = bodyEnt.Comp1;
|
||||
var xform = bodyEnt.Comp2;
|
||||
var body = island.Bodies[i];
|
||||
var (worldPos, worldRot) =
|
||||
_transform.GetWorldPositionRotation(xform);
|
||||
_transform.GetWorldPositionRotation(xformQuery.GetComponent(body.Owner), xformQuery);
|
||||
|
||||
var transform = new Transform(worldPos, worldRot);
|
||||
var position = Physics.Transform.Mul(transform, body.LocalCenter);
|
||||
@@ -766,13 +737,9 @@ public abstract partial class SharedPhysicsSystem
|
||||
if (body.BodyType == BodyType.Dynamic)
|
||||
{
|
||||
if (body.IgnoreGravity)
|
||||
{
|
||||
linearVelocity += body.Force * data.FrameTime * body.InvMass;
|
||||
}
|
||||
else
|
||||
{
|
||||
linearVelocity += (gravity + body.Force * body.InvMass) * data.FrameTime;
|
||||
}
|
||||
|
||||
angularVelocity += body.InvI * body.Torque * data.FrameTime;
|
||||
|
||||
@@ -801,6 +768,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
var jointCount = island.Joints.Count;
|
||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
if (jointCount > 0)
|
||||
{
|
||||
@@ -809,8 +777,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
var joint = island.Joints[i].Joint;
|
||||
if (!joint.Enabled) continue;
|
||||
|
||||
var bodyA = PhysicsQuery.GetComponent(joint.BodyAUid);
|
||||
var bodyB = PhysicsQuery.GetComponent(joint.BodyBUid);
|
||||
var bodyA = bodyQuery.GetComponent(joint.BodyAUid);
|
||||
var bodyB = bodyQuery.GetComponent(joint.BodyBUid);
|
||||
joint.InitVelocityConstraints(in data, in island, bodyA, bodyB, positions, angles, linearVelocities, angularVelocities);
|
||||
}
|
||||
}
|
||||
@@ -912,12 +880,12 @@ public abstract partial class SharedPhysicsSystem
|
||||
var start = i * FinaliseBodies;
|
||||
var end = Math.Min(bodyCount, start + FinaliseBodies);
|
||||
|
||||
FinalisePositions(start, end, offset, bodies, positions, angles, solvedPositions, solvedAngles);
|
||||
FinalisePositions(start, end, offset, bodies, xformQuery, positions, angles, solvedPositions, solvedAngles);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
FinalisePositions(0, bodyCount, offset, bodies, positions, angles, solvedPositions, solvedAngles);
|
||||
FinalisePositions(0, bodyCount, offset, bodies,xformQuery, positions, angles, solvedPositions, solvedAngles);
|
||||
}
|
||||
|
||||
// Check sleep status for all of the bodies
|
||||
@@ -931,7 +899,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
for (var i = 0; i < bodyCount; i++)
|
||||
{
|
||||
var body = island.Bodies[i].Comp1;
|
||||
var body = island.Bodies[i];
|
||||
|
||||
if (body.BodyType == BodyType.Static) continue;
|
||||
|
||||
@@ -962,7 +930,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
for (var i = 0; i < bodyCount; i++)
|
||||
{
|
||||
var body = island.Bodies[i].Comp1;
|
||||
var body = island.Bodies[i];
|
||||
|
||||
if (body.BodyType == BodyType.Static) continue;
|
||||
|
||||
@@ -997,18 +965,18 @@ public abstract partial class SharedPhysicsSystem
|
||||
ArrayPool<ContactPositionConstraint>.Shared.Return(positionConstraints);
|
||||
}
|
||||
|
||||
private void FinalisePositions(int start, int end, int offset, List<Entity<PhysicsComponent, TransformComponent>> bodies, Vector2[] positions, float[] angles, Vector2[] solvedPositions, float[] solvedAngles)
|
||||
private void FinalisePositions(int start, int end, int offset, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery, Vector2[] positions, float[] angles, Vector2[] solvedPositions, float[] solvedAngles)
|
||||
{
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
var ent = bodies[i];
|
||||
var body = ent.Comp1;
|
||||
var body = bodies[i];
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
continue;
|
||||
|
||||
var xform = ent.Comp2;
|
||||
var (_, parentRot, parentInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(xform.ParentUid);
|
||||
var xform = xformQuery.GetComponent(body.Owner);
|
||||
var parentXform = xformQuery.GetComponent(xform.ParentUid);
|
||||
var (_, parentRot, parentInvMatrix) = parentXform.GetWorldPositionRotationInvMatrix(xformQuery);
|
||||
var worldRot = (float) (parentRot + xform._localRotation);
|
||||
|
||||
var angle = angles[i];
|
||||
@@ -1031,7 +999,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
Vector2[] positions,
|
||||
float[] angles,
|
||||
Vector2[] linearVelocities,
|
||||
float[] angularVelocities)
|
||||
float[] angularVelocities,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
foreach (var (joint, error) in island.BrokenJoints)
|
||||
{
|
||||
@@ -1046,17 +1015,16 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
for (var i = 0; i < island.Bodies.Count; i++)
|
||||
{
|
||||
var bodyEnt = island.Bodies[i];
|
||||
var body = bodyEnt.Comp1;
|
||||
var body = island.Bodies[i];
|
||||
|
||||
// So technically we don't /need/ to skip static bodies here but it saves us having to check for deferred updates so we'll do it anyway.
|
||||
// Plus calcing worldpos can be costly so we skip that too which is nice.
|
||||
if (body.BodyType == BodyType.Static) continue;
|
||||
|
||||
var uid = bodyEnt.Owner;
|
||||
var uid = body.Owner;
|
||||
var position = positions[offset + i];
|
||||
var angle = angles[offset + i];
|
||||
var xform = bodyEnt.Comp2;
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
|
||||
var linVelocity = linearVelocities[offset + i];
|
||||
var physicsDirtied = false;
|
||||
@@ -1084,9 +1052,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
if (physicsDirtied)
|
||||
{
|
||||
Dirty(uid, body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,36 +9,45 @@ public partial class SharedPhysicsSystem
|
||||
{
|
||||
#region AddRemove
|
||||
|
||||
internal void AddAwakeBody(Entity<PhysicsComponent, TransformComponent> ent)
|
||||
internal void AddAwakeBody(EntityUid uid, PhysicsComponent body, PhysicsMapComponent? map = null)
|
||||
{
|
||||
var body = ent.Comp1;
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
if (!body.CanCollide)
|
||||
{
|
||||
Log.Error($"Tried to add non-colliding {ToPrettyString(ent)} as an awake body to map!");
|
||||
Log.Error($"Tried to add non-colliding {ToPrettyString(uid)} as an awake body to map!");
|
||||
DebugTools.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
{
|
||||
Log.Error($"Tried to add static body {ToPrettyString(ent)} as an awake body to map!");
|
||||
Log.Error($"Tried to add static body {ToPrettyString(uid)} as an awake body to map!");
|
||||
DebugTools.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(body.Awake);
|
||||
DebugTools.Assert(!AwakeBodies.Contains(ent));
|
||||
AwakeBodies.Add(ent);
|
||||
map.AwakeBodies.Add(body);
|
||||
}
|
||||
|
||||
internal void RemoveSleepBody(Entity<PhysicsComponent, TransformComponent> ent)
|
||||
internal void AddAwakeBody(EntityUid uid, PhysicsComponent body, EntityUid mapUid, PhysicsMapComponent? map = null)
|
||||
{
|
||||
DebugTools.Assert(!ent.Comp1.Awake);
|
||||
DebugTools.Assert(AwakeBodies.Contains(ent));
|
||||
AwakeBodies.Remove(ent);
|
||||
PhysMapQuery.Resolve(mapUid, ref map, false);
|
||||
AddAwakeBody(uid, body, map);
|
||||
}
|
||||
|
||||
internal void RemoveSleepBody(EntityUid uid, PhysicsComponent body, PhysicsMapComponent? map = null)
|
||||
{
|
||||
map?.AwakeBodies.Remove(body);
|
||||
}
|
||||
|
||||
internal void RemoveSleepBody(EntityUid uid, PhysicsComponent body, EntityUid mapUid, PhysicsMapComponent? map = null)
|
||||
{
|
||||
PhysMapQuery.Resolve(mapUid, ref map, false);
|
||||
RemoveSleepBody(uid, body, map);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
shape.Radius = radius;
|
||||
|
||||
if (body.CanCollide &&
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
|
||||
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
|
||||
{
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
|
||||
}
|
||||
|
||||
@@ -57,9 +58,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
shape.Radius = radius;
|
||||
|
||||
if (body.CanCollide &&
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
|
||||
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
|
||||
{
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
|
||||
}
|
||||
|
||||
@@ -82,9 +84,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
circle.Position = position;
|
||||
|
||||
if (body.CanCollide &&
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
|
||||
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
|
||||
{
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
|
||||
}
|
||||
|
||||
@@ -117,9 +120,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
edge.Vertex3 = vertex3;
|
||||
|
||||
if (body.CanCollide &&
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
|
||||
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
|
||||
{
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
|
||||
}
|
||||
|
||||
@@ -146,9 +150,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
poly.Set(vertices, vertices.Length);
|
||||
|
||||
if (body.CanCollide &&
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
|
||||
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
|
||||
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
|
||||
{
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
if (!PhysicsQuery.Resolve(uid, ref component))
|
||||
return Vector2.Zero;
|
||||
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return Vector2.Zero;
|
||||
|
||||
var velocity = component.LinearVelocity;
|
||||
@@ -56,7 +56,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
// Could make this a method with the below one but ehh
|
||||
// then you get a method bigger than this block with a billion out args and who wants that.
|
||||
var xform = XformQuery.GetComponent(parent);
|
||||
var xform = _xformQuery.GetComponent(parent);
|
||||
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
@@ -86,11 +86,9 @@ public abstract partial class SharedPhysicsSystem
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return Vector2.Zero;
|
||||
|
||||
PhysicsQuery.Resolve(uid, ref component, false);
|
||||
|
||||
var parent = xform.ParentUid;
|
||||
var localPos = xform.LocalPosition;
|
||||
|
||||
@@ -99,7 +97,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
while (parent != xform.MapUid && parent.IsValid())
|
||||
{
|
||||
xform = XformQuery.GetComponent(parent);
|
||||
xform = _xformQuery.GetComponent(parent);
|
||||
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
@@ -133,19 +131,20 @@ public abstract partial class SharedPhysicsSystem
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
if (!PhysicsQuery.Resolve(uid, ref component))
|
||||
return 0;
|
||||
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return 0f;
|
||||
|
||||
PhysicsQuery.Resolve(uid, ref component, false);
|
||||
|
||||
var angularVelocity = component?.AngularVelocity ?? 0;
|
||||
var angularVelocity = component.AngularVelocity;
|
||||
|
||||
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
|
||||
{
|
||||
if (PhysicsQuery.TryGetComponent(xform.ParentUid, out var body))
|
||||
angularVelocity += body.AngularVelocity;
|
||||
|
||||
xform = XformQuery.GetComponent(xform.ParentUid);
|
||||
xform = _xformQuery.GetComponent(xform.ParentUid);
|
||||
}
|
||||
|
||||
return angularVelocity;
|
||||
@@ -161,22 +160,23 @@ public abstract partial class SharedPhysicsSystem
|
||||
PhysicsComponent? component = null,
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
if (!PhysicsQuery.Resolve(uid, ref component))
|
||||
return (Vector2.Zero, 0);
|
||||
|
||||
PhysicsQuery.Resolve(uid, ref component, false);
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return (Vector2.Zero, 0);
|
||||
|
||||
var parent = xform.ParentUid;
|
||||
|
||||
var localPos = xform.LocalPosition;
|
||||
|
||||
var linearVelocity = component?.LinearVelocity ?? Vector2.Zero;
|
||||
var angularVelocity = component?.AngularVelocity ?? 0;
|
||||
var linearVelocity = component.LinearVelocity;
|
||||
var angularVelocity = component.AngularVelocity;
|
||||
Vector2 linearVelocityAngularContribution = Vector2.Zero;
|
||||
|
||||
while (parent != xform.MapUid && parent.IsValid())
|
||||
{
|
||||
xform = XformQuery.GetComponent(parent);
|
||||
xform = _xformQuery.GetComponent(parent);
|
||||
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
@@ -233,7 +233,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
var parent = oldParent;
|
||||
TransformComponent? parentXform = XformQuery.GetComponent(parent);
|
||||
TransformComponent? parentXform = _xformQuery.GetComponent(parent);
|
||||
var localPos = Vector2.Transform(_transform.GetWorldPosition(xform), _transform.GetInvWorldMatrix(parentXform));
|
||||
|
||||
var oldLinear = physics.LinearVelocity;
|
||||
@@ -258,7 +258,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
localPos = parentXform.LocalPosition + parentXform.LocalRotation.RotateVec(localPos);
|
||||
parent = parentXform.ParentUid;
|
||||
|
||||
} while (parent.IsValid() && XformQuery.TryGetComponent(parent, out parentXform));
|
||||
} while (parent.IsValid() && _xformQuery.TryGetComponent(parent, out parentXform));
|
||||
|
||||
oldLinear += linearAngularContribution;
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Systems;
|
||||
|
||||
public partial class SharedPhysicsSystem
|
||||
{
|
||||
[ViewVariables]
|
||||
public Vector2 Gravity { get; private set; }
|
||||
|
||||
public void SetGravity(Vector2 value)
|
||||
{
|
||||
if (Gravity.Equals(value))
|
||||
return;
|
||||
|
||||
Gravity = value;
|
||||
// TODO: Network + datafield
|
||||
}
|
||||
|
||||
// Box2D has a bunch of methods that work on worlds but in our case separate EntityManager instances are
|
||||
// separate worlds so we can just treat the physics system as the world.
|
||||
private bool _autoClearForces;
|
||||
|
||||
/// <summary>
|
||||
/// When substepping the client needs to know about the first position to use for lerping.
|
||||
/// </summary>
|
||||
protected readonly Dictionary<EntityUid, EntityUid>
|
||||
LerpData = new();
|
||||
|
||||
// TODO:
|
||||
// - Add test that movebuffer removes entities moved to nullspace.
|
||||
|
||||
// Previously we stored the WorldAABB of the proxy being moved and tracked state.
|
||||
// The issue is that if something moves multiple times in a tick it can add up, plus it's also done on hotpaths such as physics.
|
||||
// As such we defer it until we actually try and get contacts, then we can run them in parallel.
|
||||
/// <summary>
|
||||
/// Keep a buffer of everything that moved in a tick. This will be used to check for physics contacts.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal readonly HashSet<FixtureProxy> MoveBuffer = new();
|
||||
|
||||
/// <summary>
|
||||
/// Track moved grids to know if we need to run checks for them driving over entities.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal readonly HashSet<EntityUid> MovedGrids = new();
|
||||
|
||||
/// <summary>
|
||||
/// All awake bodies in the game.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly HashSet<Entity<PhysicsComponent, TransformComponent>> AwakeBodies = new();
|
||||
|
||||
/// <summary>
|
||||
/// Store last tick's invDT
|
||||
/// </summary>
|
||||
private float _invDt0;
|
||||
}
|
||||
@@ -4,10 +4,13 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -39,9 +42,11 @@ namespace Robust.Shared.Physics.Systems
|
||||
Buckets = Histogram.ExponentialBuckets(0.000_001, 1.5, 25)
|
||||
});
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IManifoldManager _manifoldManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IDependencyCollection _deps = default!;
|
||||
[Dependency] private readonly Gravity2DController _gravity = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -60,14 +65,13 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
public bool MetricsEnabled { get; protected set; }
|
||||
|
||||
private EntityQuery<CollideOnAnchorComponent> _anchorQuery;
|
||||
private EntityQuery<FixturesComponent> _fixturesQuery;
|
||||
private EntityQuery<JointComponent> JointQuery;
|
||||
private EntityQuery<JointRelayTargetComponent> RelayTargetQuery;
|
||||
protected EntityQuery<PhysicsComponent> PhysicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<CollideOnAnchorComponent> _anchorQuery;
|
||||
protected EntityQuery<PhysicsMapComponent> PhysMapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
protected EntityQuery<MapComponent> MapQuery;
|
||||
protected EntityQuery<PhysicsComponent> PhysicsQuery;
|
||||
protected EntityQuery<TransformComponent> XformQuery;
|
||||
|
||||
private ComponentRegistration _physicsReg = default!;
|
||||
private byte _angularVelocityIndex;
|
||||
@@ -102,18 +106,18 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
_angularVelocityIndex = 10;
|
||||
|
||||
_anchorQuery = GetEntityQuery<CollideOnAnchorComponent>();
|
||||
_fixturesQuery = GetEntityQuery<FixturesComponent>();
|
||||
JointQuery = GetEntityQuery<JointComponent>();
|
||||
RelayTargetQuery = GetEntityQuery<JointRelayTargetComponent>();
|
||||
MapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
XformQuery = GetEntityQuery<TransformComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_anchorQuery = GetEntityQuery<CollideOnAnchorComponent>();
|
||||
PhysMapQuery = GetEntityQuery<PhysicsMapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
MapQuery = GetEntityQuery<MapComponent>();
|
||||
|
||||
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
|
||||
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
|
||||
SubscribeLocalEvent<PhysicsComponent, EntGotRemovedFromContainerMessage>(HandleContainerRemoved);
|
||||
SubscribeLocalEvent<PhysicsMapComponent, ComponentInit>(HandlePhysicsMapInit);
|
||||
SubscribeLocalEvent<PhysicsComponent, ComponentInit>(OnPhysicsInit);
|
||||
SubscribeLocalEvent<PhysicsComponent, ComponentShutdown>(OnPhysicsShutdown);
|
||||
SubscribeLocalEvent<PhysicsComponent, ComponentGetState>(OnPhysicsGetState);
|
||||
@@ -121,7 +125,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
InitializeIsland();
|
||||
InitializeContacts();
|
||||
|
||||
Subs.CVar(_cfg, CVars.AutoClearForces, OnAutoClearChange, true);
|
||||
Subs.CVar(_cfg, CVars.AutoClearForces, OnAutoClearChange);
|
||||
Subs.CVar(_cfg, CVars.NetTickrate, UpdateSubsteps, true);
|
||||
Subs.CVar(_cfg, CVars.TargetMinimumTickrate, UpdateSubsteps, true);
|
||||
}
|
||||
@@ -149,9 +153,20 @@ namespace Robust.Shared.Physics.Systems
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePhysicsMapInit(EntityUid uid, PhysicsMapComponent component, ComponentInit args)
|
||||
{
|
||||
_deps.InjectDependencies(component);
|
||||
component.AutoClearForces = _cfg.GetCVar(CVars.AutoClearForces);
|
||||
}
|
||||
|
||||
private void OnAutoClearChange(bool value)
|
||||
{
|
||||
_autoClearForces = value;
|
||||
var enumerator = AllEntityQuery<PhysicsMapComponent>();
|
||||
|
||||
while (enumerator.MoveNext(out var comp))
|
||||
{
|
||||
comp.AutoClearForces = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSubsteps(int obj)
|
||||
@@ -184,7 +199,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (oldMap != xform.MapUid)
|
||||
{
|
||||
// This will also handle broadphase updating & joint clearing.
|
||||
HandleMapChange(uid, xform, body);
|
||||
HandleMapChange(uid, xform, body, oldMap, xform.MapUid);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,9 +210,11 @@ namespace Robust.Shared.Physics.Systems
|
||||
/// <summary>
|
||||
/// Recursively add/remove from awake bodies, clear joints, remove from move buffer, and update broadphase.
|
||||
/// </summary>
|
||||
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body)
|
||||
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body, EntityUid? oldMapId, EntityUid? newMapId)
|
||||
{
|
||||
RecursiveMapUpdate(uid, xform, body);
|
||||
PhysMapQuery.TryGetComponent(oldMapId, out var oldMap);
|
||||
PhysMapQuery.TryGetComponent(newMapId, out var newMap);
|
||||
RecursiveMapUpdate(uid, xform, body, newMap, oldMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -206,17 +223,33 @@ namespace Robust.Shared.Physics.Systems
|
||||
private void RecursiveMapUpdate(
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
PhysicsComponent? body)
|
||||
PhysicsComponent? body,
|
||||
PhysicsMapComponent? newMap,
|
||||
PhysicsMapComponent? oldMap)
|
||||
{
|
||||
DebugTools.Assert(!Deleted(uid));
|
||||
|
||||
// This entity may not have a body, but some of its children might:
|
||||
if (body != null)
|
||||
{
|
||||
if (body.Awake)
|
||||
{
|
||||
RemoveSleepBody(uid, body, oldMap);
|
||||
AddAwakeBody(uid, body, newMap);
|
||||
DebugTools.Assert(body.Awake);
|
||||
}
|
||||
else
|
||||
DebugTools.Assert(oldMap?.AwakeBodies.Contains(body) != true);
|
||||
}
|
||||
|
||||
_joints.ClearJoints(uid);
|
||||
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (XformQuery.TryGetComponent(child, out var childXform))
|
||||
if (_xformQuery.TryGetComponent(child, out var childXform))
|
||||
{
|
||||
PhysicsQuery.TryGetComponent(child, out var childBody);
|
||||
RecursiveMapUpdate(child, childXform, childBody);
|
||||
RecursiveMapUpdate(child, childXform, childBody, newMap, oldMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,6 +276,17 @@ namespace Robust.Shared.Physics.Systems
|
||||
ShutdownContacts();
|
||||
}
|
||||
|
||||
private void UpdateMapAwakeState(EntityUid uid, PhysicsComponent body)
|
||||
{
|
||||
if (Transform(uid).MapUid is not {} map)
|
||||
return;
|
||||
|
||||
if (body.Awake)
|
||||
AddAwakeBody(uid, body, map);
|
||||
else
|
||||
RemoveSleepBody(uid, body, map);
|
||||
}
|
||||
|
||||
private void HandleContainerRemoved(EntityUid uid, PhysicsComponent physics, EntGotRemovedFromContainerMessage message)
|
||||
{
|
||||
// If entity being deleted then the parent change will already be handled elsewhere and we don't want to re-add it to the map.
|
||||
@@ -270,11 +314,18 @@ namespace Robust.Shared.Physics.Systems
|
||||
var updateBeforeSolve = new PhysicsUpdateBeforeSolveEvent(prediction, frameTime);
|
||||
RaiseLocalEvent(ref updateBeforeSolve);
|
||||
|
||||
// Find new contacts and (TODO: temporary) update any per-map virtual controllers
|
||||
var contactEnumerator = AllEntityQuery<PhysicsMapComponent, TransformComponent>();
|
||||
|
||||
// Box2D does this at the end of a step and also here when there's a fixture update.
|
||||
// Given external stuff can move bodies we'll just do this here.
|
||||
_broadphase.FindNewContacts();
|
||||
// Find new contacts and (TODO: temporary) update any per-map virtual controllers
|
||||
while (contactEnumerator.MoveNext(out var comp, out var xform))
|
||||
{
|
||||
// Box2D does this at the end of a step and also here when there's a fixture update.
|
||||
// Given external stuff can move bodies we'll just do this here.
|
||||
_broadphase.FindNewContacts(comp, xform.MapID);
|
||||
|
||||
var updateMapBeforeSolve = new PhysicsUpdateBeforeMapSolveEvent(prediction, comp, frameTime);
|
||||
RaiseLocalEvent(ref updateMapBeforeSolve);
|
||||
}
|
||||
|
||||
// TODO PHYSICS Fix Collision Mispredicts
|
||||
// If a physics update induces a position update that brings fixtures into contact, the collision starts in the NEXT tick,
|
||||
@@ -299,8 +350,12 @@ namespace Robust.Shared.Physics.Systems
|
||||
// But that might be unnecessarily expensive for what are hopefully only infrequent mispredicts.
|
||||
|
||||
CollideContacts();
|
||||
var enumerator = AllEntityQuery<PhysicsMapComponent>();
|
||||
|
||||
Step(frameTime, prediction);
|
||||
while (enumerator.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
Step(uid, comp, frameTime, prediction);
|
||||
}
|
||||
|
||||
var updateAfterSolve = new PhysicsUpdateAfterSolveEvent(prediction, frameTime);
|
||||
RaiseLocalEvent(ref updateAfterSolve);
|
||||
@@ -308,7 +363,12 @@ namespace Robust.Shared.Physics.Systems
|
||||
// On last substep (or main step where no substeps occured) we'll update all of the lerp data.
|
||||
if (i == _substeps - 1)
|
||||
{
|
||||
FinalStep();
|
||||
enumerator = AllEntityQuery<PhysicsMapComponent>();
|
||||
|
||||
while (enumerator.MoveNext(out var comp))
|
||||
{
|
||||
FinalStep(comp);
|
||||
}
|
||||
}
|
||||
|
||||
EffectiveCurTime = EffectiveCurTime.Value + TimeSpan.FromSeconds(frameTime);
|
||||
@@ -317,7 +377,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
EffectiveCurTime = null;
|
||||
}
|
||||
|
||||
protected virtual void FinalStep()
|
||||
protected virtual void FinalStep(PhysicsMapComponent component)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -170,9 +170,9 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
[Obsolete("Pass in IComponentFactory")]
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component)
|
||||
where T : IComponent, new()
|
||||
where T : IComponent
|
||||
{
|
||||
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName<T>();
|
||||
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T));
|
||||
return TryGetComponent(compName, out component);
|
||||
}
|
||||
|
||||
@@ -182,9 +182,9 @@ namespace Robust.Shared.Prototypes
|
||||
return TryGetComponent(compName, out component);
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent, new()
|
||||
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent
|
||||
{
|
||||
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName<T>(), name);
|
||||
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T)), name);
|
||||
|
||||
if (!Components.TryGetValue(name, out var componentUnCast))
|
||||
{
|
||||
|
||||
@@ -45,11 +45,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
|
||||
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<PrototypeFlags<T>>? instanceProvider = null)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
|
||||
}
|
||||
if(instanceProvider != null)
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
|
||||
|
||||
var flags = new List<string>(node.Sequence.Count);
|
||||
|
||||
@@ -81,11 +78,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
|
||||
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<PrototypeFlags<T>>? instanceProvider = null)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
|
||||
}
|
||||
if(instanceProvider != null)
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
|
||||
|
||||
return new PrototypeFlags<T>(node.Value);
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@ public sealed class DictionarySerializer<TKey, TValue> :
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning(
|
||||
Logger.Warning(
|
||||
$"Provided value to a Read-call for a {nameof(FrozenDictionary<TKey, TValue>)}. Ignoring...");
|
||||
}
|
||||
|
||||
@@ -181,8 +180,7 @@ public sealed class DictionarySerializer<TKey, TValue> :
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning(
|
||||
Logger.Warning(
|
||||
$"Provided value to a Read-call for a {nameof(IReadOnlyDictionary<TKey, TValue>)}. Ignoring...");
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
|
||||
SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<FrozenSet<T>>? instanceProvider = null)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(FrozenSet<T>)}. Ignoring...");
|
||||
}
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(FrozenSet<T>)}. Ignoring...");
|
||||
|
||||
var array = new T[node.Sequence.Count];
|
||||
var i = 0;
|
||||
@@ -68,10 +65,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
|
||||
ISerializationManager.InstantiationDelegate<ImmutableHashSet<T>>? instanceProvider)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(ImmutableHashSet<T>)}. Ignoring...");
|
||||
}
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(ImmutableHashSet<T>)}. Ignoring...");
|
||||
var set = ImmutableHashSet.CreateBuilder<T>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
|
||||
@@ -127,11 +127,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
|
||||
SerializationHookContext hookCtx, ISerializationContext? context,
|
||||
ISerializationManager.InstantiationDelegate<IReadOnlyList<T>>? instanceProvider)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(IReadOnlySet<T>)}. Ignoring...");
|
||||
}
|
||||
if(instanceProvider != null)
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(IReadOnlySet<T>)}. Ignoring...");
|
||||
|
||||
var list = new List<T>();
|
||||
|
||||
@@ -149,11 +146,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
|
||||
SerializationHookContext hookCtx, ISerializationContext? context,
|
||||
ISerializationManager.InstantiationDelegate<IReadOnlyCollection<T>>? instanceProvider)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(IReadOnlyCollection<T>)}. Ignoring...");
|
||||
}
|
||||
if(instanceProvider != null)
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(IReadOnlyCollection<T>)}. Ignoring...");
|
||||
|
||||
var list = new List<T>();
|
||||
|
||||
@@ -171,11 +165,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
|
||||
SerializationHookContext hookCtx, ISerializationContext? context,
|
||||
ISerializationManager.InstantiationDelegate<ImmutableList<T>>? instanceProvider)
|
||||
{
|
||||
if (instanceProvider != null)
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
|
||||
sawmill.Warning($"Provided value to a Read-call for a {nameof(ImmutableList<T>)}. Ignoring...");
|
||||
}
|
||||
if(instanceProvider != null)
|
||||
Logger.Warning($"Provided value to a Read-call for a {nameof(ImmutableList<T>)}. Ignoring...");
|
||||
|
||||
var list = ImmutableList.CreateBuilder<T>();
|
||||
|
||||
|
||||
@@ -46,9 +46,8 @@ public sealed class PipedArgumentAttribute : Attribute;
|
||||
[MeansImplicitUse]
|
||||
public sealed class CommandArgumentAttribute : Attribute
|
||||
{
|
||||
public CommandArgumentAttribute(Type? customParser = null, bool unparseable = false)
|
||||
public CommandArgumentAttribute(Type? customParser = null)
|
||||
{
|
||||
Unparseable = unparseable;
|
||||
if (customParser == null)
|
||||
return;
|
||||
|
||||
@@ -57,13 +56,6 @@ public sealed class CommandArgumentAttribute : Attribute
|
||||
$"Custom parser {customParser.PrettyName()} does not inherit from {typeof(CustomTypeParser<>).PrettyName()}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command initialization will validate that all of a command's arguments are parseable (i.e., have a type parser).
|
||||
/// In some niche situations you might want to have a command that accepts unparseable arguments that must be
|
||||
/// supplied via a toolshed variable or command block, in which case the check can be disabled via this property.
|
||||
/// </summary>
|
||||
public bool Unparseable { get; }
|
||||
|
||||
public Type? CustomParser { get; }
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,8 @@ internal sealed class TpCommand : ToolshedCommand
|
||||
{
|
||||
private SharedTransformSystem? _xform;
|
||||
|
||||
// TODO TOOLSHED
|
||||
// add EntityCoordinates parser
|
||||
|
||||
[CommandImplementation("coords")]
|
||||
public EntityUid TpCoords([PipedArgument] EntityUid teleporter, [CommandArgument(unparseable:true)] EntityCoordinates target)
|
||||
public EntityUid TpCoords([PipedArgument] EntityUid teleporter, EntityCoordinates target)
|
||||
{
|
||||
_xform ??= GetSys<SharedTransformSystem>();
|
||||
_xform.SetCoordinates(teleporter, target);
|
||||
@@ -23,7 +20,7 @@ internal sealed class TpCommand : ToolshedCommand
|
||||
}
|
||||
|
||||
[CommandImplementation("coords")]
|
||||
public IEnumerable<EntityUid> TpCoords([PipedArgument] IEnumerable<EntityUid> teleporters, [CommandArgument(unparseable:true)] EntityCoordinates target)
|
||||
public IEnumerable<EntityUid> TpCoords([PipedArgument] IEnumerable<EntityUid> teleporters, EntityCoordinates target)
|
||||
=> teleporters.Select(x => TpCoords(x, target));
|
||||
|
||||
[CommandImplementation("to")]
|
||||
|
||||
@@ -124,12 +124,12 @@ public abstract partial class ToolshedCommand
|
||||
{
|
||||
var hasAnyAttribute = false;
|
||||
|
||||
if (param.GetCustomAttribute<CommandArgumentAttribute>() is {} cmdAttr)
|
||||
if (param.HasCustomAttribute<CommandArgumentAttribute>())
|
||||
{
|
||||
if (param.Name == null || !argNames.Add(param.Name))
|
||||
throw new InvalidCommandImplementation($"Command arguments must have a unique name");
|
||||
hasAnyAttribute = true;
|
||||
ValidateArg(param, cmdAttr);
|
||||
ValidateArg(param);
|
||||
}
|
||||
|
||||
if (param.HasCustomAttribute<PipedArgumentAttribute>())
|
||||
@@ -232,18 +232,8 @@ public abstract partial class ToolshedCommand
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateArg(ParameterInfo arg, CommandArgumentAttribute? cmdAttr = null)
|
||||
private void ValidateArg(ParameterInfo arg)
|
||||
{
|
||||
if (cmdAttr == null || cmdAttr.CustomParser == null && !cmdAttr.Unparseable)
|
||||
{
|
||||
// This checks that each argument has a corresponding type parser, as people have sometimes created a command
|
||||
// without realising that the type is unparseable.
|
||||
var t = Nullable.GetUnderlyingType(arg.ParameterType) ?? arg.ParameterType;
|
||||
var ignore = t.IsGenericType || t.IsArray || t.ContainsGenericParameters;
|
||||
if (!ignore && Toolshed.GetParserForType(t) == null)
|
||||
throw new InvalidCommandImplementation($"{Name} command argument of type {t.PrettyName()} has no type parser. You either need to add a type parser or explicitly mark the argument as unparseable.");
|
||||
}
|
||||
|
||||
var isParams = arg.HasCustomAttribute<ParamArrayAttribute>();
|
||||
if (!isParams)
|
||||
return;
|
||||
|
||||
@@ -231,10 +231,7 @@ public sealed partial class ToolshedManager
|
||||
/// <returns>Success.</returns>
|
||||
public bool TryParse<T>(ParserContext parserContext, [NotNullWhen(true)] out T? parsed)
|
||||
{
|
||||
var t = typeof(T);
|
||||
t = Nullable.GetUnderlyingType(t) ?? t;
|
||||
var res = TryParse(parserContext, t, out var p);
|
||||
|
||||
var res = TryParse(parserContext, typeof(T), out var p);
|
||||
if (p is not null)
|
||||
parsed = (T?) p;
|
||||
else
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract class SpanLikeTypeParser<T, TElem> : TypeParser<T>
|
||||
|
||||
public override CompletionResult? TryAutocomplete(ParserContext parserContext, CommandArgument? arg)
|
||||
{
|
||||
return CompletionResult.FromHint(GetArgHint(arg));
|
||||
return CompletionResult.FromHint(typeof(T).PrettyName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
using AppearanceSystem = Robust.Client.GameObjects.AppearanceSystem;
|
||||
using InputSystem = Robust.Server.GameObjects.InputSystem;
|
||||
using MapSystem = Robust.Server.GameObjects.MapSystem;
|
||||
using PointLightComponent = Robust.Client.GameObjects.PointLightComponent;
|
||||
@@ -54,10 +53,12 @@ namespace Robust.UnitTesting
|
||||
typeof(MetaDataComponent),
|
||||
typeof(TransformComponent),
|
||||
typeof(PhysicsComponent),
|
||||
typeof(PhysicsMapComponent),
|
||||
typeof(BroadphaseComponent),
|
||||
typeof(FixturesComponent),
|
||||
typeof(JointComponent),
|
||||
typeof(GridTreeComponent),
|
||||
typeof(MovedGridsComponent),
|
||||
typeof(JointRelayTargetComponent),
|
||||
typeof(OccluderComponent),
|
||||
typeof(OccluderTreeComponent),
|
||||
@@ -65,6 +66,7 @@ namespace Robust.UnitTesting
|
||||
typeof(LightTreeComponent),
|
||||
typeof(CollisionWakeComponent),
|
||||
typeof(CollideOnAnchorComponent),
|
||||
typeof(Gravity2DComponent),
|
||||
typeof(ActorComponent)
|
||||
};
|
||||
|
||||
@@ -123,6 +125,7 @@ namespace Robust.UnitTesting
|
||||
|
||||
systems.LoadExtraSystemType<SharedGridTraversalSystem>();
|
||||
systems.LoadExtraSystemType<FixtureSystem>();
|
||||
systems.LoadExtraSystemType<Gravity2DController>();
|
||||
systems.LoadExtraSystemType<CollisionWakeSystem>();
|
||||
|
||||
if (Project == UnitTestProject.Client)
|
||||
@@ -142,7 +145,6 @@ namespace Robust.UnitTesting
|
||||
systems.LoadExtraSystemType<RecursiveMoveSystem>();
|
||||
systems.LoadExtraSystemType<SpriteSystem>();
|
||||
systems.LoadExtraSystemType<SpriteTreeSystem>();
|
||||
systems.LoadExtraSystemType<AppearanceSystem>();
|
||||
systems.LoadExtraSystemType<GridChunkBoundsDebugSystem>();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -136,39 +136,35 @@ public sealed class PvsPauseTest : RobustIntegrationTest
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: false, clientPaused: true);
|
||||
|
||||
// Unpause the entity while out of range
|
||||
{
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: true, clientPaused: true);
|
||||
// Unpause the entity while out of range
|
||||
{
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: true, clientPaused: true);
|
||||
|
||||
await server.WaitPost(() => metaSys.SetEntityPaused(sEnt, false));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: false, detached: true, clientPaused: true);
|
||||
await server.WaitPost(() => metaSys.SetEntityPaused(sEnt, false));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: false, detached: true, clientPaused: true);
|
||||
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: false, detached: false, clientPaused: false);
|
||||
}
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: false, detached: false, clientPaused: false);
|
||||
}
|
||||
|
||||
// Pause the entity while out of range
|
||||
{
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: false, detached: true, clientPaused: true);
|
||||
// Pause the entity while out of range
|
||||
{
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: false, detached: true, clientPaused: true);
|
||||
|
||||
await server.WaitPost(() => metaSys.SetEntityPaused(sEnt, true));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: true, clientPaused: true);
|
||||
await server.WaitPost(() => metaSys.SetEntityPaused(sEnt, true));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: true, clientPaused: true);
|
||||
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: false, clientPaused: true);
|
||||
}
|
||||
|
||||
await client.WaitPost(() => netMan.ClientDisconnect(""));
|
||||
await server.WaitRunTicks(5);
|
||||
await client.WaitRunTicks(5);
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
|
||||
await RunTicks();
|
||||
AssertEnt(paused: true, detached: false, clientPaused: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.UnitTesting.Server.GameStates;
|
||||
|
||||
public sealed class PvsResetTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that the client doesn't reset dirty detached entities. They should remain in nullspace.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ResetTest()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
|
||||
var sEntMan = server.EntMan;
|
||||
var confMan = server.CfgMan;
|
||||
var sPlayerMan = server.PlayerMan;
|
||||
var xforms = sEntMan.System<SharedTransformSystem>();
|
||||
|
||||
var cEntMan = client.EntMan;
|
||||
var cPlayerMan = client.PlayerMan;
|
||||
var netMan = client.ResolveDependency<IClientNetManager>();
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
||||
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
|
||||
|
||||
async Task RunTicks()
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
}
|
||||
|
||||
await RunTicks();
|
||||
|
||||
// Set up map and spawn player
|
||||
EntityUid sMap = default;
|
||||
EntityUid playerUid = default;
|
||||
EntityUid sEnt = default;
|
||||
EntityCoordinates coords = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
sMap = server.System<SharedMapSystem>().CreateMap();
|
||||
coords = new(sMap, default);
|
||||
|
||||
playerUid = sEntMan.SpawnEntity(null, coords);
|
||||
sEnt = sEntMan.SpawnEntity(null, coords);
|
||||
// Attach player.
|
||||
var session = sPlayerMan.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(session, playerUid);
|
||||
sPlayerMan.JoinGame(session);
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
var farAway = new EntityCoordinates(sMap, new Vector2(100, 100));
|
||||
var netEnt = sEntMan.GetNetEntity(sEnt);
|
||||
var player = sEntMan.GetNetEntity(playerUid);
|
||||
Assert.That(player, Is.Not.EqualTo(NetEntity.Invalid));
|
||||
Assert.That(netEnt, Is.Not.EqualTo(NetEntity.Invalid));
|
||||
|
||||
// Check player got properly attached, and has received the other entity.
|
||||
Assert.That(cEntMan.TryGetEntity(netEnt, out var uid));
|
||||
Assert.That(cEntMan.TryGetEntity(player, out var cPlayerUid));
|
||||
var cEnt = uid!.Value;
|
||||
Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(cPlayerUid));
|
||||
var cMap = cEntMan.GetEntity(sEntMan.GetNetEntity(sMap));
|
||||
|
||||
void AssertDetached(bool detached)
|
||||
{
|
||||
var cXform = client.Transform(cEnt);
|
||||
var sXform = server.Transform(sEnt);
|
||||
var meta = client.MetaData(cEnt);
|
||||
|
||||
Assert.That(sXform.MapUid, Is.EqualTo(sMap));
|
||||
Assert.That(sXform.ParentUid, Is.EqualTo(sMap));
|
||||
|
||||
if (detached)
|
||||
{
|
||||
Assert.That(meta.Flags.HasFlag(MetaDataFlags.Detached));
|
||||
Assert.That(cXform.MapUid, Is.Null);
|
||||
Assert.That(cXform.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(!meta.Flags.HasFlag(MetaDataFlags.Detached));
|
||||
Assert.That(cXform.MapUid, Is.EqualTo(cMap));
|
||||
Assert.That(cXform.ParentUid, Is.EqualTo(cMap));
|
||||
}
|
||||
}
|
||||
|
||||
// Entity is initially in view
|
||||
AssertDetached(false);
|
||||
|
||||
// Move the player out of the entity's PVS range
|
||||
await server.WaitPost(() => xforms.SetCoordinates(playerUid, farAway));
|
||||
await RunTicks();
|
||||
|
||||
// Client should now have detached the entity, moving it into nullspace
|
||||
AssertDetached(true);
|
||||
|
||||
// Marking the entity as dirty due to client-side prediction should have effect
|
||||
await client.WaitPost(() => client.EntMan.Dirty(cEnt, client.Transform(cEnt)));
|
||||
await RunTicks();
|
||||
AssertDetached(true);
|
||||
|
||||
// Move the player back into range
|
||||
await server.WaitPost( () => xforms.SetCoordinates(playerUid, coords));
|
||||
await RunTicks();
|
||||
AssertDetached(false);
|
||||
|
||||
// Marking the entity as dirty due to client-side prediction should have no real effect
|
||||
await client.WaitPost(() => client.EntMan.Dirty(cEnt, client.Transform(cEnt)));
|
||||
await RunTicks();
|
||||
AssertDetached(false);
|
||||
|
||||
await client.WaitPost(() => netMan.ClientDisconnect(""));
|
||||
await server.WaitRunTicks(5);
|
||||
await client.WaitRunTicks(5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,13 +299,16 @@ namespace Robust.UnitTesting.Server
|
||||
compFactory.RegisterClass<JointComponent>();
|
||||
compFactory.RegisterClass<EyeComponent>();
|
||||
compFactory.RegisterClass<GridTreeComponent>();
|
||||
compFactory.RegisterClass<MovedGridsComponent>();
|
||||
compFactory.RegisterClass<JointRelayTargetComponent>();
|
||||
compFactory.RegisterClass<BroadphaseComponent>();
|
||||
compFactory.RegisterClass<ContainerManagerComponent>();
|
||||
compFactory.RegisterClass<PhysicsMapComponent>();
|
||||
compFactory.RegisterClass<FixturesComponent>();
|
||||
compFactory.RegisterClass<CollisionWakeComponent>();
|
||||
compFactory.RegisterClass<OccluderComponent>();
|
||||
compFactory.RegisterClass<OccluderTreeComponent>();
|
||||
compFactory.RegisterClass<Gravity2DComponent>();
|
||||
compFactory.RegisterClass<CollideOnAnchorComponent>();
|
||||
compFactory.RegisterClass<ActorComponent>();
|
||||
|
||||
@@ -319,6 +322,7 @@ namespace Robust.UnitTesting.Server
|
||||
var entitySystemMan = container.Resolve<IEntitySystemManager>();
|
||||
|
||||
entitySystemMan.LoadExtraSystemType<PhysicsSystem>();
|
||||
entitySystemMan.LoadExtraSystemType<Gravity2DController>();
|
||||
entitySystemMan.LoadExtraSystemType<SharedGridTraversalSystem>();
|
||||
entitySystemMan.LoadExtraSystemType<ContainerSystem>();
|
||||
entitySystemMan.LoadExtraSystemType<JointSystem>();
|
||||
|
||||
@@ -214,7 +214,9 @@ entities:
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapInitialized: True
|
||||
- type: PhysicsMap
|
||||
- type: GridTree
|
||||
- type: MovedGrids
|
||||
- type: Broadphase
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
@@ -269,7 +271,9 @@ entities:
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapPaused: True
|
||||
- type: PhysicsMap
|
||||
- type: GridTree
|
||||
- type: MovedGrids
|
||||
- type: Broadphase
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
@@ -292,7 +296,9 @@ entities:
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapInitialized: True
|
||||
- type: PhysicsMap
|
||||
- type: GridTree
|
||||
- type: MovedGrids
|
||||
- type: Broadphase
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
|
||||
@@ -33,8 +33,8 @@ public sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
|
||||
var server = StartServer(options);
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
EntityUid uid1 = default, uid2 = default, uid3 = default, uid4 = default;
|
||||
DeferredDeletionTestComponent comp1 = default!, comp2 = default!, comp3 = default!, comp4 = default!;
|
||||
EntityUid uid1 = default, uid2 = default, uid3 = default;
|
||||
DeferredDeletionTestComponent comp1 = default!, comp2 = default!, comp3 = default!;
|
||||
IEntityManager entMan = default!;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
@@ -46,17 +46,14 @@ public sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
|
||||
uid1 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
uid2 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
uid3 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
uid4 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
|
||||
comp1 = entMan.AddComponent<DeferredDeletionTestComponent>(uid1);
|
||||
comp2 = entMan.AddComponent<DeferredDeletionTestComponent>(uid2);
|
||||
comp3 = entMan.AddComponent<DeferredDeletionTestComponent>(uid3);
|
||||
comp4 = entMan.AddComponent<DeferredDeletionTestComponent>(uid4);
|
||||
comp3 =entMan.AddComponent<DeferredDeletionTestComponent>(uid3);
|
||||
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid1);
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid2);
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid3);
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid4);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
@@ -79,23 +76,17 @@ public sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
|
||||
var ev = new DeferredDeletionTestEvent();
|
||||
entMan.EventBus.RaiseLocalEvent(uid2, ev);
|
||||
entMan.EventBus.RaiseLocalEvent(uid3, ev);
|
||||
entMan.EventBus.RaiseLocalEvent(uid4, ev);
|
||||
entMan.DeleteEntity(uid2);
|
||||
entMan.QueueDeleteEntity(uid3);
|
||||
entMan.TryQueueDeleteEntity(uid4);
|
||||
Assert.That(entMan.Deleted(uid2));
|
||||
Assert.That(!entMan.Deleted(uid3));
|
||||
Assert.That(!entMan.Deleted(uid4));
|
||||
Assert.That(comp2.LifeStage == ComponentLifeStage.Deleted);
|
||||
Assert.That(comp3.LifeStage == ComponentLifeStage.Stopped);
|
||||
Assert.That(comp4.LifeStage == ComponentLifeStage.Stopped);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
Assert.That(comp3.LifeStage == ComponentLifeStage.Deleted);
|
||||
Assert.That(comp4.LifeStage == ComponentLifeStage.Deleted);
|
||||
Assert.That(entMan.Deleted(uid3));
|
||||
Assert.That(entMan.Deleted(uid4));
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +133,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
compInstance.Owner = entUid;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
var compRegistration = new ComponentRegistration(
|
||||
"MetaData",
|
||||
|
||||
@@ -248,15 +248,12 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
var gridEnt = grid.Owner;
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, entPos));
|
||||
var newXform = entityManager.GetComponent<TransformComponent>(newEnt);
|
||||
|
||||
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates), Is.EqualTo(new MapCoordinates(entPos, mapId)));
|
||||
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates).Position, Is.EqualTo(xformSys.ToWorldPosition(newXform.Coordinates)));
|
||||
Assert.That(xformSys.ToMapCoordinates(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(new MapCoordinates(entPos, mapId)));
|
||||
|
||||
xformSys.SetLocalPosition(gridEnt, entityManager.GetComponent<TransformComponent>(gridEnt).LocalPosition + gridPos);
|
||||
|
||||
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId)));
|
||||
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates).Position, Is.EqualTo(xformSys.ToWorldPosition(newXform.Coordinates)));
|
||||
Assert.That(xformSys.ToMapCoordinates(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -22,16 +21,12 @@ public sealed class MapGridMap_Tests
|
||||
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
var mapId = sim.CreateMap().MapId;
|
||||
List<Entity<MapGridComponent>> grids = [];
|
||||
mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered, ref grids);
|
||||
Assert.That(grids, Is.Empty);
|
||||
Assert.That(!mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered).Any());
|
||||
|
||||
entManager.AddComponent<MapGridComponent>(mapSystem.GetMapOrInvalid(mapId));
|
||||
mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered, ref grids);
|
||||
Assert.That(grids, Has.Count.EqualTo(1));
|
||||
entManager.AddComponent<MapGridComponent>(mapManager.GetMapEntityId(mapId));
|
||||
Assert.That(mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered).Count() == 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,7 +46,7 @@ public sealed class MapGridMap_Tests
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
entManager.AddComponent<MapGridComponent>(mapSystem.GetMapOrInvalid(mapId));
|
||||
entManager.AddComponent<MapGridComponent>(mapManager.GetMapEntityId(mapId));
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
});
|
||||
|
||||
|
||||
@@ -27,14 +27,12 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapSys = entMan.System<SharedMapSystem>();
|
||||
|
||||
var mapID = sim.CreateMap().MapId;
|
||||
|
||||
mapMan.Restart();
|
||||
|
||||
Assert.That(mapSys.MapExists(mapID), Is.False);
|
||||
Assert.That(mapMan.MapExists(mapID), Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +62,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var oldEntity = entMan.CreateEntityUninitialized(null, MapCoordinates.Nullspace);
|
||||
entMan.InitializeEntity(oldEntity);
|
||||
entMan.InitializeComponents(oldEntity);
|
||||
entMan.FlushEntities();
|
||||
Assert.That(entMan.Deleted(oldEntity), Is.True);
|
||||
}
|
||||
|
||||
@@ -150,17 +150,19 @@ internal sealed class MapPauseTests
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var map1 = sim.CreateMap().Uid;
|
||||
entMan.System<SharedMapSystem>().SetPaused(map1, true);
|
||||
var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(map1, default));
|
||||
var xform = entMan.GetComponent<TransformComponent>(newEnt);
|
||||
|
||||
var map2 = sim.CreateMap().Uid;
|
||||
entMan.System<SharedMapSystem>().SetPaused(map2, false);
|
||||
|
||||
// Act
|
||||
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(newEnt, map2);
|
||||
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(xform.Owner, map2);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.False);
|
||||
@@ -175,17 +177,19 @@ internal sealed class MapPauseTests
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var map1 = sim.CreateMap().Uid;
|
||||
entMan.System<SharedMapSystem>().SetPaused(map1, false);
|
||||
var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(map1, default));
|
||||
var xform = entMan.GetComponent<TransformComponent>(newEnt);
|
||||
|
||||
var map2 = sim.CreateMap().Uid;
|
||||
entMan.System<SharedMapSystem>().SetPaused(map2, true);
|
||||
|
||||
// Act
|
||||
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(newEnt, map2);
|
||||
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(xform.Owner, map2);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.True);
|
||||
|
||||
@@ -396,6 +396,17 @@ namespace Robust.UnitTesting.Shared.Maths
|
||||
Assert.That(MathHelper.CloseToPercent(color, controlColor));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToFromHcy([ValueSource(nameof(FourFloatsSource))] (float, float, float, float) floats)
|
||||
{
|
||||
var (rf, gf, bf, af) = floats;
|
||||
|
||||
var controlColor = new Color(rf, gf, bf, af);
|
||||
var color = Color.FromHcy(Color.ToHcy(controlColor));
|
||||
|
||||
Assert.That(MathHelper.CloseToPercent(color, controlColor));
|
||||
}
|
||||
|
||||
static IEnumerable<float> InterpolationValues => new float[]
|
||||
{
|
||||
0f,
|
||||
|
||||
@@ -41,7 +41,7 @@ internal sealed class NetDisconnectMessageTest
|
||||
};
|
||||
|
||||
var encoded = value.Encode();
|
||||
TestContext.Out.Write($"Encoded: {encoded}\n");
|
||||
TestContext.Write($"Encoded: {encoded}\n");
|
||||
var decodedAgain = NetDisconnectMessage.Decode(encoded);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user