mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cc2a17444 | ||
|
|
9c64fbfce2 | ||
|
|
0218c4b969 | ||
|
|
cd72523701 | ||
|
|
76bb9b4b19 | ||
|
|
afdfbba312 | ||
|
|
8b4925863e | ||
|
|
e1597da4c7 | ||
|
|
8375a4038b | ||
|
|
8270442d66 | ||
|
|
85e1920b95 | ||
|
|
5347eb3350 | ||
|
|
e4a14d1ec8 | ||
|
|
c52db4d3f2 | ||
|
|
89f78d76ab | ||
|
|
bbc4668f9c | ||
|
|
ce4016965e | ||
|
|
21e74c9881 | ||
|
|
aa52e8c2ef | ||
|
|
e106d3f72b | ||
|
|
8eae802fb6 | ||
|
|
681feaf0c7 | ||
|
|
0a5a214a06 | ||
|
|
d80be16f6c | ||
|
|
d967bc9fdc | ||
|
|
177ca6b627 | ||
|
|
3c262afaa4 | ||
|
|
bce2901b0f | ||
|
|
c392d4f996 | ||
|
|
d7ee2bccd7 | ||
|
|
4e816fa5e7 | ||
|
|
545e55055e | ||
|
|
6b059ed356 | ||
|
|
b69b4fd8fe | ||
|
|
cb63499ec9 | ||
|
|
dd12110c34 | ||
|
|
a811cfc1a1 | ||
|
|
229a45bea2 | ||
|
|
78376ccca1 | ||
|
|
e4a1415627 | ||
|
|
69589195e0 | ||
|
|
ce3b92aea2 | ||
|
|
5dc980ae92 |
@@ -23,7 +23,7 @@
|
||||
<PropertyGroup Condition="'$(FullRelease)' != 'True'">
|
||||
<DefineConstants>$(DefineConstants);DEVELOPMENT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release' Or '$(Configuration)' == 'Tools'">
|
||||
<DefineConstants>$(DefineConstants);EXCEPTION_TOLERANCE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(EnableClientScripting)' == 'True'">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
119
RELEASE-NOTES.md
119
RELEASE-NOTES.md
@@ -54,6 +54,121 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 141.2.0
|
||||
|
||||
### Other
|
||||
|
||||
* Fix bug in `NetManager` that allowed exception spam through protocol abuse.
|
||||
|
||||
|
||||
## 141.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* MapInitEvent is run clientside for placementmanager entities to predict entity appearances.
|
||||
* Add CollisionLayerChangeEvent for physics fixtures.
|
||||
|
||||
|
||||
## 141.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Component.Initialize has been fully replaced with the Eventbus.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed potential crashes if buffered audio sources (e.g. MIDI) fail to create due to running out of audio streams.
|
||||
|
||||
### Other
|
||||
|
||||
* Pressing `^C` twice on the server will now cause it to hard-exit immediately.
|
||||
* `Tools` now has `EXCEPTION_TOLERANCE` enabled.
|
||||
|
||||
|
||||
## 140.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IReplayRecordingManager.RecordingFinished` now takes a `ReplayRecordingFinished` object as argument.
|
||||
* `IReplayRecordingManager.GetReplayStats` now returns a `ReplayRecordingStats` struct instead of a tuple. The units have also been normalized
|
||||
|
||||
### New features
|
||||
|
||||
* `IReplayRecordingManager` can now track a "state" object for an active recording.
|
||||
* If the path given to `IReplayRecordingManager.TryStartRecording` is rooted, the base replay directory is ignored.
|
||||
|
||||
### Other
|
||||
|
||||
* `IReplayRecordingManager` no longer considers itself recording inside `RecordingFinished`.
|
||||
* `IReplayRecordingManager.Initialize()` was moved to an engine-internal interface.
|
||||
|
||||
|
||||
## 139.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove Component.Startup(), fully replacing it with the Eventbus.
|
||||
|
||||
|
||||
## 138.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add rotation methods to TransformSystem for no lerp.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix AnimationCompleted ordering.
|
||||
|
||||
|
||||
## 138.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Obsoleted unused `IMidiRenderer.VolumeBoost` property. Use `IMidiRenderer.VelocityOverride` instead.
|
||||
* `IMidiRenderer.TrackedCoordinates` is now a `MapCoordinates`.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `Master` property to `IMidiRenderer`, which allows it to copy all MIDI events from another renderer.
|
||||
* Added `FilteredChannels` property to `IMidiRenderer`, which allows it to filter out notes from certain channels.
|
||||
* Added `SystemReset` helper property to `IMidiRenderer`, which allows you to easily send it a SystemReset MIDI message.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed some cases were `MidiRenderer` would not respect the `MidiBank` and `MidiProgram.
|
||||
* Fixed user soundfonts not loading.
|
||||
* Fixed `ItemList` item selection unselecting everything when in `Multiple` mode.
|
||||
|
||||
|
||||
## 137.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added BQL `paused` selector.
|
||||
* `ModUpdateLevel.PostInput` allows running content code after network and async task processing.
|
||||
|
||||
### Other
|
||||
|
||||
* BQL `with` now includes paused entities.
|
||||
* The game loop now times more accurately and avoids sleeping more than necessary.
|
||||
* Sandboxing (and thus, client startup) should be much faster when ran from the launcher.
|
||||
|
||||
|
||||
## 137.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Component network state handler methods have been fully deprecated and replaced with the eventbus event equivalents (ComponentGetState and ComponentHandleState).
|
||||
|
||||
|
||||
## 136.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed debugging on Linux when CEF is enabled.
|
||||
|
||||
|
||||
## 136.0.0
|
||||
|
||||
### New features
|
||||
@@ -62,7 +177,7 @@ END TEMPLATE-->
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed OutputPanel scroll-bar not functioning properly.
|
||||
* Fixed OutputPanel scroll-bar not functioning properly.
|
||||
|
||||
|
||||
## 135.0.0
|
||||
@@ -76,7 +191,7 @@ END TEMPLATE-->
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Several methods were moved out of the `UserInterface` components and into the UI system.
|
||||
* Several methods were moved out of the `UserInterface` components and into the UI system.
|
||||
* The BUI constructor arguments have changed and now require an EntityUid to be given instead of a component.
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ cmd-replay-skip-hint = Ticks or timespan (HH:MM:SS).
|
||||
|
||||
cmd-replay-set-time-desc = Jump forwards or backwards to some specific time.
|
||||
cmd-replay-set-time-help = replay_set <tick or time>
|
||||
cmd-replay-set-time-hint = Tick or timespan (HH:MM:SS), starting from
|
||||
cmd-replay-set-time-hint = Tick or timespan (HH:MM:SS), starting from
|
||||
|
||||
cmd-replay-error-time = "{$time}" is not an integer or timespan.
|
||||
cmd-replay-error-args = Wrong number of arguments.
|
||||
@@ -33,7 +33,7 @@ cmd-replay-error-run-level = You cannot load a replay while connected to a serve
|
||||
# Recording commands
|
||||
|
||||
cmd-replay-recording-start-desc = Starts a replay recording, optionally with some time limit.
|
||||
cmd-replay-recording-start-help = Usage: replay_recording_start [name] [overwrite] [time limit]
|
||||
cmd-replay-recording-start-help = Usage: replay_recording_start [name] [overwrite] [time limit]
|
||||
cmd-replay-recording-start-success = Started recording a replay.
|
||||
cmd-replay-recording-start-already-recording = Already recording a replay.
|
||||
cmd-replay-recording-start-error = An error occurred while trying to start the recording.
|
||||
@@ -48,7 +48,7 @@ cmd-replay-recording-stop-not-recording = Not currently recording a replay.
|
||||
|
||||
cmd-replay-recording-stats-desc = Displays information about the current replay recording.
|
||||
cmd-replay-recording-stats-help = Usage: replay_recording_stats
|
||||
cmd-replay-recording-stats-result = Duration: {$time} min, Ticks: {$ticks}, Size: {$size} mb, rate: {$rate} mb/min.
|
||||
cmd-replay-recording-stats-result = Duration: {$time} min, Ticks: {$ticks}, Size: {$size} MB, rate: {$rate} MB/min.
|
||||
|
||||
|
||||
# Time Control UI
|
||||
@@ -56,4 +56,4 @@ replay-time-box-scrubbing-label = Dynamic Scrubbing
|
||||
replay-time-box-replay-time-label = Recording Time: {$current} / {$end} ({$percentage}%)
|
||||
replay-time-box-server-time-label = Server Time: {$current} / {$end}
|
||||
replay-time-box-index-label = Index: {$current} / {$total}
|
||||
replay-time-box-tick-label = Tick: {$current} / {$total}
|
||||
replay-time-box-tick-label = Tick: {$current} / {$total}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
public class ComponentIteratorBenchmark
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
|
||||
[UsedImplicitly]
|
||||
[Params(1, 10, 100, 1000)]
|
||||
public int N;
|
||||
|
||||
public A[] Comps = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(f => f.RegisterClass<A>())
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
|
||||
Comps = new A[N+2];
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
var uid = _entityManager.SpawnEntity(null, coords);
|
||||
_entityManager.AddComponent<A>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public A[] ComponentStructEnumerator()
|
||||
{
|
||||
var query = _entityManager.EntityQueryEnumerator<A>();
|
||||
var i = 0;
|
||||
|
||||
while (query.MoveNext(out var comp))
|
||||
{
|
||||
Comps[i] = comp;
|
||||
i++;
|
||||
}
|
||||
|
||||
return Comps;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public A[] ComponentIEnumerable()
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
foreach (var comp in _entityManager.EntityQuery<A>())
|
||||
{
|
||||
Comps[i] = comp;
|
||||
i++;
|
||||
}
|
||||
|
||||
return Comps;
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -85,12 +85,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
_app = new RobustCefApp(_sawmill);
|
||||
|
||||
// We pass no main arguments...
|
||||
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
|
||||
|
||||
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
|
||||
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
|
||||
// And nothing seemed to work. Odd.
|
||||
// So these arguments look like nonsense, but it turns out CEF is just *like that*.
|
||||
// The first argument is literally nonsense, but it needs to be there as otherwise the second argument doesn't apply
|
||||
// The second argument turns off CEF's bullshit error handling, which breaks dotnet's error handling.
|
||||
CefRuntime.Initialize(new CefMainArgs(new string[]{"binary","--disable-in-process-stack-traces"}), settings, _app, IntPtr.Zero);
|
||||
|
||||
if (_cfg.GetCVar(WCVars.WebResProtocol))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -15,7 +17,6 @@ public enum MidiRendererStatus : byte
|
||||
|
||||
public interface IMidiRenderer : IDisposable
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The buffered audio source of this renderer.
|
||||
/// </summary>
|
||||
@@ -34,6 +35,7 @@ public interface IMidiRenderer : IDisposable
|
||||
/// <summary>
|
||||
/// This increases all note on velocities to 127.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
bool VolumeBoost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -94,6 +96,27 @@ public interface IMidiRenderer : IDisposable
|
||||
/// </summary>
|
||||
double SequencerTimeScale { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this renderer will subscribe to another and copy its events.
|
||||
/// See <see cref="FilteredChannels"/> to filter specific channels.
|
||||
/// </summary>
|
||||
IMidiRenderer? Master { get; set; }
|
||||
|
||||
// NOTE: Why is the properties below BitArray, you ask?
|
||||
// Well see, MIDI 2.0 supports up to 256(!) channels as opposed to MIDI 1.0's meekly 16 channels...
|
||||
// I'd like us to support MIDI 2.0 one day so I'm just future-proofing here. Also BitArray is cool!
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to filter out note events from certain channels.
|
||||
/// Only NoteOn will be filtered.
|
||||
/// </summary>
|
||||
BitArray FilteredChannels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to override all NoteOn velocities. Set to null to disable.
|
||||
/// </summary>
|
||||
byte? VelocityOverride { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start listening for midi input.
|
||||
/// </summary>
|
||||
@@ -120,6 +143,11 @@ public interface IMidiRenderer : IDisposable
|
||||
/// </summary>
|
||||
void StopAllNotes();
|
||||
|
||||
/// <summary>
|
||||
/// Reset renderer back to a clean state.
|
||||
/// </summary>
|
||||
void SystemReset();
|
||||
|
||||
/// <summary>
|
||||
/// Clears all scheduled events.
|
||||
/// </summary>
|
||||
@@ -156,7 +184,7 @@ public interface IMidiRenderer : IDisposable
|
||||
/// This is only used if <see cref="Mono"/> is set to True
|
||||
/// and <see cref="TrackingEntity"/> is null.
|
||||
/// </summary>
|
||||
EntityCoordinates? TrackingCoordinates { get; set; }
|
||||
MapCoordinates? TrackingCoordinates { get; set; }
|
||||
|
||||
MidiRendererState RendererState { get; }
|
||||
|
||||
@@ -164,7 +192,8 @@ public interface IMidiRenderer : IDisposable
|
||||
/// Send a midi event for the renderer to play.
|
||||
/// </summary>
|
||||
/// <param name="midiEvent">The midi event to be played</param>
|
||||
void SendMidiEvent(RobustMidiEvent midiEvent);
|
||||
/// <param name="raiseEvent">Whether to raise an event for this event.</param>
|
||||
void SendMidiEvent(RobustMidiEvent midiEvent, bool raiseEvent = true);
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a MIDI event to be played at a later time.
|
||||
@@ -177,7 +206,7 @@ public interface IMidiRenderer : IDisposable
|
||||
/// <summary>
|
||||
/// Apply a certain state to the renderer.
|
||||
/// </summary>
|
||||
void ApplyState(MidiRendererState state);
|
||||
void ApplyState(MidiRendererState state, bool filterChannels = false);
|
||||
|
||||
/// <summary>
|
||||
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
|
||||
|
||||
@@ -3,20 +3,27 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -26,6 +33,13 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
public const string SoundfontEnvironmentVariable = "ROBUST_SOUNDFONT_OVERRIDE";
|
||||
|
||||
private int _minRendererParallel;
|
||||
private float _occlusionUpdateDelay;
|
||||
private float _positionUpdateDelay;
|
||||
|
||||
[ViewVariables] private TimeSpan _nextOcclusionUpdate = TimeSpan.Zero;
|
||||
[ViewVariables] private TimeSpan _nextPositionUpdate = TimeSpan.Zero;
|
||||
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceCacheInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -33,6 +47,9 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly ILogManager _logger = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtime = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
|
||||
@@ -59,11 +76,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
private readonly List<IMidiRenderer> _renderers = new();
|
||||
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
private bool _alive = true;
|
||||
private Settings? _settings;
|
||||
[ViewVariables] private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
@@ -115,7 +131,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
private bool _failedInitialize;
|
||||
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
private ISawmill _fluidsynthSawmill = default!;
|
||||
private float _maxCastLength;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -130,20 +146,28 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiMinRendererParallel,
|
||||
value => _minRendererParallel = value, true);
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiOcclusionUpdateDelay,
|
||||
value => _occlusionUpdateDelay = value, true);
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiPositionUpdateDelay,
|
||||
value => _positionUpdateDelay = value, true);
|
||||
|
||||
_midiSawmill = _logger.GetSawmill("midi");
|
||||
#if DEBUG
|
||||
_midiSawmill.Level = LogLevel.Debug;
|
||||
#else
|
||||
_midiSawmill.Level = LogLevel.Error;
|
||||
#endif
|
||||
_sawmill = _logger.GetSawmill("midi.fluidsynth");
|
||||
_fluidsynthSawmill = _logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
|
||||
if (!_resourceManager.UserData.Exists(CustomSoundfontDirectory))
|
||||
@@ -167,8 +191,6 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_settings["synth.lock-memory"].IntValue = 0;
|
||||
_settings["synth.threadsafe-api"].IntValue = 1;
|
||||
_settings["synth.gain"].DoubleValue = 1.0d;
|
||||
_settings["synth.polyphony"].IntValue = 1024;
|
||||
_settings["synth.cpu-cores"].IntValue = 2;
|
||||
_settings["synth.midi-channels"].IntValue = 16;
|
||||
_settings["synth.overflow.age"].DoubleValue = 3000;
|
||||
_settings["audio.driver"].StringValue = "file";
|
||||
@@ -176,8 +198,11 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_settings["audio.period-size"].IntValue = 4096;
|
||||
_settings["midi.autoconnect"].IntValue = 1;
|
||||
_settings["player.reset-synth"].IntValue = 0;
|
||||
_settings["synth.midi-channels"].IntValue = Math.Clamp(RobustMidiEvent.MaxChannels, 16, 256);
|
||||
_settings["synth.midi-bank-select"].StringValue = "gm";
|
||||
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
|
||||
|
||||
_parallel.AddAndInvokeParallelCountChanged(UpdateParallelCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -195,6 +220,18 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
private void UpdateParallelCount()
|
||||
{
|
||||
if (_settings == null)
|
||||
return;
|
||||
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(_parallel.ParallelProcessCount) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(_parallel.ParallelProcessCount, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
|
||||
private void OnRaycastLengthChanged(float value)
|
||||
{
|
||||
_maxCastLength = value;
|
||||
@@ -211,7 +248,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
|
||||
_ => LogLevel.Debug
|
||||
};
|
||||
_sawmill.Log(rLevel, message);
|
||||
_fluidsynthSawmill.Log(rLevel, message);
|
||||
}
|
||||
|
||||
public IMidiRenderer? GetNewRenderer(bool mono = true)
|
||||
@@ -238,7 +275,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
|
||||
|
||||
_midiSawmill.Debug($"Loading soundfont {FallbackSoundfont}");
|
||||
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
@@ -252,8 +289,8 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
try
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
|
||||
renderer.LoadSoundfont(filepath);
|
||||
_midiSawmill.Debug($"Loaded Linux soundfont {filepath}");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -267,7 +304,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading soundfont {OsxSoundfont}");
|
||||
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
|
||||
renderer.LoadSoundfont(OsxSoundfont);
|
||||
}
|
||||
}
|
||||
@@ -275,7 +312,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading soundfont {WindowsSoundfont}");
|
||||
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
|
||||
renderer.LoadSoundfont(WindowsSoundfont);
|
||||
}
|
||||
}
|
||||
@@ -286,27 +323,31 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading soundfont {soundfontOverride} from environment variable.");
|
||||
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
|
||||
renderer.LoadSoundfont(soundfontOverride);
|
||||
}
|
||||
}
|
||||
|
||||
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
|
||||
_midiSawmill.Debug($"Loading soundfonts from {ContentCustomSoundfontDirectory}");
|
||||
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
|
||||
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
|
||||
_midiSawmill.Debug($"Loading soundfont {file}");
|
||||
_midiSawmill.Debug($"Loading content soundfont {file}");
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
var userDataPath = _resourceManager.UserData.RootDir == null
|
||||
? CustomSoundfontDirectory
|
||||
: new ResPath(_resourceManager.UserData.RootDir) / CustomSoundfontDirectory.ToRelativePath();
|
||||
|
||||
// Load every soundfont from the user data directory last, since those may override any other soundfont.
|
||||
_midiSawmill.Debug($"Loading soundfonts from {{USERDATA}} {CustomSoundfontDirectory}");
|
||||
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}/*").Item1;
|
||||
_midiSawmill.Debug($"Loading soundfonts from user data directory {userDataPath}");
|
||||
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
|
||||
foreach (var file in enumerator)
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
|
||||
_midiSawmill.Debug($"Loading soundfont {{USERDATA}} {file}");
|
||||
_midiSawmill.Debug($"Loading user soundfont {file}");
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
@@ -336,72 +377,108 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
foreach (var renderer in _renderers)
|
||||
if (_renderers.Count == 0)
|
||||
return;
|
||||
|
||||
var transQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount };
|
||||
|
||||
if (_renderers.Count > _minRendererParallel)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if(_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
Parallel.ForEach(_renderers, opts, renderer => UpdateRenderer(renderer, transQuery, physicsQuery));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
|
||||
if (trackingEntity)
|
||||
{
|
||||
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
|
||||
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
|
||||
var sourceRelative = pos.Position - _eyeManager.CurrentEye.Position.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length() > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
_eyeManager.CurrentEye.Position.Position,
|
||||
sourceRelative.Normalized(),
|
||||
OcclusionCollisionMask),
|
||||
MathF.Min(sourceRelative.Length(), _maxCastLength),
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
|
||||
if (!renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackingEntity)
|
||||
{
|
||||
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value);
|
||||
renderer.Source.SetVelocity(vel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
UpdateRenderer(renderer, transQuery, physicsQuery);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (_nextOcclusionUpdate < _timing.RealTime)
|
||||
_nextOcclusionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_occlusionUpdateDelay));
|
||||
|
||||
if (_nextPositionUpdate < _timing.RealTime)
|
||||
_nextPositionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_positionUpdateDelay));
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
private void UpdateRenderer(IMidiRenderer renderer, EntityQuery<TransformComponent> transQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
return;
|
||||
|
||||
if (_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_nextPositionUpdate < _timing.RealTime)
|
||||
{
|
||||
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
|
||||
{
|
||||
renderer.TrackingCoordinates = transQuery.GetComponent(renderer.TrackingEntity!.Value).MapPosition;
|
||||
}
|
||||
else if (renderer.TrackingCoordinates == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!renderer.Source.SetPosition(renderer.TrackingCoordinates.Value.Position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value,
|
||||
xformQuery: transQuery, physicsQuery: physicsQuery);
|
||||
renderer.Source.SetVelocity(vel);
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap)
|
||||
{
|
||||
if (_nextOcclusionUpdate >= _timing.RealTime)
|
||||
return;
|
||||
|
||||
var pos = renderer.TrackingCoordinates.Value;
|
||||
|
||||
var sourceRelative = pos.Position - _eyeManager.CurrentEye.Position.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length() > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
_eyeManager.CurrentEye.Position.Position,
|
||||
sourceRelative.Normalized(),
|
||||
OcclusionCollisionMask),
|
||||
MathF.Min(sourceRelative.Length(), _maxCastLength),
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_runtime.LogException(ex, _midiSawmill.Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main method for the thread rendering the midi audio.
|
||||
@@ -416,7 +493,12 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using JetBrains.Annotations;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Asynchronous;
|
||||
@@ -9,7 +11,6 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
@@ -21,14 +22,13 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
// TODO: Make this a replicated CVar in MidiManager
|
||||
private const int MidiSizeLimit = 2000000;
|
||||
private const double BytesToMegabytes = 0.000001d;
|
||||
private const int ChannelCount = 16;
|
||||
private const int ChannelCount = RobustMidiEvent.MaxChannels;
|
||||
|
||||
private readonly ISawmill _midiSawmill;
|
||||
|
||||
private readonly Settings _settings;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private bool _debugEvents = false;
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _debugEvents = false;
|
||||
|
||||
// Kept around to avoid the loader callbacks getting GC'd
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
@@ -48,8 +48,9 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
private readonly SequencerClientId _robustRegister;
|
||||
private readonly SequencerClientId _debugRegister;
|
||||
|
||||
[ViewVariables]
|
||||
private MidiRendererState _rendererState = new();
|
||||
[ViewVariables] private MidiRendererState _rendererState = new();
|
||||
|
||||
private IMidiRenderer? _master;
|
||||
public MidiRendererState RendererState => _rendererState;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
@@ -70,8 +71,8 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
for (byte i = 0; i < ChannelCount; i++)
|
||||
{
|
||||
// Channel 9 is the percussion channel. Let's not change its instrument...
|
||||
if (i == 9)
|
||||
// Don't change percussion channel instrument.
|
||||
if (i == RobustMidiEvent.PercussionChannel)
|
||||
continue;
|
||||
|
||||
SendMidiEvent(RobustMidiEvent.ProgramChange(i, value, SequencerTick));
|
||||
@@ -96,11 +97,14 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
for (byte i = 0; i < ChannelCount; i++)
|
||||
{
|
||||
// Channel 9 is the percussion channel. Let's not change its bank...
|
||||
if (i == 9)
|
||||
// Don't change percussion channel bank.
|
||||
if (i == RobustMidiEvent.PercussionChannel)
|
||||
continue;
|
||||
|
||||
SendMidiEvent(RobustMidiEvent.BankSelect(i, value, SequencerTick));
|
||||
|
||||
// Re-select program.
|
||||
SendMidiEvent(RobustMidiEvent.ProgramChange(i, _midiProgram, SequencerTick));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +132,11 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisablePercussionChannel { get; set; } = true;
|
||||
public bool DisablePercussionChannel
|
||||
{
|
||||
get => FilteredChannels[RobustMidiEvent.PercussionChannel];
|
||||
set => FilteredChannels[RobustMidiEvent.PercussionChannel] = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
@@ -181,13 +189,62 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool VolumeBoost { get; set; }
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
public bool VolumeBoost
|
||||
{
|
||||
get => VelocityOverride == 127;
|
||||
set => VelocityOverride = value ? 127 : null;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? TrackingEntity { get; set; } = null;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
public MapCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
[ViewVariables]
|
||||
public BitArray FilteredChannels { get; } = new(RobustMidiEvent.MaxChannels);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte? VelocityOverride { get; set; } = null;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public IMidiRenderer? Master
|
||||
{
|
||||
get => _master;
|
||||
set
|
||||
{
|
||||
if (value == _master)
|
||||
return;
|
||||
|
||||
if (_master is { Disposed: false })
|
||||
{
|
||||
try
|
||||
{
|
||||
_master.OnMidiEvent -= SendMidiEvent;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
_master = value;
|
||||
|
||||
if (_master == null)
|
||||
return;
|
||||
|
||||
_master.OnMidiEvent += SendMidiEvent;
|
||||
ApplyState(_master.RendererState, true);
|
||||
MidiBank = _midiBank;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables, UsedImplicitly]
|
||||
private double CpuLoad => !_synth.Disposed ? _synth.CpuLoad : 0;
|
||||
|
||||
public event Action<RobustMidiEvent>? OnMidiEvent;
|
||||
public event Action? OnMidiPlayerFinished;
|
||||
|
||||
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono,
|
||||
IMidiManager midiManager, IClydeAudio clydeAudio, ITaskManager taskManager, ISawmill midiSawmill)
|
||||
@@ -354,6 +411,11 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
public void SystemReset()
|
||||
{
|
||||
SendMidiEvent(RobustMidiEvent.SystemReset(SequencerTick));
|
||||
}
|
||||
|
||||
public void ClearAllEvents()
|
||||
{
|
||||
_sequencer.RemoveEvents(SequencerClientId.Wildcard, SequencerClientId.Wildcard, -1);
|
||||
@@ -368,9 +430,6 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<RobustMidiEvent>? OnMidiEvent;
|
||||
public event Action? OnMidiPlayerFinished;
|
||||
|
||||
void IMidiRenderer.Render()
|
||||
{
|
||||
Render();
|
||||
@@ -432,7 +491,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
if (!Source.IsPlaying) Source.StartPlaying();
|
||||
}
|
||||
|
||||
public void ApplyState(MidiRendererState state)
|
||||
public void ApplyState(MidiRendererState state, bool filterChannels = false)
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
@@ -440,6 +499,9 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
|
||||
for (var channel = 0; channel < ChannelCount; channel++)
|
||||
{
|
||||
if (filterChannels && !FilteredChannels[channel])
|
||||
continue;
|
||||
|
||||
_synth.AllNotesOff(channel);
|
||||
|
||||
_synth.PitchBend(channel, state.PitchBend.AsSpan[channel]);
|
||||
@@ -462,7 +524,8 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
_synth.ProgramChange(channel, state.Program.AsSpan[channel]);
|
||||
var program = DisableProgramChangeEvent ? MidiProgram : state.Program.AsSpan[channel];
|
||||
_synth.ProgramChange(channel, program);
|
||||
|
||||
for (var key = 0; key < state.NoteVelocities.AsSpan[channel].AsSpan.Length; key++)
|
||||
{
|
||||
@@ -487,7 +550,12 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
public void SendMidiEvent(RobustMidiEvent midiEvent)
|
||||
private void SendMidiEvent(RobustMidiEvent midiEvent)
|
||||
{
|
||||
SendMidiEvent(midiEvent, true);
|
||||
}
|
||||
|
||||
public void SendMidiEvent(RobustMidiEvent midiEvent, bool raiseEvent)
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
@@ -505,11 +573,10 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.NoteOn:
|
||||
// Channel 9 is the percussion channel. We only block NoteOn events to it.
|
||||
if (DisablePercussionChannel && midiEvent.Channel == 9)
|
||||
return;
|
||||
if (FilteredChannels[midiEvent.Channel])
|
||||
break;
|
||||
|
||||
var velocity = (byte)(VolumeBoost ? 127 : midiEvent.Velocity);
|
||||
var velocity = VelocityOverride ?? midiEvent.Velocity;
|
||||
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
|
||||
@@ -523,7 +590,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
case RobustMidiCommand.ControlChange:
|
||||
// CC0 is bank selection
|
||||
if (midiEvent.Control == 0x0 && DisableProgramChangeEvent)
|
||||
return;
|
||||
break;
|
||||
|
||||
_rendererState.Controllers.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Control] = midiEvent.Value;
|
||||
if(midiEvent.Control != 0x0)
|
||||
@@ -534,7 +601,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
|
||||
case RobustMidiCommand.ProgramChange:
|
||||
if (DisableProgramChangeEvent)
|
||||
return;
|
||||
break;
|
||||
|
||||
_rendererState.Program.AsSpan[midiEvent.Channel] = midiEvent.Program;
|
||||
_synth.ProgramChange(midiEvent.Channel, midiEvent.Program);
|
||||
@@ -561,14 +628,14 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
switch (midiEvent.Control)
|
||||
{
|
||||
case 0x0 when midiEvent.Status == 0xFF:
|
||||
_rendererState = new ();
|
||||
_rendererState = new MidiRendererState();
|
||||
_synth.SystemReset();
|
||||
|
||||
// Reset the instrument to the one we were using.
|
||||
if (DisableProgramChangeEvent)
|
||||
{
|
||||
MidiProgram = _midiProgram;
|
||||
MidiBank = _midiBank;
|
||||
MidiProgram = _midiProgram;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -597,7 +664,10 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
//_midiSawmill.Error("Exception while sending midi event of type {0}: {1}", midiEvent.Type, e, midiEvent);
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => OnMidiEvent?.Invoke(midiEvent));
|
||||
if (raiseEvent)
|
||||
{
|
||||
_taskManager.RunOnMainThread(() => OnMidiEvent?.Invoke(midiEvent));
|
||||
}
|
||||
}
|
||||
|
||||
public void ScheduleMidiEvent(RobustMidiEvent midiEvent, uint time, bool absolute = false)
|
||||
@@ -633,6 +703,9 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
/// <inheritdoc />
|
||||
void IMidiRenderer.InternalDispose()
|
||||
{
|
||||
OnMidiEvent = null;
|
||||
OnMidiPlayerFinished = null;
|
||||
|
||||
Source?.Dispose();
|
||||
_driver?.Dispose();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
@@ -12,7 +13,7 @@ public struct MidiRendererState
|
||||
internal FixedArray16<byte> ChannelPressure;
|
||||
internal FixedArray16<ushort> PitchBend;
|
||||
|
||||
internal Span<byte> AsSpan => MemoryMarshal.CreateSpan(ref NoteVelocities._00._00, 4160);
|
||||
[ViewVariables] internal Span<byte> AsSpan => MemoryMarshal.CreateSpan(ref NoteVelocities._00._00, 4160);
|
||||
|
||||
static unsafe MidiRendererState()
|
||||
{
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace Robust.Client
|
||||
deps.Register<IReplayLoadManager, ReplayLoadManager>();
|
||||
deps.Register<IReplayPlaybackManager, ReplayPlaybackManager>();
|
||||
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
|
||||
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
|
||||
deps.Register<IClientGameStateManager, ClientGameStateManager>();
|
||||
deps.Register<IBaseClient, BaseClient>();
|
||||
deps.Register<IPlayerManager, PlayerManager>();
|
||||
|
||||
@@ -621,6 +621,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
internal sealed class ChunkInfoCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
@@ -631,16 +632,17 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
|
||||
|
||||
if (!_map.TryFindGridAt(mousePos, out _, out var grid))
|
||||
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
|
||||
{
|
||||
shell.WriteLine("No grid under your mouse cursor.");
|
||||
return;
|
||||
}
|
||||
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = grid.GetOrAddChunk(chunkIndex);
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, grid.MapToGrid(mousePos));
|
||||
var chunk = mapSystem.GetOrAddChunk(gridUid, grid, chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {grid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
|
||||
shell.WriteLine($"worldBounds: {mapSystem.CalcWorldAABB(gridUid, grid, chunk)} localBounds: {chunk.CachedBounds}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
|
||||
[Dependency] private readonly IReplayLoadManager _replayLoader = default!;
|
||||
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -200,7 +200,12 @@ namespace Robust.Client
|
||||
// Setup main loop
|
||||
if (_mainLoop == null)
|
||||
{
|
||||
_mainLoop = new GameLoop(_gameTiming, _runtimeLog, _prof, _logManager.GetSawmill("eng"))
|
||||
_mainLoop = new GameLoop(
|
||||
_gameTiming,
|
||||
_runtimeLog,
|
||||
_prof,
|
||||
_logManager.GetSawmill("eng"),
|
||||
GameLoopOptions.FromCVars(_configurationManager))
|
||||
{
|
||||
SleepMode = displayMode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
|
||||
};
|
||||
@@ -560,6 +565,11 @@ namespace Robust.Client
|
||||
{
|
||||
_taskManager.ProcessPendingTasks(); // tasks like connect
|
||||
}
|
||||
|
||||
using (_prof.Group("Content post engine"))
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.InputPostEngine, frameEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void Tick(FrameEventArgs frameEventArgs)
|
||||
@@ -756,6 +766,8 @@ namespace Robust.Client
|
||||
|
||||
internal void CleanupGameThread()
|
||||
{
|
||||
_replayRecording.Shutdown();
|
||||
|
||||
_modLoader.Shutdown();
|
||||
|
||||
// CEF specifically makes a massive silent stink of it if we don't shut it down from the correct thread.
|
||||
|
||||
@@ -15,15 +15,12 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private Eye? _eye = default!;
|
||||
[ViewVariables] internal Eye? _eye = default!;
|
||||
|
||||
// Horrible hack to get around ordering issues.
|
||||
private bool _setCurrentOnInitialize;
|
||||
[DataField("drawFov")]
|
||||
private bool _setDrawFovOnInitialize = true;
|
||||
[DataField("zoom")]
|
||||
private Vector2 _setZoomOnInitialize = Vector2.One;
|
||||
internal bool _setCurrentOnInitialize;
|
||||
[DataField("drawFov")] internal bool _setDrawFovOnInitialize = true;
|
||||
[DataField("zoom")] internal Vector2 _setZoomOnInitialize = Vector2.One;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this entity is used to update the eye's position instead of just using the component's owner.
|
||||
@@ -119,54 +116,6 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables]
|
||||
public MapCoordinates? Position => _eye?.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_eye = new Eye
|
||||
{
|
||||
Position = _entityManager.GetComponent<TransformComponent>(Owner).MapPosition,
|
||||
Zoom = _setZoomOnInitialize,
|
||||
DrawFov = _setDrawFovOnInitialize
|
||||
};
|
||||
|
||||
if ((_eyeManager.CurrentEye == _eye) != _setCurrentOnInitialize)
|
||||
{
|
||||
if (_setCurrentOnInitialize)
|
||||
{
|
||||
_eyeManager.ClearCurrentEye();
|
||||
}
|
||||
else
|
||||
{
|
||||
_eyeManager.CurrentEye = _eye;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not EyeComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawFov = state.DrawFov;
|
||||
// TODO: Should be a way for content to override lerping and lerp the zoom
|
||||
Zoom = state.Zoom;
|
||||
Offset = state.Offset;
|
||||
VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
Current = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
|
||||
/// keep the view following the entity.
|
||||
|
||||
@@ -40,7 +40,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
return;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
|
||||
var xform = Transform(uid);
|
||||
QueueTreeUpdate(uid, comp, xform);
|
||||
|
||||
@@ -38,7 +38,8 @@ public sealed class DebugEntityLookupSystem : EntitySystem
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(
|
||||
new EntityLookupOverlay(
|
||||
EntityManager,
|
||||
Get<EntityLookupSystem>()));
|
||||
EntityManager.System<EntityLookupSystem>(),
|
||||
EntityManager.System<SharedTransformSystem>()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -52,31 +53,35 @@ public sealed class DebugEntityLookupSystem : EntitySystem
|
||||
|
||||
public sealed class EntityLookupOverlay : Overlay
|
||||
{
|
||||
private IEntityManager _entityManager = default!;
|
||||
private EntityLookupSystem _lookup = default!;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
|
||||
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup, SharedTransformSystem transform)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_lookup = lookup;
|
||||
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var worldBounds = args.WorldBounds;
|
||||
|
||||
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
|
||||
// TODO: Static version
|
||||
_lookup.FindLookupsIntersecting(args.MapId, worldBounds, (uid, lookup) =>
|
||||
{
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
|
||||
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
|
||||
var (_, rotation, matrix, invMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(uid);
|
||||
|
||||
worldHandle.SetTransform(matrix);
|
||||
|
||||
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
var lookupAABB = invMatrix.TransformBox(worldBounds);
|
||||
var ents = new List<EntityUid>();
|
||||
|
||||
lookup.DynamicTree.QueryAabb(ref ents, static (ref List<EntityUid> state, in FixtureProxy value) =>
|
||||
@@ -105,20 +110,22 @@ public sealed class EntityLookupOverlay : Overlay
|
||||
|
||||
foreach (var ent in ents)
|
||||
{
|
||||
if (_entityManager.Deleted(ent)) continue;
|
||||
var xform = xformQuery.GetComponent(ent);
|
||||
if (_entityManager.Deleted(ent))
|
||||
continue;
|
||||
|
||||
var xform = _xformQuery.GetComponent(ent);
|
||||
|
||||
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
|
||||
var (entPos, entRot) = xform.GetWorldPositionRotation();
|
||||
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
|
||||
|
||||
var lookupPos = invMatrix.Transform(entPos);
|
||||
var lookupRot = entRot - rotation;
|
||||
|
||||
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
|
||||
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
|
||||
|
||||
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
|
||||
60
Robust.Client/GameObjects/EntitySystems/EyeSystem.cs
Normal file
60
Robust.Client/GameObjects/EntitySystems/EyeSystem.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class EyeSystem : SharedEyeSystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
|
||||
{
|
||||
component._eye = new Eye
|
||||
{
|
||||
Position = Transform(uid).MapPosition,
|
||||
Zoom = component._setZoomOnInitialize,
|
||||
DrawFov = component._setDrawFovOnInitialize
|
||||
};
|
||||
|
||||
if ((_eyeManager.CurrentEye == component._eye) != component._setCurrentOnInitialize)
|
||||
{
|
||||
if (component._setCurrentOnInitialize)
|
||||
{
|
||||
_eyeManager.ClearCurrentEye();
|
||||
}
|
||||
else
|
||||
{
|
||||
_eyeManager.CurrentEye = component._eye;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, EyeComponent component, ComponentRemove args)
|
||||
{
|
||||
component.Current = false;
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EyeComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
component.DrawFov = state.DrawFov;
|
||||
// TODO: Should be a way for content to override lerping and lerp the zoom
|
||||
component.Zoom = state.Zoom;
|
||||
component.Offset = state.Offset;
|
||||
component.VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
|
||||
comp.ContainerOccluded = occluded;
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
|
||||
if (comp.Enabled)
|
||||
_lightTree.QueueTreeUpdate(uid, comp);
|
||||
@@ -50,7 +50,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
comp._enabled = enabled;
|
||||
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
|
||||
var cast = (PointLightComponent)comp;
|
||||
if (!cast.ContainerOccluded)
|
||||
@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
|
||||
comp._radius = radius;
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
|
||||
var cast = (PointLightComponent)comp;
|
||||
if (cast.TreeUid != null)
|
||||
|
||||
@@ -22,6 +22,13 @@ public sealed partial class TransformSystem
|
||||
base.SetLocalPositionNoLerp(xform, value);
|
||||
}
|
||||
|
||||
public override void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
|
||||
{
|
||||
xform.NextRotation = null;
|
||||
xform.LerpParent = EntityUid.Invalid;
|
||||
base.SetLocalRotationNoLerp(xform, angle);
|
||||
}
|
||||
|
||||
public override void SetLocalRotation(TransformComponent xform, Angle angle)
|
||||
{
|
||||
xform.PrevRotation = xform._localRotation;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.GameObjects
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length:0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +532,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
comp.HandleComponentState(compState, null);
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
|
||||
@@ -561,7 +560,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
|
||||
comp.HandleComponentState(state, null);
|
||||
comp.ClearCreationTick(); // don't undo the re-adding.
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
@@ -1172,7 +1170,6 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
comp.HandleComponentState(cur, next);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1223,7 +1220,7 @@ namespace Robust.Client.GameStates
|
||||
return;
|
||||
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
ResetEnt(meta, false);
|
||||
ResetEnt(uid, meta, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1301,24 +1298,24 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
|
||||
foreach (var meta in _entities.EntityQuery<MetaDataComponent>(true))
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var meta))
|
||||
{
|
||||
ResetEnt(meta);
|
||||
ResetEnt(uid, meta);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset a given entity to the most recent server state.
|
||||
/// </summary>
|
||||
private void ResetEnt(MetaDataComponent meta, bool skipDetached = true)
|
||||
private void ResetEnt(EntityUid uid, MetaDataComponent meta, bool skipDetached = true)
|
||||
{
|
||||
if (skipDetached && (meta.Flags & MetaDataFlags.Detached) != 0)
|
||||
return;
|
||||
|
||||
meta.Flags &= ~MetaDataFlags.Detached;
|
||||
|
||||
var uid = meta.Owner;
|
||||
|
||||
if (!_processor.TryGetLastServerStates(uid, out var lastState))
|
||||
return;
|
||||
|
||||
@@ -1334,7 +1331,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
comp.HandleComponentState(state, null);
|
||||
}
|
||||
|
||||
// ensure we don't have any extra components
|
||||
|
||||
@@ -261,6 +261,9 @@ namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
throw new Exception("Failed to generate source. Too many simultaneous audio streams?");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Robust.Client.Physics
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public bool EnableDebug
|
||||
{
|
||||
@@ -22,12 +23,12 @@ namespace Robust.Client.Physics
|
||||
{
|
||||
if (_enableDebug == value) return;
|
||||
|
||||
Sawmill.Info($"Set grid fixture debug to {value}");
|
||||
Log.Info($"Set grid fixture debug to {value}");
|
||||
_enableDebug = value;
|
||||
|
||||
if (_enableDebug)
|
||||
{
|
||||
var overlay = new GridSplitNodeOverlay(EntityManager, _map, this);
|
||||
var overlay = new GridSplitNodeOverlay(_map, this, _transform);
|
||||
_overlay.AddOverlay(overlay);
|
||||
RaiseNetworkEvent(new RequestGridNodesMessage());
|
||||
}
|
||||
@@ -39,7 +40,7 @@ namespace Robust.Client.Physics
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableDebug = false;
|
||||
private bool _enableDebug;
|
||||
private readonly Dictionary<EntityUid, Dictionary<Vector2i, List<List<Vector2i>>>> _nodes = new();
|
||||
private readonly Dictionary<EntityUid, List<(Vector2, Vector2)>> _connections = new();
|
||||
|
||||
@@ -58,7 +59,7 @@ namespace Robust.Client.Physics
|
||||
|
||||
private void OnDebugMessage(ChunkSplitDebugMessage ev)
|
||||
{
|
||||
Sawmill.Info($"Received grid fixture debug data");
|
||||
Log.Info($"Received grid fixture debug data");
|
||||
if (!_enableDebug) return;
|
||||
|
||||
_nodes[ev.Grid] = ev.Nodes;
|
||||
@@ -69,71 +70,76 @@ namespace Robust.Client.Physics
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private IEntityManager _entManager;
|
||||
private IMapManager _mapManager;
|
||||
private GridFixtureSystem _system;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly GridFixtureSystem _system;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
|
||||
public GridSplitNodeOverlay(IEntityManager entManager, IMapManager mapManager, GridFixtureSystem system)
|
||||
public GridSplitNodeOverlay(IMapManager mapManager, GridFixtureSystem system, SharedTransformSystem transform)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = mapManager;
|
||||
_system = system;
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var iGrid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
// May not have received nodes yet.
|
||||
if (!_system._nodes.TryGetValue(iGrid.Owner, out var nodes)) continue;
|
||||
var state = (_system, _transform, args.WorldBounds, worldHandle);
|
||||
|
||||
var gridXform = xformQuery.GetComponent(iGrid.Owner);
|
||||
worldHandle.SetTransform(gridXform.WorldMatrix);
|
||||
var chunkEnumerator = iGrid.GetMapChunks(args.WorldBounds);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (GridFixtureSystem system, SharedTransformSystem transform, Box2Rotated worldBounds, DrawingHandleWorld worldHandle) tuple) =>
|
||||
{
|
||||
if (!nodes.TryGetValue(chunk.Indices, out var chunkNodes)) continue;
|
||||
// May not have received nodes yet.
|
||||
if (!tuple.system._nodes.TryGetValue(uid, out var nodes))
|
||||
return true;
|
||||
|
||||
for (var i = 0; i < chunkNodes.Count; i++)
|
||||
tuple.worldHandle.SetTransform(tuple.transform.GetWorldMatrix(uid));
|
||||
var chunkEnumerator = grid.GetMapChunks(tuple.worldBounds);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var group = chunkNodes[i];
|
||||
var offset = chunk.Indices * chunk.ChunkSize;
|
||||
var color = GetColor(chunk, i);
|
||||
if (!nodes.TryGetValue(chunk.Indices, out var chunkNodes)) continue;
|
||||
|
||||
foreach (var index in group)
|
||||
for (var i = 0; i < chunkNodes.Count; i++)
|
||||
{
|
||||
worldHandle.DrawRect(new Box2(offset + index, offset + index + 1).Enlarged(-0.1f), color);
|
||||
var group = chunkNodes[i];
|
||||
var offset = chunk.Indices * chunk.ChunkSize;
|
||||
var color = GetColor(chunk, i);
|
||||
|
||||
foreach (var index in group)
|
||||
{
|
||||
tuple.worldHandle.DrawRect(new Box2(offset + index, offset + index + 1).Enlarged(-0.1f), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var connections = _system._connections[iGrid.Owner];
|
||||
var connections = tuple.system._connections[uid];
|
||||
|
||||
foreach (var (start, end) in connections)
|
||||
{
|
||||
worldHandle.DrawLine(start, end, Color.Aquamarine);
|
||||
}
|
||||
}
|
||||
foreach (var (start, end) in connections)
|
||||
{
|
||||
tuple.worldHandle.DrawLine(start, end, Color.Aquamarine);
|
||||
}
|
||||
|
||||
static Color GetColor(MapChunk chunk, int index)
|
||||
{
|
||||
// Just want something that doesn't give similar indices at 0,0 but is also deterministic.
|
||||
// Add an offset to yIndex so we at least have some colour that isn't grey at 0,0
|
||||
var actualIndex = chunk.Indices.X * 20 + (chunk.Indices.Y + 20) * 35 + index * 50;
|
||||
|
||||
var red = (byte) (actualIndex % 255);
|
||||
var green = (byte) (actualIndex * 20 % 255);
|
||||
var blue = (byte) (actualIndex * 30 % 255);
|
||||
|
||||
return new Color(red, green, blue, 85);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, true);
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
|
||||
private Color GetColor(MapChunk chunk, int index)
|
||||
{
|
||||
// Just want something that doesn't give similar indices at 0,0 but is also deterministic.
|
||||
// Add an offset to yIndex so we at least have some colour that isn't grey at 0,0
|
||||
var actualIndex = chunk.Indices.X * 20 + (chunk.Indices.Y + 20) * 35 + index * 50;
|
||||
|
||||
var red = (byte) (actualIndex % 255);
|
||||
var green = (byte) (actualIndex * 20 % 255);
|
||||
var blue = (byte) (actualIndex * 30 % 255);
|
||||
|
||||
return new Color(red, green, blue, 85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,6 +694,9 @@ namespace Robust.Client.Placement
|
||||
IsActive = true;
|
||||
|
||||
CurrentPlacementOverlayEntity = EntityManager.SpawnEntity(templateName, MapCoordinates.Nullspace);
|
||||
EntityManager.RunMapInit(
|
||||
CurrentPlacementOverlayEntity.Value,
|
||||
EntityManager.GetComponent<MetaDataComponent>(CurrentPlacementOverlayEntity.Value));
|
||||
}
|
||||
|
||||
public void PreparePlacementSprite(SpriteComponent sprite)
|
||||
|
||||
@@ -78,15 +78,16 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
|
||||
IWritableDirProvider directory,
|
||||
string? name = null,
|
||||
bool overwrite = false,
|
||||
TimeSpan? duration = null)
|
||||
TimeSpan? duration = null,
|
||||
object? state = null)
|
||||
{
|
||||
if (!base.TryStartRecording(directory, name, overwrite, duration))
|
||||
if (!base.TryStartRecording(directory, name, overwrite, duration, state))
|
||||
return false;
|
||||
|
||||
var (state, detachMsg) = CreateFullState();
|
||||
var (gameState, detachMsg) = CreateFullState();
|
||||
if (detachMsg != null)
|
||||
RecordReplayMessage(detachMsg);
|
||||
Update(state);
|
||||
Update(gameState);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<!-- ILLink configuration -->
|
||||
<ItemGroup>
|
||||
<RobustLinkRoots Include="Robust.Client" />
|
||||
<RobustLinkRoots Include="Robust.Shared" />
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
|
||||
<RobustLinkAssemblies Include="OpenToolkit.Graphics" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -55,12 +55,12 @@ namespace Robust.Client.UserInterface
|
||||
continue;
|
||||
|
||||
toRemove.Add(key);
|
||||
AnimationCompleted?.Invoke(key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_playingAnimations.Remove(key);
|
||||
AnimationCompleted?.Invoke(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Robust.Client.UserInterface
|
||||
private VAlignment _verticalAlignment = VAlignment.Stretch;
|
||||
private Thickness _margin;
|
||||
private bool _measuring;
|
||||
private bool _arranging;
|
||||
|
||||
/// <summary>
|
||||
/// The desired minimum size this control needs for layout to avoid cutting off content or such.
|
||||
@@ -469,13 +470,12 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
public void InvalidateMeasure()
|
||||
{
|
||||
if (!IsMeasureValid)
|
||||
if (!IsMeasureValid || _measuring)
|
||||
return;
|
||||
|
||||
IsMeasureValid = false;
|
||||
IsArrangeValid = false;
|
||||
|
||||
UserInterfaceManagerInternal.QueueMeasureUpdate(this);
|
||||
InvalidateArrange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -484,7 +484,7 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
public void InvalidateArrange()
|
||||
{
|
||||
if (!IsArrangeValid)
|
||||
if (!IsArrangeValid || _arranging)
|
||||
{
|
||||
// Already queued for a layout update, don't bother.
|
||||
return;
|
||||
@@ -508,7 +508,16 @@ namespace Robust.Client.UserInterface
|
||||
if (!IsMeasureValid || PreviousMeasure != availableSize)
|
||||
{
|
||||
IsMeasureValid = true;
|
||||
var desired = MeasureCore(availableSize);
|
||||
_measuring = true;
|
||||
Vector2 desired;
|
||||
try
|
||||
{
|
||||
desired = MeasureCore(availableSize);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_measuring = false;
|
||||
}
|
||||
|
||||
if (desired.X < 0 || desired.Y < 0 || !float.IsFinite(desired.X) || !float.IsFinite(desired.Y))
|
||||
throw new InvalidOperationException("Invalid size returned from Measure()");
|
||||
@@ -540,16 +549,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
var constrained = ApplySizeConstraints(this, withoutMargin);
|
||||
|
||||
Vector2 measured;
|
||||
try
|
||||
{
|
||||
_measuring = true;
|
||||
measured = MeasureOverride(constrained);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_measuring = false;
|
||||
}
|
||||
var measured = MeasureOverride(constrained);
|
||||
|
||||
if (!float.IsNaN(SetWidth))
|
||||
{
|
||||
@@ -604,14 +604,22 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
public void Arrange(UIBox2 finalRect)
|
||||
{
|
||||
if (!IsMeasureValid)
|
||||
Measure(PreviousMeasure ?? finalRect.Size);
|
||||
|
||||
if (!IsArrangeValid || PreviousArrange != finalRect)
|
||||
_arranging = true;
|
||||
try
|
||||
{
|
||||
IsArrangeValid = true;
|
||||
ArrangeCore(finalRect);
|
||||
PreviousArrange = finalRect;
|
||||
if (!IsMeasureValid)
|
||||
Measure(PreviousMeasure ?? finalRect.Size);
|
||||
|
||||
if (!IsArrangeValid || PreviousArrange != finalRect)
|
||||
{
|
||||
IsArrangeValid = true;
|
||||
ArrangeCore(finalRect);
|
||||
PreviousArrange = finalRect;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_arranging = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,6 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
internal void DoStyleUpdate()
|
||||
{
|
||||
_stylingDirty = false;
|
||||
_styleProperties.Clear();
|
||||
|
||||
if (_stylesheetUpdateNeeded)
|
||||
@@ -229,6 +228,10 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
|
||||
StylePropertiesChanged();
|
||||
|
||||
// Setting this at the end of the function to prevent style updates from ever re-queueing a style update,
|
||||
// which would cause an infinite loop.
|
||||
_stylingDirty = false;
|
||||
}
|
||||
|
||||
protected virtual void StylePropertiesChanged()
|
||||
|
||||
@@ -88,9 +88,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_scrollBar.MoveToEnd();
|
||||
}
|
||||
|
||||
public Item AddItem(string text, Texture? icon = null, bool selectable = true)
|
||||
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
|
||||
{
|
||||
var item = new Item(this) {Text = text, Icon = icon, Selectable = selectable};
|
||||
var item = new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata};
|
||||
Add(item);
|
||||
return item;
|
||||
}
|
||||
@@ -448,7 +448,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (item.Selected && SelectMode != ItemListSelectMode.Button)
|
||||
{
|
||||
ClearSelected();
|
||||
if(SelectMode != ItemListSelectMode.Multiple)
|
||||
ClearSelected();
|
||||
item.Selected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,23 +223,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var first = GetChild(0);
|
||||
var second = GetChild(1);
|
||||
|
||||
var firstDesiredSize = firstMinSize ?? (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
|
||||
var secondDesiredSize = secondMinSize ?? (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
|
||||
var firstOrientedMinSize = Vertical ? first.MinSize.Y : first.MinSize.X;
|
||||
var secondOrientedMinSize = Vertical ? second.MinSize.Y : second.MinSize.X;
|
||||
|
||||
if (firstOrientedMinSize > firstDesiredSize && firstOrientedMinSize != 0)
|
||||
{
|
||||
first.Measure(controlSize);
|
||||
}
|
||||
|
||||
if (secondOrientedMinSize > secondDesiredSize && secondOrientedMinSize != 0)
|
||||
{
|
||||
second.Measure(controlSize);
|
||||
}
|
||||
|
||||
firstMinSize = Vertical ? first.DesiredSize.Y : first.DesiredSize.X;
|
||||
secondMinSize = Vertical ? second.DesiredSize.Y : second.DesiredSize.X;
|
||||
firstMinSize ??= (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
|
||||
secondMinSize ??= (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
|
||||
var size = Vertical ? controlSize.Y : controlSize.X;
|
||||
|
||||
_splitStart = MathHelper.Clamp(_splitStart, firstMinSize.Value,
|
||||
|
||||
@@ -62,18 +62,19 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
var screenSize = _displayManager.ScreenSize;
|
||||
var screenScale = _displayManager.MainWindow.ContentScale;
|
||||
|
||||
MapCoordinates mouseWorldMap;
|
||||
EntityCoordinates mouseGridPos;
|
||||
TileRef tile;
|
||||
|
||||
mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
|
||||
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
|
||||
if (mouseWorldMap == MapCoordinates.Nullspace)
|
||||
return;
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldMap, out _, out var mouseGrid))
|
||||
var mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
|
||||
{
|
||||
mouseGridPos = mouseGrid.MapToGrid(mouseWorldMap);
|
||||
tile = mouseGrid.GetTileRef(mouseGridPos);
|
||||
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
|
||||
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -152,12 +152,17 @@ internal sealed partial class UserInterfaceManager
|
||||
_styleUpdateQueue.Enqueue(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a control so that it gets remeasured in the next frame update. Does not queue an arrange update.
|
||||
/// </summary>
|
||||
public void QueueMeasureUpdate(Control control)
|
||||
{
|
||||
_measureUpdateQueue.Enqueue(control);
|
||||
_arrangeUpdateQueue.Enqueue(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a control so that it gets rearranged in the next frame update. Does not queue a measure update.
|
||||
/// </summary>
|
||||
public void QueueArrangeUpdate(Control control)
|
||||
{
|
||||
_arrangeUpdateQueue.Enqueue(control);
|
||||
|
||||
@@ -38,6 +38,7 @@ internal sealed partial class UserInterfaceManager
|
||||
newRoot.StyleSheetUpdate();
|
||||
newRoot.InvalidateMeasure();
|
||||
QueueMeasureUpdate(newRoot);
|
||||
QueueArrangeUpdate(newRoot);
|
||||
|
||||
if (window.IsFocused)
|
||||
FocusRoot(newRoot);
|
||||
|
||||
@@ -26,6 +26,7 @@ using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
@@ -54,6 +55,12 @@ namespace Robust.Client.UserInterface
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtime = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Upper limit on the number of times that controls can be measured / arranged each tick before being deferred
|
||||
/// to the next frame update. This is just meant to prevent infinite loops from completely locking up the UI.
|
||||
/// </summary>
|
||||
public const int ControlUpdateLimit = 25_000;
|
||||
|
||||
[ViewVariables] public InterfaceTheme ThemeDefaults { get; private set; } = default!;
|
||||
[ViewVariables]
|
||||
public Stylesheet? Stylesheet
|
||||
@@ -220,6 +227,12 @@ namespace Robust.Client.UserInterface
|
||||
var total = 0;
|
||||
while (_styleUpdateQueue.Count != 0)
|
||||
{
|
||||
if (total >= ControlUpdateLimit)
|
||||
{
|
||||
_sawmillUI.Warning($"Hit style update limit. Queued: {_styleUpdateQueue.Count}. Next in queue: {_styleUpdateQueue.Peek()}. Parent: {_styleUpdateQueue.Peek().Parent}");
|
||||
break;
|
||||
}
|
||||
|
||||
var control = _styleUpdateQueue.Dequeue();
|
||||
|
||||
if (control.Disposed)
|
||||
@@ -237,12 +250,20 @@ namespace Robust.Client.UserInterface
|
||||
var total = 0;
|
||||
while (_measureUpdateQueue.Count != 0)
|
||||
{
|
||||
if (total >= ControlUpdateLimit)
|
||||
{
|
||||
_sawmillUI.Warning($"Hit measure update limit. Queued: {_measureUpdateQueue.Count}. Next in queue: {_measureUpdateQueue.Peek()}. Parent: {_measureUpdateQueue.Peek().Parent}");
|
||||
break;
|
||||
}
|
||||
|
||||
var control = _measureUpdateQueue.Dequeue();
|
||||
|
||||
if (control.Disposed)
|
||||
continue;
|
||||
|
||||
RunMeasure(control);
|
||||
if (!control.IsMeasureValid && control.IsInsideTree)
|
||||
_sawmillUI.Warning($"Control's measure is invalid after measuring. Control: {control}. Parent: {control.Parent}.");
|
||||
total += 1;
|
||||
}
|
||||
|
||||
@@ -254,12 +275,19 @@ namespace Robust.Client.UserInterface
|
||||
var total = 0;
|
||||
while (_arrangeUpdateQueue.Count != 0)
|
||||
{
|
||||
if (total >= ControlUpdateLimit)
|
||||
{
|
||||
_sawmillUI.Warning($"Hit arrange update limit. Queued: {_arrangeUpdateQueue.Count}. Next in queue: {_arrangeUpdateQueue.Peek()}. Parent: {_arrangeUpdateQueue.Peek().Parent}");
|
||||
break;
|
||||
}
|
||||
var control = _arrangeUpdateQueue.Dequeue();
|
||||
|
||||
if (control.Disposed)
|
||||
continue;
|
||||
|
||||
RunArrange(control);
|
||||
if (!control.IsArrangeValid && control.IsInsideTree)
|
||||
_sawmillUI.Warning($"Control's arrangement is invalid after arranging. Control: {control}. Parent: {control.Parent}.");
|
||||
total += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly IStatusHost _statusHost = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replay = default!;
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replay = default!;
|
||||
[Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!;
|
||||
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
|
||||
|
||||
@@ -510,7 +510,12 @@ namespace Robust.Server
|
||||
{
|
||||
if (_mainLoop == null)
|
||||
{
|
||||
_mainLoop = new GameLoop(_time, _runtimeLog, _prof, _log.GetSawmill("eng"))
|
||||
_mainLoop = new GameLoop(
|
||||
_time,
|
||||
_runtimeLog,
|
||||
_prof,
|
||||
_log.GetSawmill("eng"),
|
||||
GameLoopOptions.FromCVars(_config))
|
||||
{
|
||||
SleepMode = SleepMode.Delay,
|
||||
DetectSoftLock = true,
|
||||
@@ -628,6 +633,8 @@ namespace Robust.Server
|
||||
// called right before main loop returns, do all saving/cleanup in here
|
||||
public void Cleanup()
|
||||
{
|
||||
_replay.Shutdown();
|
||||
|
||||
_modLoader.Shutdown();
|
||||
|
||||
_playerManager.Shutdown();
|
||||
@@ -670,6 +677,8 @@ namespace Robust.Server
|
||||
|
||||
_network.ProcessPackets();
|
||||
_taskManager.ProcessPendingTasks();
|
||||
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.InputPostEngine, args);
|
||||
}
|
||||
|
||||
private void Update(FrameEventArgs frameEventArgs)
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Robust.Server.Bql
|
||||
return base.DoInitialSelection(arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
return entityManager.GetAllComponents((Type) arguments[0])
|
||||
return entityManager.GetAllComponents((Type) arguments[0], includePaused: true)
|
||||
.Select(x => x.Owner);
|
||||
}
|
||||
}
|
||||
@@ -352,4 +352,17 @@ namespace Robust.Server.Bql
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PausedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "paused";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => entityManager.GetComponent<MetaDataComponent>(e).EntityPaused ^ isInverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Robust.Server.Console
|
||||
private long _lastReceivedBytes;
|
||||
private long _lastSentBytes;
|
||||
|
||||
private bool _hasCancelled;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -319,8 +320,15 @@ namespace Robust.Server.Console
|
||||
|
||||
private void CancelKeyHandler(object? sender, ConsoleCancelEventArgs args)
|
||||
{
|
||||
if (_hasCancelled)
|
||||
{
|
||||
Con.WriteLine("Double CancelKey, terminating process.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle process exiting ourselves.
|
||||
args.Cancel = true;
|
||||
_hasCancelled = true;
|
||||
|
||||
_taskManager.RunOnMainThread(() => { _baseServer.Shutdown("CancelKey"); });
|
||||
}
|
||||
|
||||
@@ -87,10 +87,5 @@ namespace Robust.Server.GameObjects
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new EyeComponentState(DrawFov, Zoom, Offset, VisibilityMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
Robust.Server/GameObjects/EntitySystems/EyeSystem.cs
Normal file
18
Robust.Server/GameObjects/EntitySystems/EyeSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
public sealed class EyeSystem : SharedEyeSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<EyeComponent, ComponentGetState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new EyeComponentState(component.DrawFov, component.Zoom, component.Offset, component.VisibilityMask);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
private IServerEntityManagerInternal _serverEntityManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private ISawmill _logLoader = default!;
|
||||
@@ -424,12 +425,16 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
}
|
||||
|
||||
var entities = (SequenceDataNode) metaDef["entities"];
|
||||
EntityPrototype? proto = null;
|
||||
|
||||
if (type != null)
|
||||
_prototypeManager.TryIndex(type, out proto);
|
||||
|
||||
foreach (var entityDef in entities.Cast<MappingDataNode>())
|
||||
{
|
||||
var uid = entityDef.Get<ValueDataNode>("uid").AsInt();
|
||||
|
||||
var entity = _serverEntityManager.AllocEntity(type);
|
||||
var entity = _serverEntityManager.AllocEntity(proto);
|
||||
data.Entities.Add(entity);
|
||||
data.UidEntityMap.Add(uid, entity);
|
||||
data.EntitiesToDeserialize.Add(entity, entityDef);
|
||||
@@ -462,11 +467,13 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
}
|
||||
else if (ev.RenamedPrototypes.TryGetValue(typeNode.Value, out var newType))
|
||||
{
|
||||
entity = _serverEntityManager.AllocEntity(newType);
|
||||
_prototypeManager.TryIndex<EntityPrototype>(newType, out var prototype);
|
||||
entity = _serverEntityManager.AllocEntity(prototype);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = _serverEntityManager.AllocEntity(typeNode.Value);
|
||||
_prototypeManager.TryIndex<EntityPrototype>(typeNode.Value, out var prototype);
|
||||
entity = _serverEntityManager.AllocEntity(prototype);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -758,11 +765,12 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
SequenceDataNode yamlGridChunks = (SequenceDataNode)yamlGrid["chunks"];
|
||||
|
||||
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
|
||||
var gridUid = grid.Owner;
|
||||
|
||||
foreach (var chunkNode in yamlGridChunks.Cast<MappingDataNode>())
|
||||
{
|
||||
var (chunkOffsetX, chunkOffsetY) = _serManager.Read<Vector2i>(chunkNode["ind"]);
|
||||
_serManager.Read(chunkNode, _context, instanceProvider: () => grid.GetOrAddChunk(chunkOffsetX, chunkOffsetY), notNullableOverride: true);
|
||||
_serManager.Read(chunkNode, _context, instanceProvider: () => _mapSystem.GetOrAddChunk(gridUid, grid, chunkOffsetX, chunkOffsetY), notNullableOverride: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
foreach (var (_, comp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
if (comp.SessionSpecific || comp.SendOnlyToOwner)
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Server.GameObjects
|
||||
// These methods are used by the map loader to do multi-stage entity construction during map load.
|
||||
// I would recommend you refer to the MapLoader for usage.
|
||||
|
||||
EntityUid AllocEntity(string? prototypeName, EntityUid uid = default);
|
||||
EntityUid AllocEntity(EntityPrototype? prototype, EntityUid uid = default);
|
||||
|
||||
void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null);
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace Robust.Server.GameObjects
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
EntityUid IServerEntityManagerInternal.AllocEntity(string? prototypeName, EntityUid uid)
|
||||
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype, EntityUid uid)
|
||||
{
|
||||
return AllocEntity(prototypeName, out _, uid);
|
||||
return AllocEntity(prototype, out _, uid);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context)
|
||||
|
||||
@@ -107,15 +107,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
private EntityQuery<EyeComponent> _eyeQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_eyeQuery = GetEntityQuery<EyeComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_sawmill = Logger.GetSawmill("PVS");
|
||||
|
||||
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
|
||||
|
||||
@@ -182,7 +179,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
sb.Append($" Entity last sent: {lastSeenTick.Value}");
|
||||
}
|
||||
|
||||
_sawmill.Warning(sb.ToString());
|
||||
Log.Warning(sb.ToString());
|
||||
|
||||
sessionData.LastSeenAt.Clear();
|
||||
|
||||
@@ -231,7 +228,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
private PVSCollection<TIndex> RegisterPVSCollection<TIndex>() where TIndex : IComparable<TIndex>, IEquatable<TIndex>
|
||||
{
|
||||
var collection = new PVSCollection<TIndex>(_sawmill, EntityManager, _transform);
|
||||
var collection = new PVSCollection<TIndex>(Log, EntityManager, _transform);
|
||||
_pvsCollections.Add(collection);
|
||||
return collection;
|
||||
}
|
||||
@@ -334,12 +331,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
if (e.NewStatus == SessionStatus.InGame)
|
||||
{
|
||||
if (!PlayerData.TryAdd(e.Session, new()))
|
||||
_sawmill.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
|
||||
Log.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
|
||||
|
||||
foreach (var pvsCollection in _pvsCollections)
|
||||
{
|
||||
if (!pvsCollection.AddPlayer(e.Session))
|
||||
_sawmill.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
|
||||
Log.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -353,7 +350,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
foreach (var pvsCollection in _pvsCollections)
|
||||
{
|
||||
if (!pvsCollection.RemovePlayer(e.Session))
|
||||
_sawmill.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
|
||||
Log.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
|
||||
}
|
||||
|
||||
if (data.Overflow != null)
|
||||
@@ -822,7 +819,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
#if DEBUG
|
||||
// This happens relatively frequently for the current TickBuffer value, and doesn't really provide any
|
||||
// useful info when not debugging/testing locally. Hence only enable on DEBUG.
|
||||
_sawmill.Debug($"Client {session} exceeded tick buffer.");
|
||||
Log.Debug($"Client {session} exceeded tick buffer.");
|
||||
#endif
|
||||
}
|
||||
else if (oldEntry.Value.Value != lastAcked)
|
||||
@@ -980,7 +977,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
var rep = new EntityStringRepresentation(uid, metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
|
||||
_sawmill.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
|
||||
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1110,7 +1107,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
if (component.Deleted || !component.Initialized)
|
||||
{
|
||||
_sawmill.Error("Entity manager returned deleted or uninitialized components while sending entity data");
|
||||
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
id = tileDefinitionManager[defName].TileId;
|
||||
|
||||
var tile = new Tile(id, flags, variant);
|
||||
chunk.SetTile(x, y, tile);
|
||||
chunk.TrySetTile(x, y, tile, out _, out _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
chunk.SetTile(x, y, source.GetTile(x, y));
|
||||
chunk.TrySetTile(x, y, source.GetTile(x, y), out _, out _);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!-- Try to fix sporadic errors against Robust.Packaging, apparently?? -->
|
||||
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
|
||||
<RobustILLink>true</RobustILLink>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
@@ -40,5 +41,14 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ILLink configuration -->
|
||||
<ItemGroup>
|
||||
<RobustLinkRoots Include="Robust.Server" />
|
||||
<RobustLinkRoots Include="Robust.Shared" />
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Trimming.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace Robust.Server
|
||||
deps.Register<IServerEntityManagerInternal, ServerEntityManager>();
|
||||
deps.Register<IServerGameStateManager, ServerGameStateManager>();
|
||||
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
|
||||
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
|
||||
deps.Register<IServerReplayRecordingManager, ReplayRecordingManager>();
|
||||
deps.Register<IServerNetManager, NetManager>();
|
||||
deps.Register<IStatusHost, StatusHost>();
|
||||
|
||||
@@ -105,11 +105,11 @@ internal sealed partial class StatusHost
|
||||
private Task<bool> SourceAczDictionaryViaFile(AssetPass pass, IPackageLogger logger)
|
||||
{
|
||||
var path = PathHelpers.ExecutableRelativeFile("Content.Client.zip");
|
||||
if (!File.Exists(path))
|
||||
if (!FileHelper.TryOpenFileRead(path, out var fileStream))
|
||||
return Task.FromResult(false);
|
||||
|
||||
_aczSawmill.Info($"StatusHost found client zip: {path}");
|
||||
using var zip = new ZipArchive(File.OpenRead(path), ZipArchiveMode.Read, leaveOpen: false);
|
||||
using var zip = new ZipArchive(fileStream, ZipArchiveMode.Read, leaveOpen: false);
|
||||
SourceAczDictionaryViaZipStream(zip, pass, logger);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Server.Utility
|
||||
{
|
||||
internal static class WindowsTickPeriod
|
||||
{
|
||||
private const uint TIMERR_NOERROR = 0;
|
||||
// This is an actual error code my god.
|
||||
private const uint TIMERR_NOCANDO = 97;
|
||||
|
||||
public static void TimeBeginPeriod(uint period)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeBeginPeriod(period);
|
||||
if (ret != TIMERR_NOERROR)
|
||||
var ret = Windows.timeBeginPeriod(period);
|
||||
if (ret != Windows.TIMERR_NOERROR)
|
||||
throw new InvalidOperationException($"timeBeginPeriod returned error: {ret}");
|
||||
}
|
||||
|
||||
@@ -24,15 +20,9 @@ namespace Robust.Server.Utility
|
||||
if (!OperatingSystem.IsWindows())
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeEndPeriod(period);
|
||||
if (ret != TIMERR_NOERROR)
|
||||
var ret = Windows.timeBeginPeriod(period);
|
||||
if (ret != Windows.TIMERR_NOERROR)
|
||||
throw new InvalidOperationException($"timeEndPeriod returned error: {ret}");
|
||||
}
|
||||
|
||||
[DllImport("Winmm.dll")]
|
||||
private static extern uint timeBeginPeriod(uint uPeriod);
|
||||
|
||||
[DllImport("Winmm.dll")]
|
||||
private static extern uint timeEndPeriod(uint uPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,10 +162,10 @@ namespace Robust.Shared.Scripting
|
||||
public abstract void show(object obj);
|
||||
|
||||
#region EntityManager proxy methods
|
||||
public T Comp<T>(EntityUid uid)
|
||||
public T Comp<T>(EntityUid uid) where T : Component
|
||||
=> ent.GetComponent<T>(uid);
|
||||
|
||||
public bool TryComp<T>(EntityUid uid, out T? comp)
|
||||
public bool TryComp<T>(EntityUid uid, out T? comp) where T : Component
|
||||
=> ent.TryGetComponent(uid, out comp);
|
||||
|
||||
public bool HasComp<T>(EntityUid uid)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Audio.Midi
|
||||
@@ -9,6 +10,9 @@ namespace Robust.Shared.Audio.Midi
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct RobustMidiEvent
|
||||
{
|
||||
public const int MaxChannels = 16;
|
||||
public const int PercussionChannel = 9;
|
||||
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
@@ -49,6 +53,17 @@ namespace Robust.Shared.Audio.Midi
|
||||
Tick = tick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones another event but with a different tick value.
|
||||
/// </summary>
|
||||
public RobustMidiEvent(RobustMidiEvent ev, uint tick)
|
||||
{
|
||||
Status = ev.Status;
|
||||
Data1 = ev.Data1;
|
||||
Data2 = ev.Data2;
|
||||
Tick = tick;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} >> CHANNEL: 0x{Channel:X} || COMMAND: 0x{Command:X} {MidiCommand} || DATA1: 0x{Data1:X} || DATA2: 0x{Data2:X} || TICK: {Tick} <<";
|
||||
|
||||
@@ -319,6 +319,12 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> SysGCCollectStart =
|
||||
CVarDef.Create("sys.gc_collect_start", true);
|
||||
|
||||
/// <summary>
|
||||
/// Use precise sleeping methods in the game loop.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> SysPreciseSleep =
|
||||
CVarDef.Create("sys.precise_sleep", true);
|
||||
|
||||
/*
|
||||
* METRICS
|
||||
*/
|
||||
@@ -1258,6 +1264,15 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MidiVolume =
|
||||
CVarDef.Create("midi.volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> MidiMinRendererParallel =
|
||||
CVarDef.Create("midi.min_renderers_parallel_update", 3, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<float> MidiPositionUpdateDelay =
|
||||
CVarDef.Create("midi.position_update_delay", 0.125f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<float> MidiOcclusionUpdateDelay =
|
||||
CVarDef.Create("midi.occlusion_update_delay", 0.25f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* HUB
|
||||
* CVars related to public master server hub
|
||||
|
||||
@@ -15,8 +15,16 @@ internal sealed class RecursiveMoveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
bool Subscribed = false;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
internal void AddSubscription()
|
||||
{
|
||||
if (Subscribed)
|
||||
@@ -34,14 +42,12 @@ internal sealed class RecursiveMoveSystem : EntitySystem
|
||||
DebugTools.Assert(!_mapManager.IsMap(args.Sender));
|
||||
DebugTools.Assert(!_mapManager.IsGrid(args.Sender));
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
AnythingMovedSubHandler(args.Sender, args.Component, xformQuery);
|
||||
AnythingMovedSubHandler(args.Sender, args.Component);
|
||||
}
|
||||
|
||||
private void AnythingMovedSubHandler(
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
TransformComponent xform)
|
||||
{
|
||||
// TODO maybe use a c# event? This event gets raised a lot.
|
||||
// Would probably help with server performance and is also the main bottleneck for replay scrubbing.
|
||||
@@ -55,8 +61,8 @@ internal sealed class RecursiveMoveSystem : EntitySystem
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
if (xformQuery.TryGetComponent(child.Value, out var childXform))
|
||||
AnythingMovedSubHandler(child.Value, childXform, xformQuery);
|
||||
if (_xformQuery.TryGetComponent(child.Value, out var childXform))
|
||||
AnythingMovedSubHandler(child.Value, childXform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ internal sealed class TeleportCommand : LocalizedCommands
|
||||
|
||||
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
|
||||
{
|
||||
var gridPos = grid.WorldToLocal(position);
|
||||
var gridPos = xformSystem.GetInvWorldMatrix(gridUid).Transform(position);
|
||||
|
||||
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
|
||||
}
|
||||
|
||||
@@ -54,28 +54,6 @@ namespace Robust.Shared.Containers
|
||||
Containers.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
// naive implementation that just sends the full state of the component
|
||||
Dictionary<string, ContainerManagerComponentState.ContainerData> containerSet = new(Containers.Count);
|
||||
|
||||
foreach (var container in Containers.Values)
|
||||
{
|
||||
var uidArr = new EntityUid[container.ContainedEntities.Count];
|
||||
|
||||
for (var index = 0; index < container.ContainedEntities.Count; index++)
|
||||
{
|
||||
uidArr[index] = container.ContainedEntities[index];
|
||||
}
|
||||
|
||||
var sContainer = new ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr);
|
||||
containerSet.Add(container.ID, sContainer);
|
||||
}
|
||||
|
||||
return new ContainerManagerComponentState(containerSet);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T MakeContainer<T>(string id)
|
||||
where T : IContainer
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -23,6 +24,28 @@ namespace Robust.Shared.Containers
|
||||
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentStartup>(OnStartupValidation);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentGetState>(OnContainerGetState);
|
||||
}
|
||||
|
||||
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
|
||||
{
|
||||
// naive implementation that just sends the full state of the component
|
||||
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet = new(component.Containers.Count);
|
||||
|
||||
foreach (var container in component.Containers.Values)
|
||||
{
|
||||
var uidArr = new EntityUid[container.ContainedEntities.Count];
|
||||
|
||||
for (var index = 0; index < container.ContainedEntities.Count; index++)
|
||||
{
|
||||
uidArr[index] = container.ContainedEntities[index];
|
||||
}
|
||||
|
||||
var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr);
|
||||
containerSet.Add(container.ID, sContainer);
|
||||
}
|
||||
|
||||
args.State = new ContainerManagerComponent.ContainerManagerComponentState(containerSet);
|
||||
}
|
||||
|
||||
// TODO: Make ContainerManagerComponent ECS and make these proxy methods the real deal.
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Robust.Shared.ContentPack
|
||||
_config = Task.Run(() => LoadConfig(sawmill));
|
||||
}
|
||||
|
||||
private Resolver CreateResolver()
|
||||
internal Resolver CreateResolver()
|
||||
{
|
||||
var dotnetDir = Path.GetDirectoryName(typeof(int).Assembly.Location)!;
|
||||
var ourPath = typeof(AssemblyTypeChecker).Assembly.Location;
|
||||
@@ -100,6 +100,19 @@ namespace Robust.Shared.ContentPack
|
||||
/// <param name="assembly">Assembly to load.</param>
|
||||
/// <returns></returns>
|
||||
public bool CheckAssembly(Stream assembly)
|
||||
{
|
||||
using var resolver = CreateResolver();
|
||||
|
||||
return CheckAssembly(assembly, resolver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the assembly for any illegal types. Any types not on the white list
|
||||
/// will cause the assembly to be rejected.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly to load.</param>
|
||||
/// <returns></returns>
|
||||
public bool CheckAssembly(Stream assembly, Resolver resolver)
|
||||
{
|
||||
if (WouldNoOp)
|
||||
{
|
||||
@@ -110,8 +123,7 @@ namespace Robust.Shared.ContentPack
|
||||
_sawmill.Debug("Checking assembly...");
|
||||
var fullStopwatch = Stopwatch.StartNew();
|
||||
|
||||
var resolver = CreateResolver();
|
||||
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true);
|
||||
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true, PEStreamOptions.PrefetchEntireImage);
|
||||
var reader = peReader.GetMetadataReader();
|
||||
|
||||
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
|
||||
@@ -856,7 +868,7 @@ namespace Robust.Shared.ContentPack
|
||||
return handle.IsNil ? null : reader.GetString(handle);
|
||||
}
|
||||
|
||||
private sealed class Resolver : IResolver
|
||||
internal sealed class Resolver : IResolver, IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, PEReader?> _dictionary = new();
|
||||
private readonly AssemblyTypeChecker _parent;
|
||||
@@ -883,12 +895,10 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
var path = Path.Combine(diskLoadPath, dllName);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
if (!FileHelper.TryOpenFileRead(path, out var fileStream))
|
||||
continue;
|
||||
}
|
||||
|
||||
return ModLoader.MakePEReader(File.OpenRead(path));
|
||||
return ModLoader.MakePEReader(fileStream);
|
||||
}
|
||||
|
||||
foreach (var resLoadPath in _resLoadPaths)
|
||||
@@ -910,6 +920,14 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
return _dictionary.GetOrAdd(simpleName, ResolveCore);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var reader in _dictionary.Values)
|
||||
{
|
||||
reader?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TypeProvider : ISignatureTypeProvider<MType, int>
|
||||
|
||||
@@ -46,16 +46,11 @@ namespace Robust.Shared.ContentPack
|
||||
public bool TryGetFile(ResPath relPath, [NotNullWhen(true)] out Stream? stream)
|
||||
{
|
||||
var path = GetPath(relPath);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
stream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
CheckPathCasing(relPath);
|
||||
|
||||
stream = File.OpenRead(path);
|
||||
return true;
|
||||
var ret = FileHelper.TryOpenFileRead(path, out var fStream);
|
||||
stream = fStream;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool FileExists(ResPath relPath)
|
||||
|
||||
@@ -118,13 +118,14 @@ namespace Robust.Shared.ContentPack
|
||||
var checkerSw = Stopwatch.StartNew();
|
||||
|
||||
var typeChecker = MakeTypeChecker();
|
||||
var resolver = typeChecker.CreateResolver();
|
||||
|
||||
Parallel.ForEach(files, pair =>
|
||||
{
|
||||
var (name, (path, _)) = pair;
|
||||
|
||||
using var stream = _res.ContentFileRead(path);
|
||||
if (!typeChecker.CheckAssembly(stream))
|
||||
if (!typeChecker.CheckAssembly(stream, resolver))
|
||||
{
|
||||
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
|
||||
}
|
||||
@@ -426,12 +427,15 @@ namespace Robust.Shared.ContentPack
|
||||
};
|
||||
}
|
||||
|
||||
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false)
|
||||
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false, PEStreamOptions options=PEStreamOptions.Default)
|
||||
{
|
||||
if (!stream.CanSeek)
|
||||
stream = leaveOpen ? stream.CopyToMemoryStream() : stream.ConsumeToMemoryStream();
|
||||
|
||||
return new PEReader(stream, leaveOpen ? PEStreamOptions.LeaveOpen : default);
|
||||
if (leaveOpen)
|
||||
options |= PEStreamOptions.LeaveOpen;
|
||||
|
||||
return new PEReader(stream, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,10 @@
|
||||
/// This update is called after the main state manager on render frames, thus only applies to the client.
|
||||
/// </summary>
|
||||
FramePostEngine,
|
||||
|
||||
/// <summary>
|
||||
/// Ran after processing network packets and pending asynchronous tasks.
|
||||
/// </summary>
|
||||
InputPostEngine,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +305,7 @@ Types:
|
||||
ConcurrentQueue`1: { All: True }
|
||||
ConcurrentStack`1: { All: True }
|
||||
System.Collections:
|
||||
BitArray: { All: True }
|
||||
IEnumerable: { All: True }
|
||||
IEnumerator: { All: True }
|
||||
IReadOnlyList`1: { All: True }
|
||||
|
||||
@@ -75,14 +75,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
LifeStage = ComponentLifeStage.Initializing;
|
||||
entManager.EventBus.RaiseComponentEvent(this, type, CompInitInstance);
|
||||
Initialize();
|
||||
|
||||
#if DEBUG
|
||||
if (LifeStage != ComponentLifeStage.Initialized)
|
||||
{
|
||||
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Initialize)} in derived method.");
|
||||
}
|
||||
#endif
|
||||
LifeStage = ComponentLifeStage.Initialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,14 +88,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
LifeStage = ComponentLifeStage.Starting;
|
||||
entManager.EventBus.RaiseComponentEvent(this, CompStartupInstance);
|
||||
Startup();
|
||||
|
||||
#if DEBUG
|
||||
if (LifeStage != ComponentLifeStage.Running)
|
||||
{
|
||||
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Startup)} in derived method.");
|
||||
}
|
||||
#endif
|
||||
LifeStage = ComponentLifeStage.Running;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,26 +156,6 @@ namespace Robust.Shared.GameObjects
|
||||
private static readonly ComponentShutdown CompShutdownInstance = new();
|
||||
private static readonly ComponentRemove CompRemoveInstance = new();
|
||||
|
||||
/// <summary>
|
||||
/// Called when all of the entity's other components have been added and are available,
|
||||
/// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct.
|
||||
/// </summary>
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
LifeStage = ComponentLifeStage.Initialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Components are allowed to remove themselves in their own Startup function.
|
||||
/// </remarks>
|
||||
protected virtual void Startup()
|
||||
{
|
||||
LifeStage = ComponentLifeStage.Running;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component is removed from an entity.
|
||||
/// Shuts down the component.
|
||||
@@ -208,21 +174,6 @@ namespace Robust.Shared.GameObjects
|
||||
entManager.Dirty(this);
|
||||
}
|
||||
|
||||
private static readonly ComponentState DefaultComponentState = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ComponentState GetComponentState()
|
||||
{
|
||||
DebugTools.Assert(
|
||||
Attribute.GetCustomAttribute(GetType(), typeof(NetworkedComponentAttribute)) != null,
|
||||
$"Calling base {nameof(GetComponentState)} without being networked.");
|
||||
|
||||
return DefaultComponentState;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void HandleComponentState(ComponentState? curState, ComponentState? nextState) { }
|
||||
|
||||
// these two methods clear the LastModifiedTick/CreationTick to mark it as "not different from prototype load".
|
||||
// This is used as optimization in the game state system to avoid sending redundant component data.
|
||||
internal virtual void ClearTicks()
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -23,7 +24,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Mapping of component name to type.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, ComponentRegistration> names = new();
|
||||
private readonly Dictionary<string, ComponentRegistration> _names = new();
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of lowercase component names to their registration.
|
||||
@@ -107,11 +108,11 @@ namespace Robust.Shared.GameObjects
|
||||
IgnoredComponentNames.Remove(name);
|
||||
}
|
||||
|
||||
if (names.ContainsKey(name))
|
||||
if (_names.ContainsKey(name))
|
||||
{
|
||||
if (!overwrite)
|
||||
{
|
||||
throw new InvalidOperationException($"{name} is already registered, previous: {names[name]}");
|
||||
throw new InvalidOperationException($"{name} is already registered, previous: {_names[name]}");
|
||||
}
|
||||
|
||||
RemoveComponent(name);
|
||||
@@ -130,7 +131,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var registration = new ComponentRegistration(name, type, idx);
|
||||
|
||||
names[name] = registration;
|
||||
_names[name] = registration;
|
||||
_lowerCaseNames[lowerCaseName] = name;
|
||||
types[type] = registration;
|
||||
CompIdx.AssignArray(ref _array, idx, registration);
|
||||
@@ -213,7 +214,7 @@ namespace Robust.Shared.GameObjects
|
||||
throw new InvalidOperationException($"Cannot add {name} to ignored components: It is already registered as ignored");
|
||||
}
|
||||
|
||||
if (names.ContainsKey(name))
|
||||
if (_names.ContainsKey(name))
|
||||
{
|
||||
if (!overwrite)
|
||||
{
|
||||
@@ -232,9 +233,9 @@ namespace Robust.Shared.GameObjects
|
||||
if (_networkedComponents is not null)
|
||||
throw new ComponentRegistrationLockException();
|
||||
|
||||
var registration = names[name];
|
||||
var registration = _names[name];
|
||||
|
||||
names.Remove(registration.Name);
|
||||
_names.Remove(registration.Name);
|
||||
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
|
||||
types.Remove(registration.Type);
|
||||
}
|
||||
@@ -246,7 +247,7 @@ namespace Robust.Shared.GameObjects
|
||||
componentName = lowerCaseName;
|
||||
}
|
||||
|
||||
if (names.ContainsKey(componentName))
|
||||
if (_names.ContainsKey(componentName))
|
||||
{
|
||||
return ComponentAvailability.Available;
|
||||
}
|
||||
@@ -313,7 +314,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
try
|
||||
{
|
||||
return names[componentName];
|
||||
return _names[componentName];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
@@ -321,6 +322,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public string GetComponentName(Type componentType)
|
||||
{
|
||||
return GetRegistration(componentType).Name;
|
||||
@@ -374,7 +376,7 @@ namespace Robust.Shared.GameObjects
|
||||
componentName = lowerCaseName;
|
||||
}
|
||||
|
||||
if (names.TryGetValue(componentName, out var tempRegistration))
|
||||
if (_names.TryGetValue(componentName, out var tempRegistration))
|
||||
{
|
||||
registration = tempRegistration;
|
||||
return true;
|
||||
@@ -474,9 +476,9 @@ namespace Robust.Shared.GameObjects
|
||||
// component names are 1:1 with component concrete types
|
||||
|
||||
// a subset of component names are networked
|
||||
var networkedRegs = new List<ComponentRegistration>(names.Count);
|
||||
var networkedRegs = new List<ComponentRegistration>(_names.Count);
|
||||
|
||||
foreach (var kvRegistration in names)
|
||||
foreach (var kvRegistration in _names)
|
||||
{
|
||||
var registration = kvRegistration.Value;
|
||||
if (Attribute.GetCustomAttribute(registration.Type, typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute)
|
||||
|
||||
@@ -94,7 +94,6 @@ namespace Robust.Shared.GameObjects
|
||||
[Obsolete("Use a system update loop instead")]
|
||||
public static void SpawnRepeatingTimer(this EntityUid entity, int milliseconds, Action onFired, CancellationToken cancellationToken)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
entity
|
||||
.EnsureTimerComponent()
|
||||
.SpawnRepeating(milliseconds, onFired, cancellationToken);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -32,6 +33,8 @@ namespace Robust.Shared.GameObjects
|
||||
private const int EntityCapacity = 1024;
|
||||
private const int NetComponentCapacity = 8;
|
||||
|
||||
private static readonly ComponentState DefaultComponentState = new();
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<ushort, Component>> _netComponents
|
||||
= new(EntityCapacity);
|
||||
|
||||
@@ -700,15 +703,15 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetComponent<T>(EntityUid uid)
|
||||
public T GetComponent<T>(EntityUid uid) where T : IComponent
|
||||
{
|
||||
var dict = _entTraitArray[CompIdx.ArrayIndex<T>()];
|
||||
DebugTools.Assert(dict != null, $"Unknown component: {typeof(T).Name}");
|
||||
if (dict!.TryGetValue(uid, out var comp))
|
||||
{
|
||||
if (!comp.Deleted)
|
||||
if (!comp!.Deleted)
|
||||
{
|
||||
return (T)(IComponent)comp;
|
||||
return (T)(IComponent) comp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1034,8 +1037,7 @@ namespace Robust.Shared.GameObjects
|
||||
where TComp1 : Component
|
||||
{
|
||||
var trait1 = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
|
||||
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
|
||||
return new EntityQueryEnumerator<TComp1>(trait1, metaTraits);
|
||||
return new EntityQueryEnumerator<TComp1>(trait1, _metaQuery);
|
||||
}
|
||||
|
||||
public EntityQueryEnumerator<TComp1, TComp2> EntityQueryEnumerator<TComp1, TComp2>()
|
||||
@@ -1044,8 +1046,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var trait1 = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
|
||||
var trait2 = _entTraitArray[CompIdx.ArrayIndex<TComp2>()];
|
||||
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
|
||||
return new EntityQueryEnumerator<TComp1, TComp2>(trait1, trait2, metaTraits);
|
||||
return new EntityQueryEnumerator<TComp1, TComp2>(trait1, trait2, _metaQuery);
|
||||
}
|
||||
|
||||
public EntityQueryEnumerator<TComp1, TComp2, TComp3> EntityQueryEnumerator<TComp1, TComp2, TComp3>()
|
||||
@@ -1056,8 +1057,7 @@ namespace Robust.Shared.GameObjects
|
||||
var trait1 = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
|
||||
var trait2 = _entTraitArray[CompIdx.ArrayIndex<TComp2>()];
|
||||
var trait3 = _entTraitArray[CompIdx.ArrayIndex<TComp3>()];
|
||||
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
|
||||
return new EntityQueryEnumerator<TComp1, TComp2, TComp3>(trait1, trait2, trait3, metaTraits);
|
||||
return new EntityQueryEnumerator<TComp1, TComp2, TComp3>(trait1, trait2, trait3, _metaQuery);
|
||||
}
|
||||
|
||||
public EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4> EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>()
|
||||
@@ -1070,8 +1070,8 @@ namespace Robust.Shared.GameObjects
|
||||
var trait2 = _entTraitArray[CompIdx.ArrayIndex<TComp2>()];
|
||||
var trait3 = _entTraitArray[CompIdx.ArrayIndex<TComp3>()];
|
||||
var trait4 = _entTraitArray[CompIdx.ArrayIndex<TComp4>()];
|
||||
var metaTraits = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
|
||||
return new EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>(trait1, trait2, trait3, trait4, metaTraits);
|
||||
|
||||
return new EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>(trait1, trait2, trait3, trait4, _metaQuery);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1091,15 +1091,11 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
var metaComps = _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()];
|
||||
|
||||
foreach (var t1Comp in comps!.Values)
|
||||
foreach (var t1Comp in comps.Values)
|
||||
{
|
||||
if (t1Comp.Deleted || !metaComps.TryGetValue(t1Comp.Owner, out var metaComp)) continue;
|
||||
if (t1Comp.Deleted || !_metaQuery.TryGetComponentInternal(t1Comp.Owner, out var metaComp)) continue;
|
||||
|
||||
var meta = (MetaDataComponent)metaComp;
|
||||
|
||||
if (meta.EntityPaused) continue;
|
||||
if (metaComp.EntityPaused) continue;
|
||||
|
||||
yield return (T)(object)t1Comp;
|
||||
}
|
||||
@@ -1304,7 +1300,7 @@ namespace Robust.Shared.GameObjects
|
||||
var getState = new ComponentGetState(session, fromTick);
|
||||
eventBus.RaiseComponentEvent(component, ref getState);
|
||||
|
||||
return getState.State ?? component.GetComponentState();
|
||||
return getState.State ?? DefaultComponentState;
|
||||
}
|
||||
|
||||
public bool CanGetComponentState(IEventBus eventBus, IComponent component, ICommonSession player)
|
||||
@@ -1369,6 +1365,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1 GetComponent(EntityUid uid)
|
||||
{
|
||||
if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted)
|
||||
@@ -1378,6 +1375,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
|
||||
{
|
||||
if (uid == null)
|
||||
@@ -1390,6 +1388,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool TryGetComponent(EntityUid uid, [NotNullWhen(true)] out TComp1? component)
|
||||
{
|
||||
if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted)
|
||||
@@ -1403,12 +1402,14 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool HasComponent(EntityUid uid)
|
||||
{
|
||||
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
|
||||
{
|
||||
if (component != null)
|
||||
@@ -1430,6 +1431,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1? CompOrNull(EntityUid uid)
|
||||
{
|
||||
if (TryGetComponent(uid, out var comp))
|
||||
@@ -1437,6 +1439,103 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#region Internal
|
||||
|
||||
/// <summary>
|
||||
/// Elides the component.Deleted check of <see cref="GetComponent"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
internal TComp1 GetComponentInternal(EntityUid uid)
|
||||
{
|
||||
if (_traitDict.TryGetValue(uid, out var comp))
|
||||
return (TComp1) comp;
|
||||
|
||||
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elides the component.Deleted check of <see cref="TryGetComponent(System.Nullable{Robust.Shared.GameObjects.EntityUid},out TComp1?)"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
internal bool TryGetComponentInternal([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
|
||||
{
|
||||
if (uid == null)
|
||||
{
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetComponentInternal(uid.Value, out component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elides the component.Deleted check of <see cref="TryGetComponent(System.Nullable{Robust.Shared.GameObjects.EntityUid},out TComp1?)"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
internal bool TryGetComponentInternal(EntityUid uid, [NotNullWhen(true)] out TComp1? component)
|
||||
{
|
||||
if (_traitDict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
component = (TComp1) comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elides the component.Deleted check of <see cref="HasComponent"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
internal bool HasComponentInternal(EntityUid uid)
|
||||
{
|
||||
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elides the component.Deleted check of <see cref="Resolve"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
internal bool ResolveInternal(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
|
||||
{
|
||||
if (component != null)
|
||||
{
|
||||
DebugTools.Assert(uid == component.Owner, "Specified Entity is not the component's Owner!");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_traitDict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
component = (TComp1)comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (logMissing)
|
||||
_sawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {uid}!\n{new StackTrace(1, true)}");
|
||||
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Elides the component.Deleted check of <see cref="CompOrNull"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
internal TComp1? CompOrNullInternal(EntityUid uid)
|
||||
{
|
||||
if (TryGetComponent(uid, out var comp))
|
||||
return comp;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Query
|
||||
@@ -1448,14 +1547,14 @@ namespace Robust.Shared.GameObjects
|
||||
where TComp1 : Component
|
||||
{
|
||||
private Dictionary<EntityUid, Component>.Enumerator _traitDict;
|
||||
private readonly Dictionary<EntityUid, Component> _metaDict;
|
||||
private readonly EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public EntityQueryEnumerator(
|
||||
Dictionary<EntityUid, Component> traitDict,
|
||||
Dictionary<EntityUid, Component> metaDict)
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
_traitDict = traitDict.GetEnumerator();
|
||||
_metaDict = metaDict;
|
||||
_metaQuery = metaQuery;
|
||||
}
|
||||
|
||||
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1)
|
||||
@@ -1476,7 +1575,7 @@ namespace Robust.Shared.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
|
||||
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1508,16 +1607,16 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
private Dictionary<EntityUid, Component>.Enumerator _traitDict;
|
||||
private readonly Dictionary<EntityUid, Component> _traitDict2;
|
||||
private readonly Dictionary<EntityUid, Component> _metaDict;
|
||||
private readonly EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public EntityQueryEnumerator(
|
||||
Dictionary<EntityUid, Component> traitDict,
|
||||
Dictionary<EntityUid, Component> traitDict2,
|
||||
Dictionary<EntityUid, Component> metaDict)
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
_traitDict = traitDict.GetEnumerator();
|
||||
_traitDict2 = traitDict2;
|
||||
_metaDict = metaDict;
|
||||
_metaQuery = metaQuery;
|
||||
}
|
||||
|
||||
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2)
|
||||
@@ -1539,7 +1638,7 @@ namespace Robust.Shared.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
|
||||
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1579,18 +1678,18 @@ namespace Robust.Shared.GameObjects
|
||||
private Dictionary<EntityUid, Component>.Enumerator _traitDict;
|
||||
private readonly Dictionary<EntityUid, Component> _traitDict2;
|
||||
private readonly Dictionary<EntityUid, Component> _traitDict3;
|
||||
private readonly Dictionary<EntityUid, Component> _metaDict;
|
||||
private readonly EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public EntityQueryEnumerator(
|
||||
Dictionary<EntityUid, Component> traitDict,
|
||||
Dictionary<EntityUid, Component> traitDict2,
|
||||
Dictionary<EntityUid, Component> traitDict3,
|
||||
Dictionary<EntityUid, Component> metaDict)
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
_traitDict = traitDict.GetEnumerator();
|
||||
_traitDict2 = traitDict2;
|
||||
_traitDict3 = traitDict3;
|
||||
_metaDict = metaDict;
|
||||
_metaQuery = metaQuery;
|
||||
}
|
||||
|
||||
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3)
|
||||
@@ -1613,7 +1712,7 @@ namespace Robust.Shared.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
|
||||
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1664,20 +1763,20 @@ namespace Robust.Shared.GameObjects
|
||||
private readonly Dictionary<EntityUid, Component> _traitDict2;
|
||||
private readonly Dictionary<EntityUid, Component> _traitDict3;
|
||||
private readonly Dictionary<EntityUid, Component> _traitDict4;
|
||||
private readonly Dictionary<EntityUid, Component> _metaDict;
|
||||
private readonly EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public EntityQueryEnumerator(
|
||||
Dictionary<EntityUid, Component> traitDict,
|
||||
Dictionary<EntityUid, Component> traitDict2,
|
||||
Dictionary<EntityUid, Component> traitDict3,
|
||||
Dictionary<EntityUid, Component> traitDict4,
|
||||
Dictionary<EntityUid, Component> metaDict)
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
_traitDict = traitDict.GetEnumerator();
|
||||
_traitDict2 = traitDict2;
|
||||
_traitDict3 = traitDict3;
|
||||
_traitDict4 = traitDict4;
|
||||
_metaDict = metaDict;
|
||||
_metaQuery = metaQuery;
|
||||
}
|
||||
|
||||
public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3, [NotNullWhen(true)] out TComp4? comp4)
|
||||
@@ -1701,7 +1800,7 @@ namespace Robust.Shared.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_metaDict.TryGetValue(current.Key, out var metaComp) || ((MetaDataComponent)metaComp).EntityPaused)
|
||||
if (!_metaQuery.TryGetComponentInternal(current.Key, out var metaComp) || metaComp.EntityPaused)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public delegate void EntityUidQueryCallback(EntityUid uid);
|
||||
|
||||
public delegate void ComponentQueryCallback<T>(EntityUid uid, T component) where T : Component;
|
||||
|
||||
/// <inheritdoc />
|
||||
[Virtual]
|
||||
public partial class EntityManager : IEntityManager
|
||||
@@ -36,6 +38,9 @@ namespace Robust.Shared.GameObjects
|
||||
// positions on spawn....
|
||||
private SharedTransformSystem _xforms = default!;
|
||||
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
#endregion Dependencies
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -75,7 +80,8 @@ namespace Robust.Shared.GameObjects
|
||||
public event Action<EntityUid>? EntityDirtied; // only raised after initialization
|
||||
|
||||
private string _xformName = string.Empty;
|
||||
private string _metaName = string.Empty;
|
||||
|
||||
private SharedMapSystem _mapSystem = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private ISawmill _resolveSawmill = default!;
|
||||
@@ -102,7 +108,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
InitializeComponents();
|
||||
_xformName = _componentFactory.GetComponentName(typeof(TransformComponent));
|
||||
_metaName = _componentFactory.GetComponentName(typeof(MetaDataComponent));
|
||||
_sawmill = LogManager.GetSawmill("entity");
|
||||
_resolveSawmill = LogManager.GetSawmill("resolve");
|
||||
|
||||
@@ -114,7 +119,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public bool IsDefault(EntityUid uid)
|
||||
{
|
||||
if (!TryGetComponent<MetaDataComponent>(uid, out var metadata) || metadata.EntityPrototype == null)
|
||||
if (!_metaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null)
|
||||
return false;
|
||||
|
||||
var prototype = metadata.EntityPrototype;
|
||||
@@ -221,7 +226,10 @@ namespace Robust.Shared.GameObjects
|
||||
_entitySystemManager.Initialize();
|
||||
Started = true;
|
||||
_eventBus.CalcOrdering();
|
||||
_xforms = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
_mapSystem = System<SharedMapSystem>();
|
||||
_xforms = System<SharedTransformSystem>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
@@ -308,7 +316,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (coordinates.IsValid(this))
|
||||
{
|
||||
_xforms.SetCoordinates(newEntity, GetComponent<TransformComponent>(newEntity), coordinates, unanchor: false);
|
||||
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
|
||||
}
|
||||
|
||||
return newEntity;
|
||||
@@ -318,7 +326,7 @@ namespace Robust.Shared.GameObjects
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, default, overrides);
|
||||
var transform = GetComponent<TransformComponent>(newEntity);
|
||||
var transform = _xformQuery.GetComponent(newEntity);
|
||||
|
||||
if (coordinates.MapId == MapId.Nullspace)
|
||||
{
|
||||
@@ -335,7 +343,7 @@ namespace Robust.Shared.GameObjects
|
||||
EntityCoordinates coords;
|
||||
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
|
||||
{
|
||||
coords = new EntityCoordinates(gridUid, grid.WorldToLocal(coordinates.Position));
|
||||
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
|
||||
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
|
||||
}
|
||||
else
|
||||
@@ -675,29 +683,20 @@ namespace Robust.Shared.GameObjects
|
||||
/// Allocates an entity and stores it but does not load components or do initialization.
|
||||
/// </summary>
|
||||
private protected EntityUid AllocEntity(
|
||||
string? prototypeName,
|
||||
EntityPrototype? prototype,
|
||||
out MetaDataComponent metadata,
|
||||
EntityUid uid = default)
|
||||
{
|
||||
EntityPrototype? prototype = null;
|
||||
if (!string.IsNullOrWhiteSpace(prototypeName))
|
||||
{
|
||||
// If the prototype doesn't exist then we throw BEFORE we allocate the entity.
|
||||
prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
}
|
||||
|
||||
var entity = AllocEntity(out metadata, uid);
|
||||
|
||||
metadata._entityPrototype = prototype;
|
||||
Dirty(metadata, metadata);
|
||||
|
||||
Dirty(entity, metadata, metadata);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates an entity and stores it but does not load components or do initialization.
|
||||
/// </summary>
|
||||
private protected EntityUid AllocEntity(out MetaDataComponent metadata, EntityUid uid = default)
|
||||
private EntityUid AllocEntity(out MetaDataComponent metadata, EntityUid uid = default)
|
||||
{
|
||||
if (uid == default)
|
||||
{
|
||||
@@ -735,7 +734,9 @@ namespace Robust.Shared.GameObjects
|
||||
if (prototypeName == null)
|
||||
return AllocEntity(out _, uid);
|
||||
|
||||
var entity = AllocEntity(prototypeName, out var metadata, uid);
|
||||
PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype);
|
||||
|
||||
var entity = AllocEntity(prototype, out var metadata, uid);
|
||||
try
|
||||
{
|
||||
EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, ComponentFactory, this, _serManager, context);
|
||||
@@ -752,7 +753,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
|
||||
{
|
||||
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
|
||||
EntityPrototype.LoadEntity(_metaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
|
||||
}
|
||||
|
||||
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
|
||||
@@ -764,12 +765,12 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
try
|
||||
{
|
||||
var meta = GetComponent<MetaDataComponent>(entity);
|
||||
var meta = _metaQuery.GetComponent(entity);
|
||||
InitializeEntity(entity, meta);
|
||||
StartEntity(entity);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_mapManager.IsMapInitialized(mapId ?? GetComponent<TransformComponent>(entity).MapID))
|
||||
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
|
||||
RunMapInit(entity, meta);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -398,7 +398,7 @@ public partial class EntitySystem
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.GetComponent<T>"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T Comp<T>(EntityUid uid) where T : class, IComponent
|
||||
protected T Comp<T>(EntityUid uid) where T : Component
|
||||
{
|
||||
return EntityManager.GetComponent<T>(uid);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -63,24 +60,5 @@ namespace Robust.Shared.GameObjects
|
||||
/// This is the last game tick Dirty() was called.
|
||||
/// </summary>
|
||||
GameTick LastModifiedTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the component's state for replicating on the client.
|
||||
/// </summary>
|
||||
/// <returns>ComponentState object</returns>
|
||||
ComponentState GetComponentState();
|
||||
|
||||
/// <summary>
|
||||
/// Handles an incoming component state from the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function should only be called on the client.
|
||||
/// Both, one, or neither of the two states can be null.
|
||||
/// On the next tick, curState will be nextState.
|
||||
/// Passing null for both arguments should do nothing.
|
||||
/// </remarks>
|
||||
/// <param name="curState">Current component state for this tick.</param>
|
||||
/// <param name="nextState">Next component state for the next tick.</param>
|
||||
void HandleComponentState(ComponentState? curState, ComponentState? nextState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -153,6 +154,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <exception cref="UnknownComponentException">
|
||||
/// Thrown if no component exists with the given type <see cref="componentType"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
string GetComponentName(Type componentType);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -236,7 +236,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <typeparam name="T">A trait or type of a component to retrieve.</typeparam>
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <returns>The component of Type from the Entity.</returns>
|
||||
T GetComponent<T>(EntityUid uid);
|
||||
T GetComponent<T>(EntityUid uid) where T : IComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Shared.GameObjects
|
||||
else if (TryComp(uid, out PhysicsComponent? physics))
|
||||
_physics.SetCanCollide(uid, true, body: physics);
|
||||
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, CollisionWakeComponent component, ref ComponentHandleState args)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
@@ -26,12 +26,10 @@ public sealed partial class EntityLookupSystem
|
||||
EntityUid lookupUid,
|
||||
HashSet<EntityUid> intersecting,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<BroadphaseComponent> lookupQuery,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
LookupFlags flags)
|
||||
{
|
||||
var lookup = lookupQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
|
||||
var localAABB = invMatrix.TransformBox(worldAABB);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
@@ -79,12 +77,10 @@ public sealed partial class EntityLookupSystem
|
||||
EntityUid lookupUid,
|
||||
HashSet<EntityUid> intersecting,
|
||||
Box2Rotated worldBounds,
|
||||
LookupFlags flags,
|
||||
EntityQuery<BroadphaseComponent> lookupQuery,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
LookupFlags flags)
|
||||
{
|
||||
var lookup = lookupQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
|
||||
// We don't just use CalcBoundingBox because the transformed bounds might be tighter.
|
||||
var localAABB = invMatrix.TransformBox(worldBounds);
|
||||
|
||||
@@ -132,12 +128,10 @@ public sealed partial class EntityLookupSystem
|
||||
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<BroadphaseComponent> lookupQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityUid? ignored = null)
|
||||
{
|
||||
var lookup = lookupQuery.GetComponent(lookupUid);
|
||||
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB);
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldAABB);
|
||||
var state = (ignored, found: false);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
@@ -197,12 +191,10 @@ public sealed partial class EntityLookupSystem
|
||||
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
|
||||
Box2Rotated worldBounds,
|
||||
LookupFlags flags,
|
||||
EntityQuery<BroadphaseComponent> lookupQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityUid? ignored = null)
|
||||
{
|
||||
var lookup = lookupQuery.GetComponent(lookupUid);
|
||||
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds);
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldBounds);
|
||||
var state = (ignored, found: false);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
@@ -259,35 +251,35 @@ public sealed partial class EntityLookupSystem
|
||||
return state.found;
|
||||
}
|
||||
|
||||
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd, EntityQuery<TransformComponent> xformQuery)
|
||||
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
|
||||
{
|
||||
var childEnumerator = xformQuery.GetComponent(uid).ChildEnumerator;
|
||||
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
toAdd.Add(child.Value);
|
||||
RecursiveAdd(child.Value, ref toAdd, xformQuery);
|
||||
RecursiveAdd(child.Value, ref toAdd);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags, EntityQuery<TransformComponent> xformQuery)
|
||||
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags)
|
||||
{
|
||||
if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0)
|
||||
return;
|
||||
|
||||
var conQuery = GetEntityQuery<ContainerManagerComponent>();
|
||||
var toAdd = new ValueList<EntityUid>();
|
||||
|
||||
foreach (var uid in intersecting)
|
||||
{
|
||||
if (!conQuery.TryGetComponent(uid, out var conManager)) continue;
|
||||
if (!_containerQuery.TryGetComponent(uid, out var conManager))
|
||||
continue;
|
||||
|
||||
foreach (var con in conManager.GetAllContainers())
|
||||
{
|
||||
foreach (var contained in con.ContainedEntities)
|
||||
{
|
||||
toAdd.Add(contained);
|
||||
RecursiveAdd(contained, ref toAdd, xformQuery);
|
||||
RecursiveAdd(contained, ref toAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,11 +313,9 @@ public sealed partial class EntityLookupSystem
|
||||
float arcWidth,
|
||||
LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
|
||||
{
|
||||
var angle = new Angle(_transform.GetWorldPosition(entity, xformQuery) - coordinates.Position);
|
||||
var angle = new Angle(_transform.GetWorldPosition(entity) - coordinates.Position);
|
||||
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
|
||||
angle.Degrees > direction.Degrees - arcWidth / 2)
|
||||
yield return entity;
|
||||
@@ -340,48 +330,63 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return false;
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Don't need to check contained entities as they have the same bounds as the parent.
|
||||
var found = false;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery)) return true;
|
||||
}
|
||||
var state = (this, worldAABB, flags, found);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found) tuple) =>
|
||||
{
|
||||
if (!tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags))
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags);
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
// Get grid entities
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
AddEntitiesIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery);
|
||||
var state = (this, _map, intersecting, worldAABB, flags);
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid gridUid, MapGridComponent grid, ref (
|
||||
EntityLookupSystem lookup, SharedMapSystem _map, HashSet<EntityUid> intersecting,
|
||||
Box2 worldAABB, LookupFlags flags) tuple) =>
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
|
||||
tuple.lookup.AddEntitiesIntersecting(gridUid, tuple.intersecting, tuple.worldAABB, tuple.flags);
|
||||
|
||||
if ((tuple.flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
if (Deleted(uid)) continue;
|
||||
intersecting.Add(uid);
|
||||
// TODO: Need a struct enumerator version.
|
||||
foreach (var uid in tuple._map.GetAnchoredEntities(gridUid, grid, tuple.worldAABB))
|
||||
{
|
||||
if (tuple.lookup.Deleted(uid))
|
||||
continue;
|
||||
|
||||
tuple.intersecting.Add(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery);
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -392,39 +397,55 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Don't need to check contained entities as they have the same bounds as the parent.
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
|
||||
{
|
||||
if (AnyEntitiesIntersecting(grid.Owner, worldBounds, flags, lookupQuery, xformQuery)) return true;
|
||||
}
|
||||
const bool found = false;
|
||||
var state = (this, worldBounds, flags, found);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid, ref (EntityLookupSystem lookup, Box2Rotated worldBounds, LookupFlags flags, bool found) tuple) =>
|
||||
{
|
||||
if (tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldBounds, tuple.flags))
|
||||
{
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
return AnyEntitiesIntersecting(mapUid, worldBounds, flags, lookupQuery, xformQuery);
|
||||
return AnyEntitiesIntersecting(mapUid, worldBounds, flags);
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
if (mapId == MapId.Nullspace)
|
||||
return intersecting;
|
||||
|
||||
// Get grid entities
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
|
||||
var state = (this, intersecting, worldBounds, flags);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, static
|
||||
(EntityUid uid, MapGridComponent _,
|
||||
ref (EntityLookupSystem lookup,
|
||||
HashSet<EntityUid> intersecting,
|
||||
Box2Rotated worldBounds,
|
||||
LookupFlags flags) tuple) =>
|
||||
{
|
||||
AddEntitiesIntersecting(grid.Owner, intersecting, worldBounds, flags, lookupQuery, xformQuery);
|
||||
}
|
||||
tuple.lookup.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldBounds, tuple.flags);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags, lookupQuery, xformQuery);
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -438,50 +459,72 @@ public sealed partial class EntityLookupSystem
|
||||
public bool AnyEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var worldAABB = GetWorldAABB(uid);
|
||||
var mapID = Transform(uid).MapID;
|
||||
var mapID = _xformQuery.GetComponent(uid).MapID;
|
||||
|
||||
if (mapID == MapId.Nullspace) return false;
|
||||
if (mapID == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
const bool found = false;
|
||||
var state = (this, worldAABB, flags, found, uid);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapID, worldAABB, ref state,
|
||||
static (EntityUid gridUid, MapGridComponent grid,
|
||||
ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found, EntityUid ignored) tuple) =>
|
||||
{
|
||||
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
|
||||
{
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapID, worldAABB))
|
||||
{
|
||||
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapID);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
|
||||
}
|
||||
|
||||
public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var mapPos = Transform(uid).MapPosition;
|
||||
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
|
||||
|
||||
if (mapPos.MapId == MapId.Nullspace) return false;
|
||||
if (mapPos.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
var rangeVec = new Vector2(range, range);
|
||||
|
||||
var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec);
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB))
|
||||
const bool found = false;
|
||||
var state = (this, worldAABB, flags, found, uid);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static (
|
||||
EntityUid gridUid,
|
||||
MapGridComponent _, ref (
|
||||
EntityLookupSystem lookup,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
bool found,
|
||||
EntityUid ignored) tuple) =>
|
||||
{
|
||||
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
|
||||
return true;
|
||||
}
|
||||
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
|
||||
{
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
|
||||
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var mapPos = Transform(uid).MapPosition;
|
||||
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
|
||||
|
||||
if (mapPos.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
|
||||
if (mapPos.MapId == MapId.Nullspace)
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var intersecting = GetEntitiesInRange(mapPos, range, flags);
|
||||
intersecting.Remove(uid);
|
||||
@@ -490,12 +533,13 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
var mapId = xform.MapID;
|
||||
|
||||
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
|
||||
if (mapId == MapId.Nullspace)
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
|
||||
var bounds = GetAABBNoContainer(uid, worldPos, worldRot);
|
||||
|
||||
var intersecting = GetEntitiesIntersecting(mapId, bounds, flags);
|
||||
@@ -509,7 +553,8 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public bool AnyEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (!coordinates.IsValid(EntityManager)) return false;
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return false;
|
||||
|
||||
var mapPos = coordinates.ToMap(EntityManager, _transform);
|
||||
return AnyEntitiesIntersecting(mapPos, flags);
|
||||
@@ -517,7 +562,8 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public bool AnyEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (!coordinates.IsValid(EntityManager)) return false;
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return false;
|
||||
|
||||
var mapPos = coordinates.ToMap(EntityManager, _transform);
|
||||
return AnyEntitiesInRange(mapPos, range, flags);
|
||||
@@ -581,7 +627,8 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
DebugTools.Assert(range > 0, "Range must be a positive float");
|
||||
|
||||
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
|
||||
if (mapId == MapId.Nullspace)
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
// TODO: Actual circles
|
||||
var rangeVec = new Vector2(range, range);
|
||||
@@ -601,7 +648,7 @@ public sealed partial class EntityLookupSystem
|
||||
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
|
||||
|
||||
var lookup = Comp<BroadphaseComponent>(gridId);
|
||||
var lookup = _broadQuery.GetComponent(gridId);
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
var tileSize = grid.TileSize;
|
||||
|
||||
@@ -647,8 +694,7 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
}
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -656,8 +702,10 @@ public sealed partial class EntityLookupSystem
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Vector2i gridIndices, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
|
||||
var lookup = Comp<BroadphaseComponent>(gridId);
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var lookup = _broadQuery.GetComponent(gridId);
|
||||
var tileSize = grid.TileSize;
|
||||
var aabb = GetLocalBounds(gridIndices, tileSize);
|
||||
return GetEntitiesIntersecting(lookup, aabb, flags);
|
||||
@@ -707,22 +755,20 @@ public sealed partial class EntityLookupSystem
|
||||
}, aabb);
|
||||
}
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
|
||||
if (!_mapManager.GridExists(gridId))
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags, lookupQuery, xformQuery);
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -731,12 +777,10 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags, lookupQuery, xformQuery);
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -754,8 +798,7 @@ public sealed partial class EntityLookupSystem
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var localAABB = xformQuery.GetComponent(component.Owner).InvWorldMatrix.TransformBox(worldAABB);
|
||||
var localAABB = _transform.GetInvWorldMatrix(component.Owner).TransformBox(worldAABB);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
@@ -793,7 +836,7 @@ public sealed partial class EntityLookupSystem
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
AddContained(intersecting, flags, xformQuery);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -838,7 +881,7 @@ public sealed partial class EntityLookupSystem
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
AddContained(intersecting, flags, GetEntityQuery<TransformComponent>());
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
@@ -850,36 +893,24 @@ public sealed partial class EntityLookupSystem
|
||||
/// <summary>
|
||||
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
|
||||
/// </summary>
|
||||
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2 worldAABB)
|
||||
public void FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds, ComponentQueryCallback<BroadphaseComponent> callback)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
callback(mapUid, _broadQuery.GetComponent(mapUid));
|
||||
|
||||
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
|
||||
var state = (callback, _broadQuery);
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return lookupQuery.GetComponent(grid.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
|
||||
/// </summary>
|
||||
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
|
||||
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
|
||||
|
||||
// Copy-paste with above but the query may differ slightly internally.
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||
{
|
||||
yield return lookupQuery.GetComponent(grid.Owner);
|
||||
}
|
||||
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (ComponentQueryCallback<BroadphaseComponent> callback, EntityQuery<BroadphaseComponent> _broadQuery)
|
||||
tuple) =>
|
||||
{
|
||||
tuple.callback(uid, tuple._broadQuery.GetComponent(uid));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -904,8 +935,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
if (worldMatrix == null || angle == null)
|
||||
{
|
||||
var gridXform = Transform(tileRef.GridUid);
|
||||
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
|
||||
var (_, wAng, wMat) = _transform.GetWorldPositionRotationMatrix(tileRef.GridUid);
|
||||
worldMatrix = wMat;
|
||||
angle = wAng;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<BroadphaseComponent> _broadQuery;
|
||||
|
||||
@@ -53,7 +53,7 @@ public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent
|
||||
return;
|
||||
|
||||
comp.BoundingBox = box;
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
|
||||
if (comp.TreeUid != null)
|
||||
QueueTreeUpdate(uid, comp);
|
||||
@@ -65,7 +65,7 @@ public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent
|
||||
return;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
QueueTreeUpdate(uid, comp);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -46,7 +46,7 @@ public abstract class SharedAppearanceSystem : EntitySystem
|
||||
DebugTools.Assert(value.GetType().IsValueType || value is ICloneable, "Appearance data values must be cloneable.");
|
||||
|
||||
component.AppearanceData[key] = value;
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
QueueUpdate(uid, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,12 @@ using Robust.Shared.Random;
|
||||
namespace Robust.Shared.GameObjects;
|
||||
public abstract class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] protected readonly IConfigurationManager CfgManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] protected readonly IRobustRandom RandMan = default!;
|
||||
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Default max range at which the sound can be heard.
|
||||
@@ -292,7 +293,7 @@ public abstract class SharedAudioSystem : EntitySystem
|
||||
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
|
||||
{
|
||||
if (_mapManager.TryFindGridAt(mapCoordinates, out var gridUid, out var mapGrid))
|
||||
return new EntityCoordinates(gridUid, mapGrid.WorldToLocal(mapCoordinates.Position));
|
||||
return new EntityCoordinates(gridUid, _map.WorldToLocal(gridUid, mapGrid, mapCoordinates.Position));
|
||||
|
||||
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
|
||||
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates.Position);
|
||||
|
||||
8
Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs
Normal file
8
Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract class SharedEyeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
@@ -21,9 +22,9 @@ namespace Robust.Shared.GameObjects
|
||||
public abstract class SharedGridFixtureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
protected ISawmill Sawmill = default!;
|
||||
private bool _enabled;
|
||||
private float _fixtureEnlargement;
|
||||
|
||||
@@ -33,12 +34,17 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
|
||||
Sawmill = Logger.GetSawmill("physics");
|
||||
|
||||
_cfg.OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
|
||||
_cfg.OnValueChanged(CVars.GridFixtureEnlargement, SetEnlargement, true);
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
|
||||
SubscribeLocalEvent<RegenerateGridBoundsEvent>(OnGridBoundsRegenerate);
|
||||
}
|
||||
|
||||
private void OnGridBoundsRegenerate(ref RegenerateGridBoundsEvent ev)
|
||||
{
|
||||
RegenerateCollision(ev.Entity, ev.ChunkRectangles, ev.RemovedChunks);
|
||||
}
|
||||
|
||||
protected virtual void OnGridInit(GridInitializeEvent ev)
|
||||
@@ -48,7 +54,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// This will also check for grid splits if applicable.
|
||||
var grid = Comp<MapGridComponent>(ev.EntityUid);
|
||||
grid.RegenerateCollision(grid.GetMapChunks().Values.ToHashSet());
|
||||
_map.RegenerateCollision(ev.EntityUid, grid, _map.GetMapChunks(ev.EntityUid, grid).Values.ToHashSet());
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -68,23 +74,24 @@ namespace Robust.Shared.GameObjects
|
||||
Dictionary<MapChunk, List<Box2i>> mapChunks,
|
||||
List<MapChunk> removedChunks)
|
||||
{
|
||||
if (!_enabled) return;
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body))
|
||||
{
|
||||
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
|
||||
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
|
||||
{
|
||||
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
|
||||
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out TransformComponent? xform))
|
||||
{
|
||||
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
|
||||
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -224,7 +227,7 @@ public abstract partial class SharedMapSystem
|
||||
if (chunkData.IsDeleted())
|
||||
continue;
|
||||
|
||||
var chunk = component.GetOrAddChunk(chunkData.Index);
|
||||
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
DebugTools.Assert(chunkData.TileData.Length == component.ChunkSize * component.ChunkSize);
|
||||
|
||||
@@ -237,7 +240,7 @@ public abstract partial class SharedMapSystem
|
||||
if (chunk.GetTile(x, y) == tile)
|
||||
continue;
|
||||
|
||||
chunk.SetTile(x, y, tile);
|
||||
SetChunkTile(uid, component, chunk, x, y, tile);
|
||||
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
|
||||
}
|
||||
}
|
||||
@@ -247,13 +250,13 @@ public abstract partial class SharedMapSystem
|
||||
{
|
||||
if (chunkData.IsDeleted())
|
||||
{
|
||||
component.RemoveChunk(chunkData.Index);
|
||||
RemoveChunk(uid, component, chunkData.Index);
|
||||
continue;
|
||||
}
|
||||
|
||||
var chunk = component.GetOrAddChunk(chunkData.Index);
|
||||
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
component.RegenerateCollision(chunk);
|
||||
RegenerateCollision(uid, component, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,12 +266,12 @@ public abstract partial class SharedMapSystem
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.FullGridData.ContainsKey(index))
|
||||
component.RemoveChunk(index);
|
||||
RemoveChunk(uid, component, index);
|
||||
}
|
||||
|
||||
foreach (var (index, tiles) in state.FullGridData)
|
||||
{
|
||||
var chunk = component.GetOrAddChunk(index);
|
||||
var chunk = GetOrAddChunk(uid, component, index);
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
DebugTools.Assert(tiles.Length == component.ChunkSize * component.ChunkSize);
|
||||
|
||||
@@ -281,13 +284,13 @@ public abstract partial class SharedMapSystem
|
||||
if (chunk.GetTile(x, y) == tile)
|
||||
continue;
|
||||
|
||||
chunk.SetTile(x, y, tile);
|
||||
SetChunkTile(uid, component, chunk, x, y, tile);
|
||||
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
|
||||
}
|
||||
}
|
||||
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
component.RegenerateCollision(chunk);
|
||||
RegenerateCollision(uid, component, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +303,7 @@ public abstract partial class SharedMapSystem
|
||||
{
|
||||
if (args.FromTick <= component.CreationTick)
|
||||
{
|
||||
GetFullState(component, ref args);
|
||||
GetFullState(uid, component, ref args);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -324,7 +327,7 @@ public abstract partial class SharedMapSystem
|
||||
chunkData.Add(ChunkDatum.CreateDeleted(indices));
|
||||
}
|
||||
|
||||
foreach (var (index, chunk) in component.GetMapChunks())
|
||||
foreach (var (index, chunk) in GetMapChunks(uid, component))
|
||||
{
|
||||
if (chunk.LastTileModifiedTick < fromTick)
|
||||
continue;
|
||||
@@ -348,11 +351,11 @@ public abstract partial class SharedMapSystem
|
||||
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
|
||||
}
|
||||
|
||||
private void GetFullState(MapGridComponent component, ref ComponentGetState args)
|
||||
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var chunkData = new Dictionary<Vector2i, Tile[]>();
|
||||
|
||||
foreach (var (index, chunk) in component.GetMapChunks())
|
||||
foreach (var (index, chunk) in GetMapChunks(uid, component))
|
||||
{
|
||||
var tileBuffer = new Tile[component.ChunkSize * (uint)component.ChunkSize];
|
||||
|
||||
@@ -385,7 +388,6 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
foreach (var chunk in component.Chunks.Values)
|
||||
{
|
||||
chunk.TileModified += component.OnTileModified;
|
||||
chunk.LastTileModifiedTick = curTick;
|
||||
}
|
||||
|
||||
@@ -445,7 +447,7 @@ public abstract partial class SharedMapSystem
|
||||
if (!Resolve(uid, ref xform))
|
||||
return new Box2();
|
||||
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, GetEntityQuery<TransformComponent>());
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
|
||||
var aabb = grid.LocalAABB.Translated(worldPos);
|
||||
|
||||
return new Box2Rotated(aabb, worldRot, worldPos).CalcBoundingBox();
|
||||
@@ -456,7 +458,7 @@ public abstract partial class SharedMapSystem
|
||||
DebugTools.Assert(!EntityManager.HasComponent<MapComponent>(uid));
|
||||
var aabb = GetWorldAABB(uid, grid);
|
||||
|
||||
if (!TryComp<TransformComponent>(uid, out var xform))
|
||||
if (!_xformQuery.TryGetComponent(uid, out var xform))
|
||||
return;
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
@@ -486,4 +488,796 @@ public abstract partial class SharedMapSystem
|
||||
movedGrids.MovedGrids.Remove(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveChunk(EntityUid uid, MapGridComponent grid, Vector2i origin)
|
||||
{
|
||||
if (!grid.Chunks.TryGetValue(origin, out var chunk))
|
||||
return;
|
||||
|
||||
if (_netManager.IsServer)
|
||||
grid.ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
|
||||
|
||||
chunk.Fixtures.Clear();
|
||||
grid.Chunks.Remove(origin);
|
||||
|
||||
if (grid.Chunks.Count == 0)
|
||||
RaiseLocalEvent(uid, new EmptyGridEvent { GridId = uid }, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the chunk local bounds of this chunk.
|
||||
/// </summary>
|
||||
private void RegenerateCollision(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
|
||||
{
|
||||
RegenerateCollision(uid, grid, new HashSet<MapChunk> { mapChunk });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
|
||||
/// </summary>
|
||||
internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> chunks)
|
||||
{
|
||||
if (HasComp<MapComponent>(uid))
|
||||
return;
|
||||
|
||||
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
|
||||
var removedChunks = new List<MapChunk>();
|
||||
|
||||
foreach (var mapChunk in chunks)
|
||||
{
|
||||
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
|
||||
// generate collision rectangles for this chunk based on filled tiles.
|
||||
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
|
||||
mapChunk.CachedBounds = localBounds;
|
||||
|
||||
if (mapChunk.FilledTiles > 0)
|
||||
chunkRectangles.Add(mapChunk, rectangles);
|
||||
else
|
||||
{
|
||||
// Gone. Reduced to atoms
|
||||
// Need to do this before RemoveChunk because it clears fixtures.
|
||||
FixturesComponent? manager = null;
|
||||
PhysicsComponent? body = null;
|
||||
TransformComponent? xform = null;
|
||||
|
||||
foreach (var fixture in mapChunk.Fixtures)
|
||||
{
|
||||
_fixtures.DestroyFixture(uid, fixture, false, manager: manager, body: body, xform: xform);
|
||||
}
|
||||
|
||||
RemoveChunk(uid, grid, mapChunk.Indices);
|
||||
removedChunks.Add(mapChunk);
|
||||
}
|
||||
}
|
||||
|
||||
grid.LocalAABB = new Box2();
|
||||
|
||||
foreach (var chunk in grid.Chunks.Values)
|
||||
{
|
||||
var chunkBounds = chunk.CachedBounds;
|
||||
|
||||
if (chunkBounds.Size.Equals(Vector2i.Zero))
|
||||
continue;
|
||||
|
||||
if (grid.LocalAABB.Size == Vector2.Zero)
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
grid.LocalAABB = gridBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
grid.LocalAABB = grid.LocalAABB.Union(gridBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// May have been deleted from the bulk update above!
|
||||
if (Deleted(uid))
|
||||
return;
|
||||
|
||||
_physics.WakeBody(uid);
|
||||
OnGridBoundsChange(uid, grid);
|
||||
var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
|
||||
RaiseLocalEvent(ref ev);
|
||||
}
|
||||
|
||||
#region TileAccess
|
||||
|
||||
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
|
||||
{
|
||||
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
|
||||
}
|
||||
|
||||
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
||||
{
|
||||
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
|
||||
}
|
||||
|
||||
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, Vector2i tileCoordinates)
|
||||
{
|
||||
var chunkIndices = GridTileToChunkIndices(uid, grid, tileCoordinates);
|
||||
|
||||
if (!grid.Chunks.TryGetValue(chunkIndices, out var output))
|
||||
{
|
||||
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
|
||||
return new TileRef(uid, tileCoordinates.X, tileCoordinates.Y, default);
|
||||
}
|
||||
|
||||
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
|
||||
return GetTileRef(uid, grid, output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given chunk indices.
|
||||
/// </summary>
|
||||
/// <param name="mapChunk"></param>
|
||||
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
|
||||
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
internal TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, ushort xIndex, ushort yIndex)
|
||||
{
|
||||
if (xIndex >= mapChunk.ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
|
||||
|
||||
if (yIndex >= mapChunk.ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
|
||||
|
||||
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
|
||||
return new TileRef(uid, indices, mapChunk.GetTile(xIndex, yIndex));
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
|
||||
{
|
||||
foreach (var kvChunk in grid.Chunks)
|
||||
{
|
||||
var chunk = kvChunk.Value;
|
||||
for (ushort x = 0; x < grid.ChunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < grid.ChunkSize; y++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
|
||||
if (ignoreEmpty && tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * grid.ChunkSize;
|
||||
yield return new TileRef(uid, gridX, gridY, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GridTileEnumerator GetAllTilesEnumerator(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
|
||||
{
|
||||
return new GridTileEnumerator(uid, grid.Chunks.GetEnumerator(), grid.ChunkSize, ignoreEmpty);
|
||||
}
|
||||
|
||||
public void SetTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Tile tile)
|
||||
{
|
||||
var localTile = CoordinatesToTile(uid, grid, coords);
|
||||
SetTile(uid, grid, new Vector2i(localTile.X, localTile.Y), tile);
|
||||
}
|
||||
|
||||
public void SetTile(EntityUid uid, MapGridComponent grid, Vector2i gridIndices, Tile tile)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
|
||||
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
|
||||
// Yeah it's noodly.
|
||||
// RegenerateCollision(chunk);
|
||||
}
|
||||
|
||||
public void SetTiles(EntityUid uid, MapGridComponent grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
|
||||
{
|
||||
if (tiles.Count == 0)
|
||||
return;
|
||||
|
||||
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
|
||||
|
||||
foreach (var (gridIndices, tile) in tiles)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
|
||||
chunks.Add(chunk);
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
}
|
||||
|
||||
RegenerateCollision(uid, grid, chunks);
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var localAABB = localArea.CalcBoundingBox();
|
||||
return GetLocalTilesIntersecting(uid, grid, localAABB, ignoreEmpty, predicate);
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var matrix = _transform.GetInvWorldMatrix(uid);
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
|
||||
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var matrix = _transform.GetInvWorldMatrix(uid);
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
|
||||
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
|
||||
// that way we can avoid the GetComp here.
|
||||
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
|
||||
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
|
||||
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
|
||||
|
||||
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
|
||||
{
|
||||
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
|
||||
{
|
||||
var gridChunk = GridTileToChunkIndices(uid, grid, new Vector2i(x, y));
|
||||
|
||||
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
|
||||
{
|
||||
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
|
||||
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
yield return tile;
|
||||
}
|
||||
else if (!ignoreEmpty)
|
||||
{
|
||||
var tile = new TileRef(uid, x, y, Tile.Empty);
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
|
||||
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
|
||||
var circleGridPos = new EntityCoordinates(uid, WorldToLocal(uid, grid, worldArea.Position));
|
||||
|
||||
foreach (var tile in GetTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate))
|
||||
{
|
||||
var local = GridTileToLocal(uid, grid, tile.GridIndices);
|
||||
|
||||
if (!local.TryDistance(EntityManager, _transform, circleGridPos, out var distance))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (distance <= worldArea.Radius)
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetTile(EntityUid uid, MapGridComponent grid, Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
// Similar to TryGetTileRef but for the tiles intersecting iterators.
|
||||
var gridChunk = GridTileToChunkIndices(uid, grid, indices);
|
||||
|
||||
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
|
||||
{
|
||||
var chunkTile = chunk.GridTileToChunkTile(indices);
|
||||
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
{
|
||||
tileRef = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tileRef = tile;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (!ignoreEmpty)
|
||||
{
|
||||
var tile = new TileRef(uid, indices.X, indices.Y, Tile.Empty);
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tileRef = tile;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
tileRef = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion TileAccess
|
||||
|
||||
#region ChunkAccess
|
||||
|
||||
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, int xIndex, int yIndex)
|
||||
{
|
||||
return GetOrAddChunk(uid, grid, new Vector2i(xIndex, yIndex));
|
||||
}
|
||||
|
||||
internal bool TryGetChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
|
||||
{
|
||||
return grid.Chunks.TryGetValue(chunkIndices, out chunk);
|
||||
}
|
||||
|
||||
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
|
||||
{
|
||||
if (grid.Chunks.TryGetValue(chunkIndices, out var output))
|
||||
return output;
|
||||
|
||||
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, grid.ChunkSize)
|
||||
{
|
||||
LastTileModifiedTick = _timing.CurTick
|
||||
};
|
||||
|
||||
return grid.Chunks[chunkIndices] = newChunk;
|
||||
}
|
||||
|
||||
public bool HasChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
|
||||
{
|
||||
return grid.Chunks.ContainsKey(chunkIndices);
|
||||
}
|
||||
|
||||
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks(EntityUid uid, MapGridComponent grid)
|
||||
{
|
||||
return grid.Chunks;
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
|
||||
{
|
||||
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
|
||||
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
|
||||
{
|
||||
var matrix = _transform.GetInvWorldMatrix(uid);
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
|
||||
{
|
||||
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
|
||||
}
|
||||
|
||||
#endregion ChunkAccess
|
||||
|
||||
#region SnapGridAccess
|
||||
|
||||
public int AnchoredEntityCount(EntityUid uid, MapGridComponent grid, Vector2i pos)
|
||||
{
|
||||
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
|
||||
|
||||
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk))
|
||||
return 0;
|
||||
|
||||
var (x, y) = chunk.GridTileToChunkTile(pos);
|
||||
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
|
||||
{
|
||||
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
||||
{
|
||||
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Vector2i pos)
|
||||
{
|
||||
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
|
||||
// create an entire chunk for it.
|
||||
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
|
||||
|
||||
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
}
|
||||
|
||||
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(EntityUid uid, MapGridComponent grid, Vector2i pos)
|
||||
{
|
||||
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
|
||||
|
||||
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
|
||||
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
return snapgrid == null
|
||||
? AnchoredEntitiesEnumerator.Empty
|
||||
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
|
||||
{
|
||||
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localAABB, true, null))
|
||||
{
|
||||
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
|
||||
{
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
|
||||
{
|
||||
foreach (var tile in GetTilesIntersecting(uid, grid, worldAABB))
|
||||
{
|
||||
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
|
||||
{
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2Rotated worldBounds)
|
||||
{
|
||||
foreach (var tile in GetTilesIntersecting(uid, grid, worldBounds))
|
||||
{
|
||||
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
|
||||
{
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _xformQuery.GetComponent(uid).MapID;
|
||||
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
|
||||
#endif
|
||||
|
||||
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
|
||||
}
|
||||
|
||||
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordinates worldPos)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _xformQuery.GetComponent(uid).MapID;
|
||||
DebugTools.Assert(mapId == worldPos.MapId);
|
||||
#endif
|
||||
|
||||
var localPos = WorldToLocal(uid, grid, worldPos.Position);
|
||||
return SnapGridLocalCellFor(uid, grid, localPos);
|
||||
}
|
||||
|
||||
private Vector2i SnapGridLocalCellFor(EntityUid uid, MapGridComponent grid, Vector2 localPos)
|
||||
{
|
||||
var x = (int)Math.Floor(localPos.X / grid.TileSize);
|
||||
var y = (int)Math.Floor(localPos.Y / grid.TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public bool IsAnchored(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
|
||||
{
|
||||
var tilePos = TileIndicesFor(uid, grid, coords);
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, tilePos);
|
||||
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
return snapgrid?.Contains(euid) == true;
|
||||
}
|
||||
|
||||
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
|
||||
|
||||
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
|
||||
return false;
|
||||
|
||||
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
|
||||
{
|
||||
return AddToSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
|
||||
}
|
||||
|
||||
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
|
||||
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
|
||||
}
|
||||
|
||||
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
|
||||
{
|
||||
RemoveFromSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
|
||||
}
|
||||
|
||||
private (MapChunk, Vector2i) ChunkAndOffsetForTile(EntityUid uid, MapGridComponent grid, Vector2i pos)
|
||||
{
|
||||
var gridChunkIndices = GridTileToChunkIndices(uid, grid, pos);
|
||||
var chunk = GetOrAddChunk(uid, grid, gridChunkIndices);
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
return (chunk, chunkTile);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetInDir(EntityUid uid, MapGridComponent grid, EntityCoordinates position, Direction dir)
|
||||
{
|
||||
var pos = GetDirection(TileIndicesFor(uid, grid, position), dir);
|
||||
return GetAnchoredEntities(uid, grid, pos);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetOffset(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Vector2i offset)
|
||||
{
|
||||
var pos = TileIndicesFor(uid, grid, coords) + offset;
|
||||
return GetAnchoredEntities(uid, grid, pos);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetLocal(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
||||
{
|
||||
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
|
||||
}
|
||||
|
||||
public EntityCoordinates DirectionToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Direction direction)
|
||||
{
|
||||
return GridTileToLocal(uid, grid, GetDirection(TileIndicesFor(uid, grid, coords), direction));
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
||||
{
|
||||
var position = TileIndicesFor(uid, grid, coords);
|
||||
foreach (var cell in GetAnchoredEntities(uid, grid, position))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, 1)))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, -1)))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(1, 0)))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(-1, 0)))
|
||||
yield return cell;
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, int n)
|
||||
{
|
||||
var position = TileIndicesFor(uid, grid, coords);
|
||||
|
||||
for (var y = -n; y <= n; ++y)
|
||||
for (var x = -n; x <= n; ++x)
|
||||
{
|
||||
var enumerator = GetAnchoredEntitiesEnumerator(uid, grid, position + new Vector2i(x, y));
|
||||
|
||||
while (enumerator.MoveNext(out var cell))
|
||||
{
|
||||
yield return cell.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transforms
|
||||
|
||||
public Vector2 WorldToLocal(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
|
||||
{
|
||||
var matrix = _transform.GetInvWorldMatrix(uid);
|
||||
return matrix.Transform(posWorld);
|
||||
}
|
||||
|
||||
public EntityCoordinates MapToGrid(EntityUid uid, MapCoordinates posWorld)
|
||||
{
|
||||
var mapId = _xformQuery.GetComponent(uid).MapID;
|
||||
|
||||
if (posWorld.MapId != mapId)
|
||||
throw new ArgumentException(
|
||||
$"Grid {uid} is on map {mapId}, but coords are on map {posWorld.MapId}.",
|
||||
nameof(posWorld));
|
||||
|
||||
if (!TryComp<MapGridComponent>(uid, out var grid))
|
||||
{
|
||||
return new EntityCoordinates(MapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
|
||||
}
|
||||
|
||||
return new EntityCoordinates(uid, WorldToLocal(uid, grid, posWorld.Position));
|
||||
}
|
||||
|
||||
public Vector2 LocalToWorld(EntityUid uid, MapGridComponent grid, Vector2 posLocal)
|
||||
{
|
||||
var matrix = _transform.GetWorldMatrix(uid);
|
||||
return matrix.Transform(posLocal);
|
||||
}
|
||||
|
||||
public Vector2i WorldToTile(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
|
||||
{
|
||||
var local = WorldToLocal(uid, grid, posWorld);
|
||||
var x = (int)Math.Floor(local.X / grid.TileSize);
|
||||
var y = (int)Math.Floor(local.Y / grid.TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public Vector2i LocalToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coordinates)
|
||||
{
|
||||
var position = LocalToGrid(uid, grid, coordinates);
|
||||
return new Vector2i((int) Math.Floor(position.X / grid.TileSize), (int) Math.Floor(position.Y / grid.TileSize));
|
||||
}
|
||||
|
||||
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _xformQuery.GetComponent(uid).MapID;
|
||||
DebugTools.Assert(mapId == coords.MapId);
|
||||
#endif
|
||||
|
||||
var local = WorldToLocal(uid, grid, coords.Position);
|
||||
|
||||
var x = (int)Math.Floor(local.X / grid.TileSize);
|
||||
var y = (int)Math.Floor(local.Y / grid.TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _xformQuery.GetComponent(uid).MapID;
|
||||
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
|
||||
#endif
|
||||
var local = LocalToGrid(uid, grid, coords);
|
||||
|
||||
var x = (int)Math.Floor(local.X / grid.TileSize);
|
||||
var y = (int)Math.Floor(local.Y / grid.TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public Vector2i LocalToChunkIndices(EntityUid uid, MapGridComponent grid, EntityCoordinates gridPos)
|
||||
{
|
||||
var local = LocalToGrid(uid, grid, gridPos);
|
||||
|
||||
var x = (int)Math.Floor(local.X / (grid.TileSize * grid.ChunkSize));
|
||||
var y = (int)Math.Floor(local.Y / (grid.TileSize * grid.ChunkSize));
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public Vector2 LocalToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates position)
|
||||
{
|
||||
return position.EntityId == uid
|
||||
? position.Position
|
||||
: WorldToLocal(uid, grid, position.ToMapPos(EntityManager, _transform));
|
||||
}
|
||||
|
||||
public bool CollidesWithGrid(EntityUid uid, MapGridComponent grid, Vector2i indices)
|
||||
{
|
||||
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
|
||||
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
return false;
|
||||
|
||||
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
||||
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
|
||||
}
|
||||
|
||||
public Vector2i GridTileToChunkIndices(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
var x = (int)Math.Floor(gridTile.X / (float) grid.ChunkSize);
|
||||
var y = (int)Math.Floor(gridTile.Y / (float) grid.ChunkSize);
|
||||
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
return new(uid,
|
||||
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
|
||||
}
|
||||
|
||||
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
var locX = gridTile.X * grid.TileSize + (grid.TileSize / 2f);
|
||||
var locY = gridTile.Y * grid.TileSize + (grid.TileSize / 2f);
|
||||
|
||||
return _transform.GetWorldMatrix(uid).Transform(new Vector2(locX, locY));
|
||||
}
|
||||
|
||||
public MapCoordinates GridTileToWorld(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
var parentMapId = _xformQuery.GetComponent(uid).MapID;
|
||||
|
||||
return new(GridTileToWorldPos(uid, grid, gridTile), parentMapId);
|
||||
}
|
||||
|
||||
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2i indices, out TileRef tile)
|
||||
{
|
||||
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
|
||||
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
{
|
||||
tile = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
||||
tile = GetTileRef(uid, grid, chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, out TileRef tile)
|
||||
{
|
||||
return TryGetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords), out tile);
|
||||
}
|
||||
|
||||
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2 worldPos, out TileRef tile)
|
||||
{
|
||||
return TryGetTileRef(uid, grid, WorldToTile(uid, grid, worldPos), out tile);
|
||||
}
|
||||
|
||||
#endregion Transforms
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the world space AABB for this chunk.
|
||||
/// </summary>
|
||||
internal Box2 CalcWorldAABB(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
|
||||
{
|
||||
var (position, rotation) =
|
||||
_transform.GetWorldPositionRotation(uid);
|
||||
|
||||
var chunkPosition = mapChunk.Indices;
|
||||
var tileScale = grid.TileSize;
|
||||
var chunkScale = mapChunk.ChunkSize;
|
||||
|
||||
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
|
||||
|
||||
return new Box2Rotated(
|
||||
((Box2)mapChunk.CachedBounds
|
||||
.Scale(tileScale))
|
||||
.Translated(worldPos),
|
||||
rotation, worldPos).CalcBoundingBox();
|
||||
}
|
||||
|
||||
private void OnTileModified(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
|
||||
bool shapeChanged)
|
||||
{
|
||||
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
|
||||
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
|
||||
mapChunk.LastTileModifiedTick = _timing.CurTick;
|
||||
grid.LastTileModifiedTick = _timing.CurTick;
|
||||
Dirty(grid);
|
||||
|
||||
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
|
||||
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
|
||||
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
|
||||
if (!MapManager.SuppressOnTileChanged)
|
||||
{
|
||||
var newTileRef = new TileRef(uid, gridTile, newTile);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile);
|
||||
}
|
||||
|
||||
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
|
||||
{
|
||||
RegenerateCollision(uid, grid, mapChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedMapSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces a single tile inside of the chunk.
|
||||
/// </summary>
|
||||
/// <param name="xIndex">The X tile index relative to the chunk.</param>
|
||||
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
|
||||
/// <param name="tile">The new tile to insert.</param>
|
||||
internal void SetChunkTile(EntityUid uid, MapGridComponent grid, MapChunk chunk, ushort xIndex, ushort yIndex, Tile tile)
|
||||
{
|
||||
if (!chunk.TrySetTile(xIndex, yIndex, tile, out var oldTile, out var shapeChanged))
|
||||
return;
|
||||
|
||||
var tileIndices = new Vector2i(xIndex, yIndex);
|
||||
OnTileModified(uid, grid, chunk, tileIndices, tile, oldTile, shapeChanged);
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,30 @@ using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public abstract partial class SharedMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _mapInternal = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
InitializeMap();
|
||||
InitializeGrid();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
comp._enabled = enabled;
|
||||
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
public virtual void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
|
||||
@@ -46,7 +46,7 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
comp._radius = radius;
|
||||
Dirty(comp);
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ public abstract partial class SharedTransformSystem
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
// Bypass some of the expensive stuff in unanchoring / anchoring.
|
||||
oldGrid.RemoveFromSnapGridCell(tilePos, uid);
|
||||
newGrid.AddToSnapGridCell(tilePos, uid);
|
||||
_map.RemoveFromSnapGridCell(oldGridUid, oldGrid, tilePos, uid);
|
||||
_map.AddToSnapGridCell(newGridUid, newGrid, tilePos, uid);
|
||||
// TODO: Could do this re-parent way better.
|
||||
// Unfortunately we don't want any anchoring events to go out hence... this.
|
||||
xform._anchored = false;
|
||||
@@ -77,7 +77,7 @@ public abstract partial class SharedTransformSystem
|
||||
MapGridComponent grid,
|
||||
Vector2i tileIndices)
|
||||
{
|
||||
if (!grid.AddToSnapGridCell(tileIndices, uid))
|
||||
if (!_map.AddToSnapGridCell(gridUid, grid, tileIndices, uid))
|
||||
return false;
|
||||
|
||||
var wasAnchored = xform._anchored;
|
||||
@@ -94,7 +94,7 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
|
||||
// Anchor snapping. If there is a coordinate change, it will dirty the component for us.
|
||||
var pos = new EntityCoordinates(gridUid, grid.GridTileToLocal(tileIndices).Position);
|
||||
var pos = new EntityCoordinates(gridUid, _map.GridTileToLocal(gridUid, grid, tileIndices).Position);
|
||||
SetCoordinates(uid, xform, pos, unanchor: false);
|
||||
|
||||
return true;
|
||||
@@ -102,14 +102,14 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridComponent grid)
|
||||
{
|
||||
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
|
||||
var tileIndices = _map.TileIndicesFor(grid.Owner, grid, xform.Coordinates);
|
||||
return AnchorEntity(uid, xform, grid, tileIndices);
|
||||
}
|
||||
|
||||
public bool AnchorEntity(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
return _mapManager.TryGetGrid(xform.GridUid, out var grid)
|
||||
&& AnchorEntity(uid, xform, grid, grid.TileIndicesFor(xform.Coordinates));
|
||||
&& AnchorEntity(uid, xform, grid, _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates));
|
||||
}
|
||||
|
||||
public void Unanchor(EntityUid uid, TransformComponent xform, bool setPhysics = true)
|
||||
@@ -128,8 +128,8 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||
{
|
||||
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
|
||||
grid.RemoveFromSnapGridCell(tileIndices, uid);
|
||||
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
|
||||
}
|
||||
else if (xform.Initialized)
|
||||
{
|
||||
@@ -156,7 +156,7 @@ public abstract partial class SharedTransformSystem
|
||||
/// </summary>
|
||||
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
|
||||
{
|
||||
return ContainsEntity(xform, entity, GetEntityQuery<TransformComponent>());
|
||||
return ContainsEntity(xform, entity, _xformQuery);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
@@ -168,7 +168,7 @@ public abstract partial class SharedTransformSystem
|
||||
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
|
||||
{
|
||||
return ContainsEntity(xform, entityTransform, GetEntityQuery<TransformComponent>());
|
||||
return ContainsEntity(xform, entityTransform, _xformQuery);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
@@ -227,11 +227,9 @@ public abstract partial class SharedTransformSystem
|
||||
return value;
|
||||
}
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!component._mapIdInitialized)
|
||||
{
|
||||
FindMapIdAndSet(uid, component, EntityManager, xformQuery, _mapManager);
|
||||
FindMapIdAndSet(uid, component, EntityManager, _xformQuery, _mapManager);
|
||||
component._mapIdInitialized = true;
|
||||
}
|
||||
|
||||
@@ -241,7 +239,7 @@ public abstract partial class SharedTransformSystem
|
||||
// Note that _children is a HashSet<EntityUid>,
|
||||
// so duplicate additions (which will happen) don't matter.
|
||||
|
||||
var parentXform = xformQuery.GetComponent(component.ParentUid);
|
||||
var parentXform = _xformQuery.GetComponent(component.ParentUid);
|
||||
if (parentXform.LifeStage > ComponentLifeStage.Running || LifeStage(component.ParentUid) > EntityLifeStage.MapInitialized)
|
||||
{
|
||||
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
|
||||
@@ -365,7 +363,7 @@ public abstract partial class SharedTransformSystem
|
||||
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
|
||||
DebugTools.Assert(gridId == null || HasComp<MapGridComponent>(gridId));
|
||||
|
||||
xformQuery ??= GetEntityQuery<TransformComponent>();
|
||||
xformQuery ??= _xformQuery;
|
||||
SetGridIdRecursive(uid, xform, gridId, xformQuery.Value);
|
||||
}
|
||||
|
||||
@@ -418,6 +416,16 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
#region Local Rotation
|
||||
|
||||
public void SetLocalRotationNoLerp(EntityUid uid, Angle angle)
|
||||
{
|
||||
SetLocalRotationNoLerp(_xformQuery.GetComponent(uid), angle);
|
||||
}
|
||||
|
||||
public virtual void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
|
||||
{
|
||||
xform.LocalRotation = angle;
|
||||
}
|
||||
|
||||
public void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref xform)) return;
|
||||
@@ -464,7 +472,7 @@ public abstract partial class SharedTransformSystem
|
||||
Unanchor(uid, xform);
|
||||
|
||||
// Set new values
|
||||
Dirty(xform);
|
||||
Dirty(uid, xform);
|
||||
xform.MatricesDirty = true;
|
||||
xform._localPosition = value.Position;
|
||||
|
||||
@@ -476,8 +484,6 @@ public abstract partial class SharedTransformSystem
|
||||
// Perform parent change logic
|
||||
if (value.EntityId != xform._parent)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (value.EntityId == uid)
|
||||
{
|
||||
QueueDel(uid);
|
||||
@@ -486,7 +492,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (value.EntityId.IsValid())
|
||||
{
|
||||
if (!xformQuery.Resolve(value.EntityId, ref newParent, false))
|
||||
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
|
||||
{
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
|
||||
@@ -520,13 +526,13 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
|
||||
recursiveUid = recursiveXform.ParentUid;
|
||||
recursiveXform = xformQuery.GetComponent(recursiveUid);
|
||||
recursiveXform = _xformQuery.GetComponent(recursiveUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xform._parent.IsValid())
|
||||
xformQuery.Resolve(xform._parent, ref oldParent);
|
||||
_xformQuery.Resolve(xform._parent, ref oldParent);
|
||||
|
||||
oldParent?._children.Remove(uid);
|
||||
newParent?._children.Add(uid);
|
||||
@@ -536,7 +542,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (newParent != null)
|
||||
{
|
||||
xform.ChangeMapId(newParent.MapID, xformQuery);
|
||||
xform.ChangeMapId(newParent.MapID, _xformQuery);
|
||||
|
||||
if (!xform._gridInitialized)
|
||||
InitializeGridUid(uid, xform);
|
||||
@@ -549,18 +555,18 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
xform.ChangeMapId(MapId.Nullspace, xformQuery);
|
||||
xform.ChangeMapId(MapId.Nullspace, _xformQuery);
|
||||
if (!xform._gridInitialized)
|
||||
InitializeGridUid(uid, xform);
|
||||
else
|
||||
SetGridId(uid, xform, null, xformQuery);
|
||||
SetGridId(uid, xform, null, _xformQuery);
|
||||
}
|
||||
|
||||
if (xform.Initialized)
|
||||
{
|
||||
// preserve world rotation
|
||||
if (rotation == null && oldParent != null && newParent != null && !xform.NoLocalRotation)
|
||||
xform._localRotation += GetWorldRotation(oldParent, xformQuery) - GetWorldRotation(newParent, xformQuery);
|
||||
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
|
||||
|
||||
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
|
||||
|
||||
@@ -588,7 +594,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public void ReparentChildren(EntityUid oldUid, EntityUid uid)
|
||||
{
|
||||
ReparentChildren(oldUid, uid, GetEntityQuery<TransformComponent>());
|
||||
ReparentChildren(oldUid, uid, _xformQuery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -615,7 +621,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public TransformComponent? GetParent(EntityUid uid)
|
||||
{
|
||||
return GetParent(uid, GetEntityQuery<TransformComponent>());
|
||||
return GetParent(uid, _xformQuery);
|
||||
}
|
||||
|
||||
public TransformComponent? GetParent(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
|
||||
@@ -625,7 +631,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public TransformComponent? GetParent(TransformComponent xform)
|
||||
{
|
||||
return GetParent(xform, GetEntityQuery<TransformComponent>());
|
||||
return GetParent(xform, _xformQuery);
|
||||
}
|
||||
|
||||
public TransformComponent? GetParent(TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
|
||||
@@ -636,13 +642,12 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public void SetParent(EntityUid uid, EntityUid parent)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
SetParent(uid, query.GetComponent(uid), parent, query);
|
||||
SetParent(uid, _xformQuery.GetComponent(uid), parent, _xformQuery);
|
||||
}
|
||||
|
||||
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, TransformComponent? parentXform = null)
|
||||
{
|
||||
SetParent(uid, xform, parent, GetEntityQuery<TransformComponent>(), parentXform);
|
||||
SetParent(uid, xform, parent, _xformQuery, parentXform);
|
||||
}
|
||||
|
||||
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
|
||||
@@ -701,8 +706,8 @@ public abstract partial class SharedTransformSystem
|
||||
// remove from any old grid lookups
|
||||
if (xform.Anchored && TryComp(xform.ParentUid, out MapGridComponent? grid))
|
||||
{
|
||||
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
|
||||
grid.RemoveFromSnapGridCell(tileIndices, uid);
|
||||
var tileIndices = _map.TileIndicesFor(xform.ParentUid, grid, xform.Coordinates);
|
||||
_map.RemoveFromSnapGridCell(xform.ParentUid, grid, tileIndices, uid);
|
||||
}
|
||||
|
||||
// Set anchor state true during the move event unless the entity wasn't and isn't being anchored. This avoids unnecessary entity lookup changes.
|
||||
@@ -719,8 +724,8 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (xform.ParentUid == xform.GridUid && TryComp(xform.GridUid, out MapGridComponent? newGrid))
|
||||
{
|
||||
var tileIndices = newGrid.TileIndicesFor(xform.Coordinates);
|
||||
newGrid.AddToSnapGridCell(tileIndices, uid);
|
||||
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, newGrid, xform.Coordinates);
|
||||
_map.AddToSnapGridCell(xform.GridUid.Value, newGrid, tileIndices, uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -768,8 +773,7 @@ public abstract partial class SharedTransformSystem
|
||||
[Pure]
|
||||
public Matrix3 GetWorldMatrix(EntityUid uid)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
return GetWorldMatrix(query.GetComponent(uid), query);
|
||||
return GetWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
@@ -777,7 +781,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetWorldMatrix(TransformComponent component)
|
||||
{
|
||||
return GetWorldMatrix(component, GetEntityQuery<TransformComponent>());
|
||||
return GetWorldMatrix(component, _xformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -835,6 +839,12 @@ public abstract partial class SharedTransformSystem
|
||||
return GetWorldPosition(component);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
|
||||
{
|
||||
return GetWorldPositionRotation(_xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(TransformComponent component)
|
||||
{
|
||||
@@ -937,7 +947,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
|
||||
{
|
||||
SetWorldPosition(component, worldPos, GetEntityQuery<TransformComponent>());
|
||||
SetWorldPosition(component, worldPos, _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -963,8 +973,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(EntityUid uid)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
return GetWorldRotation(query.GetComponent(uid), query);
|
||||
return GetWorldRotation(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
@@ -972,7 +981,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(TransformComponent component)
|
||||
{
|
||||
return GetWorldRotation(component, GetEntityQuery<TransformComponent>());
|
||||
return GetWorldRotation(component, _xformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -1040,7 +1049,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot)
|
||||
{
|
||||
SetWorldPositionRotation(component, worldPos, worldRot, GetEntityQuery<TransformComponent>());
|
||||
SetWorldPositionRotation(component, worldPos, worldRot, _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1105,15 +1114,14 @@ public abstract partial class SharedTransformSystem
|
||||
[Pure]
|
||||
public Matrix3 GetInvWorldMatrix(EntityUid uid)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
return GetInvWorldMatrix(query.GetComponent(uid), query);
|
||||
return GetInvWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetInvWorldMatrix(TransformComponent component)
|
||||
{
|
||||
return GetInvWorldMatrix(component, GetEntityQuery<TransformComponent>());
|
||||
return GetInvWorldMatrix(component, _xformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -1138,15 +1146,14 @@ public abstract partial class SharedTransformSystem
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
|
||||
GetWorldPositionRotationMatrix(EntityUid uid)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
return GetWorldPositionRotationMatrix(query.GetComponent(uid), query);
|
||||
return GetWorldPositionRotationMatrix(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
|
||||
GetWorldPositionRotationMatrix(TransformComponent xform)
|
||||
{
|
||||
return GetWorldPositionRotationMatrix(xform, GetEntityQuery<TransformComponent>());
|
||||
return GetWorldPositionRotationMatrix(xform, _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1176,7 +1183,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(TransformComponent xform)
|
||||
{
|
||||
return GetWorldPositionRotationInvMatrix(xform, GetEntityQuery<TransformComponent>());
|
||||
return GetWorldPositionRotationInvMatrix(xform, _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1200,15 +1207,14 @@ public abstract partial class SharedTransformSystem
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
|
||||
GetWorldPositionRotationMatrixWithInv(EntityUid uid)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
return GetWorldPositionRotationMatrixWithInv(query.GetComponent(uid), query);
|
||||
return GetWorldPositionRotationMatrixWithInv(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
|
||||
GetWorldPositionRotationMatrixWithInv(TransformComponent xform)
|
||||
{
|
||||
return GetWorldPositionRotationMatrixWithInv(xform, GetEntityQuery<TransformComponent>());
|
||||
return GetWorldPositionRotationMatrixWithInv(xform, _xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1231,9 +1237,8 @@ public abstract partial class SharedTransformSystem
|
||||
#region AttachToGridOrMap
|
||||
public void AttachToGridOrMap(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
if (query.Resolve(uid, ref xform))
|
||||
AttachToGridOrMap(uid, xform, query);
|
||||
if (_xformQuery.Resolve(uid, ref xform))
|
||||
AttachToGridOrMap(uid, xform, _xformQuery);
|
||||
}
|
||||
|
||||
public void AttachToGridOrMap(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> query)
|
||||
@@ -1271,18 +1276,17 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public bool TryGetMapOrGridCoordinates(EntityUid uid, [NotNullWhen(true)] out EntityCoordinates? coordinates, TransformComponent? xform = null)
|
||||
{
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
coordinates = null;
|
||||
|
||||
if (!query.Resolve(uid, ref xform))
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
return false;
|
||||
|
||||
if (!xform.ParentUid.IsValid())
|
||||
return false;
|
||||
|
||||
EntityUid newParent;
|
||||
var oldPos = GetWorldPosition(xform, query);
|
||||
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, query, out var gridUid, out _))
|
||||
var oldPos = GetWorldPosition(xform, _xformQuery);
|
||||
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, _xformQuery, out var gridUid, out _))
|
||||
{
|
||||
newParent = gridUid;
|
||||
}
|
||||
@@ -1295,7 +1299,7 @@ public abstract partial class SharedTransformSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
coordinates = new(newParent, GetInvWorldMatrix(newParent, query).Transform(oldPos));
|
||||
coordinates = new(newParent, GetInvWorldMatrix(newParent, _xformQuery).Transform(oldPos));
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
@@ -1332,8 +1336,8 @@ public abstract partial class SharedTransformSystem
|
||||
if (xform.Anchored && _metaQuery.TryGetComponent(xform.GridUid, out var meta) && meta.EntityLifeStage <= EntityLifeStage.MapInitialized)
|
||||
{
|
||||
var grid = Comp<MapGridComponent>(xform.GridUid.Value);
|
||||
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
|
||||
grid.RemoveFromSnapGridCell(tileIndices, uid);
|
||||
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
|
||||
xform._anchored = false;
|
||||
var anchorStateChangedEvent = new AnchorStateChangedEvent(xform, true);
|
||||
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
|
||||
@@ -1352,7 +1356,7 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (LifeStage(uid) > EntityLifeStage.Initialized)
|
||||
{
|
||||
SetGridId(uid, component, uid, GetEntityQuery<TransformComponent>());
|
||||
SetGridId(uid, component, uid, _xformQuery);
|
||||
return;
|
||||
}
|
||||
component._gridInitialized = true;
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
@@ -254,7 +255,7 @@ namespace Robust.Shared.GameObjects
|
||||
return GetWorldPosition(xform).Floored();
|
||||
|
||||
// We're on a grid, need to convert the coordinates to grid tiles.
|
||||
return _mapManager.GetGrid(xform.GridUid.Value).CoordinatesToTile(xform.Coordinates);
|
||||
return _map.CoordinatesToTile(xform.GridUid.Value, Comp<MapGridComponent>(xform.GridUid.Value), xform.Coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Unicode;
|
||||
using System.Timers;
|
||||
using JetBrains.Annotations;
|
||||
using Serilog.Events;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Shared.Log
|
||||
{
|
||||
@@ -85,9 +85,7 @@ namespace Robust.Shared.Log
|
||||
};
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[UsedImplicitly]
|
||||
#endif
|
||||
public static void TryDetachFromConsoleWindow()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
@@ -200,14 +198,15 @@ namespace Robust.Shared.Log
|
||||
internal static class WindowsConsole
|
||||
{
|
||||
|
||||
public static bool TryEnableVirtualTerminalProcessing()
|
||||
public static unsafe bool TryEnableVirtualTerminalProcessing()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stdHandle = NativeMethods.GetStdHandle(-11);
|
||||
NativeMethods.GetConsoleMode(stdHandle, out var mode);
|
||||
NativeMethods.SetConsoleMode(stdHandle, mode | 4);
|
||||
NativeMethods.GetConsoleMode(stdHandle, out mode);
|
||||
var stdHandle = Windows.GetStdHandle(unchecked((uint)-11));
|
||||
uint mode;
|
||||
Windows.GetConsoleMode(stdHandle, &mode);
|
||||
Windows.SetConsoleMode(stdHandle, mode | 4);
|
||||
Windows.GetConsoleMode(stdHandle, &mode);
|
||||
return (mode & 4) == 4;
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
@@ -226,7 +225,7 @@ namespace Robust.Shared.Log
|
||||
|
||||
public static void TryDetachFromConsoleWindow()
|
||||
{
|
||||
if (NativeMethods.GetConsoleWindow() == default
|
||||
if (Windows.GetConsoleWindow() == default
|
||||
|| Debugger.IsAttached
|
||||
|| System.Console.IsOutputRedirected
|
||||
|| System.Console.IsErrorRedirected
|
||||
@@ -235,27 +234,12 @@ namespace Robust.Shared.Log
|
||||
return;
|
||||
}
|
||||
|
||||
_freedConsole = NativeMethods.FreeConsole();
|
||||
_freedConsole = Windows.FreeConsole();
|
||||
}
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
public const int CodePageUtf8 = 65001;
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, int mode);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
internal static extern bool GetConsoleMode(IntPtr handle, out int mode);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
internal static extern IntPtr GetStdHandle(int handle);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
internal static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
internal static extern IntPtr GetConsoleWindow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -27,10 +20,8 @@ namespace Robust.Shared.Map.Components
|
||||
[NetworkedComponent]
|
||||
public sealed class MapGridComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private SharedMapSystem MapSystem => _entManager.System<SharedMapSystem>();
|
||||
|
||||
// This field is used for deserialization internally in the map loader.
|
||||
// If you want to remove this, you would have to restructure the map save file.
|
||||
@@ -40,6 +31,9 @@ namespace Robust.Shared.Map.Components
|
||||
|
||||
[DataField("chunkSize")] internal ushort ChunkSize = 16;
|
||||
|
||||
[ViewVariables]
|
||||
public int ChunkCount => Chunks.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the side of a square tile in world units.
|
||||
/// </summary>
|
||||
@@ -70,7 +64,7 @@ namespace Robust.Shared.Map.Components
|
||||
internal readonly Dictionary<Vector2i, MapChunk> Chunks = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Box2 LocalAABB { get; private set; }
|
||||
public Box2 LocalAABB { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to enable or disable grid splitting.
|
||||
@@ -79,849 +73,221 @@ namespace Robust.Shared.Map.Components
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("canSplit")]
|
||||
public bool CanSplit = true;
|
||||
|
||||
internal void RemoveChunk(Vector2i origin)
|
||||
{
|
||||
if (!Chunks.TryGetValue(origin, out var chunk))
|
||||
return;
|
||||
|
||||
if (_netManager.IsServer)
|
||||
ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
|
||||
|
||||
chunk.Fixtures.Clear();
|
||||
Chunks.Remove(origin);
|
||||
|
||||
if (Chunks.Count == 0)
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, new EmptyGridEvent { GridId = Owner }, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
|
||||
/// </summary>
|
||||
internal void RegenerateCollision(IReadOnlySet<MapChunk> chunks)
|
||||
{
|
||||
if (_entMan.HasComponent<MapComponent>(Owner))
|
||||
return;
|
||||
|
||||
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
|
||||
var removedChunks = new List<MapChunk>();
|
||||
var fixtureSystem = _entMan.EntitySysManager.GetEntitySystem<FixtureSystem>();
|
||||
_entMan.EntitySysManager.TryGetEntitySystem(out SharedGridFixtureSystem? system);
|
||||
|
||||
foreach (var mapChunk in chunks)
|
||||
{
|
||||
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
|
||||
// generate collision rectangles for this chunk based on filled tiles.
|
||||
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
|
||||
mapChunk.CachedBounds = localBounds;
|
||||
|
||||
if (mapChunk.FilledTiles > 0)
|
||||
chunkRectangles.Add(mapChunk, rectangles);
|
||||
else
|
||||
{
|
||||
// Gone. Reduced to atoms
|
||||
// Need to do this before RemoveChunk because it clears fixtures.
|
||||
FixturesComponent? manager = null;
|
||||
PhysicsComponent? body = null;
|
||||
TransformComponent? xform = null;
|
||||
|
||||
foreach (var fixture in mapChunk.Fixtures)
|
||||
{
|
||||
fixtureSystem.DestroyFixture(Owner, fixture, false, manager: manager, body: body, xform: xform);
|
||||
}
|
||||
|
||||
RemoveChunk(mapChunk.Indices);
|
||||
removedChunks.Add(mapChunk);
|
||||
}
|
||||
}
|
||||
|
||||
LocalAABB = new Box2();
|
||||
foreach (var chunk in Chunks.Values)
|
||||
{
|
||||
var chunkBounds = chunk.CachedBounds;
|
||||
|
||||
if (chunkBounds.Size.Equals(Vector2i.Zero))
|
||||
continue;
|
||||
|
||||
if (LocalAABB.Size == Vector2.Zero)
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalAABB = gridBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalAABB = LocalAABB.Union(gridBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// May have been deleted from the bulk update above!
|
||||
if (_entMan.Deleted(Owner))
|
||||
return;
|
||||
|
||||
// TODO: Move this to the component when we combine.
|
||||
_entMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>().WakeBody(Owner);
|
||||
_entMan.EntitySysManager.GetEntitySystem<SharedMapSystem>().OnGridBoundsChange(Owner, this);
|
||||
system?.RegenerateCollision(Owner, chunkRectangles, removedChunks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the chunk local bounds of this chunk.
|
||||
/// </summary>
|
||||
internal void RegenerateCollision(MapChunk mapChunk)
|
||||
{
|
||||
RegenerateCollision(new HashSet<MapChunk> { mapChunk });
|
||||
}
|
||||
|
||||
#region TileAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(MapCoordinates coords)
|
||||
{
|
||||
return GetTileRef(CoordinatesToTile(coords));
|
||||
return MapSystem.GetTileRef(Owner, this, coords);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(EntityCoordinates coords)
|
||||
{
|
||||
return GetTileRef(CoordinatesToTile(coords));
|
||||
return MapSystem.GetTileRef(Owner, this, coords);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(Vector2i tileCoordinates)
|
||||
{
|
||||
var chunkIndices = GridTileToChunkIndices(tileCoordinates);
|
||||
|
||||
if (!Chunks.TryGetValue(chunkIndices, out var output))
|
||||
{
|
||||
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
|
||||
return new TileRef(Owner, tileCoordinates.X, tileCoordinates.Y, default);
|
||||
}
|
||||
|
||||
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
|
||||
return GetTileRef(output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
|
||||
return MapSystem.GetTileRef(Owner, this, tileCoordinates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given chunk indices.
|
||||
/// </summary>
|
||||
/// <param name="mapChunk"></param>
|
||||
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
|
||||
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
internal TileRef GetTileRef(MapChunk mapChunk, ushort xIndex, ushort yIndex)
|
||||
{
|
||||
if (xIndex >= mapChunk.ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
|
||||
|
||||
if (yIndex >= mapChunk.ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
|
||||
|
||||
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
|
||||
return new TileRef(Owner, indices, mapChunk.GetTile(xIndex, yIndex));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
|
||||
{
|
||||
foreach (var kvChunk in Chunks)
|
||||
{
|
||||
var chunk = kvChunk.Value;
|
||||
for (ushort x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
|
||||
if (ignoreEmpty && tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * ChunkSize;
|
||||
yield return new TileRef(Owner, gridX, gridY, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return MapSystem.GetAllTiles(Owner, this, ignoreEmpty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridTileEnumerator GetAllTilesEnumerator(bool ignoreEmpty = true)
|
||||
{
|
||||
return new GridTileEnumerator(Owner, Chunks.GetEnumerator(), ChunkSize, ignoreEmpty);
|
||||
return MapSystem.GetAllTilesEnumerator(Owner, this, ignoreEmpty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(EntityCoordinates coords, Tile tile)
|
||||
{
|
||||
var localTile = CoordinatesToTile(coords);
|
||||
SetTile(new Vector2i(localTile.X, localTile.Y), tile);
|
||||
MapSystem.SetTile(Owner, this, coords, tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(Vector2i gridIndices, Tile tile)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
|
||||
// Yeah it's noodly.
|
||||
// RegenerateCollision(chunk);
|
||||
MapSystem.SetTile(Owner, this, gridIndices, tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles)
|
||||
{
|
||||
if (tiles.Count == 0) return;
|
||||
|
||||
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / ChunkSize));
|
||||
|
||||
foreach (var (gridIndices, tile) in tiles)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
|
||||
chunks.Add(chunk);
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
}
|
||||
|
||||
RegenerateCollision(chunks);
|
||||
MapSystem.SetTiles(Owner, this, tiles);
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2Rotated localArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var localAABB = localArea.CalcBoundingBox();
|
||||
return GetLocalTilesIntersecting(localAABB, ignoreEmpty, predicate);
|
||||
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Box2Rotated worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
|
||||
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
|
||||
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2 localArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
|
||||
// that way we can avoid the GetComp here.
|
||||
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
|
||||
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
|
||||
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
|
||||
|
||||
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
|
||||
{
|
||||
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
|
||||
{
|
||||
var gridChunk = GridTileToChunkIndices(new Vector2i(x, y));
|
||||
|
||||
if (Chunks.TryGetValue(gridChunk, out var chunk))
|
||||
{
|
||||
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
|
||||
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
yield return tile;
|
||||
}
|
||||
else if (!ignoreEmpty)
|
||||
{
|
||||
var tile = new TileRef(Owner, x, y, new Tile());
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Circle worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
|
||||
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
|
||||
var circleGridPos = new EntityCoordinates(Owner, WorldToLocal(worldArea.Position));
|
||||
|
||||
foreach (var tile in GetTilesIntersecting(aabb, ignoreEmpty, predicate))
|
||||
{
|
||||
var local = GridTileToLocal(tile.GridIndices);
|
||||
|
||||
if (!local.TryDistance(_entMan, circleGridPos, out var distance))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (distance <= worldArea.Radius)
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetTile(Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
// Similar to TryGetTileRef but for the tiles intersecting iterators.
|
||||
var gridChunk = GridTileToChunkIndices(indices);
|
||||
|
||||
if (Chunks.TryGetValue(gridChunk, out var chunk))
|
||||
{
|
||||
var chunkTile = chunk.GridTileToChunkTile(indices);
|
||||
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
{
|
||||
tileRef = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tileRef = tile;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (!ignoreEmpty)
|
||||
{
|
||||
var tile = new TileRef(Owner, indices.X, indices.Y, Tile.Empty);
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tileRef = tile;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
tileRef = null;
|
||||
return false;
|
||||
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
|
||||
}
|
||||
|
||||
#endregion TileAccess
|
||||
|
||||
#region ChunkAccess
|
||||
|
||||
/// <summary>
|
||||
/// The total number of allocated chunks in the grid.
|
||||
/// </summary>
|
||||
public int ChunkCount => Chunks.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal MapChunk GetOrAddChunk(int xIndex, int yIndex)
|
||||
{
|
||||
return GetOrAddChunk(new Vector2i(xIndex, yIndex));
|
||||
}
|
||||
|
||||
internal bool TryGetChunk(Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
|
||||
{
|
||||
return Chunks.TryGetValue(chunkIndices, out chunk);
|
||||
return MapSystem.TryGetChunk(Owner, this, chunkIndices, out chunk);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal MapChunk GetOrAddChunk(Vector2i chunkIndices)
|
||||
{
|
||||
if (Chunks.TryGetValue(chunkIndices, out var output))
|
||||
return output;
|
||||
|
||||
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, ChunkSize);
|
||||
newChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
|
||||
if (Initialized)
|
||||
newChunk.TileModified += OnTileModified;
|
||||
|
||||
return Chunks[chunkIndices] = newChunk;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasChunk(Vector2i chunkIndices)
|
||||
{
|
||||
return Chunks.ContainsKey(chunkIndices);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks()
|
||||
{
|
||||
return Chunks;
|
||||
return MapSystem.GetMapChunks(Owner, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal ChunkEnumerator GetMapChunks(Box2 worldAABB)
|
||||
{
|
||||
var localAABB = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix
|
||||
.TransformBox(worldAABB);
|
||||
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal ChunkEnumerator GetMapChunks(Box2Rotated worldArea)
|
||||
{
|
||||
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
return new ChunkEnumerator(Chunks, localArea, ChunkSize);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetLocalMapChunks(Box2 localAABB)
|
||||
{
|
||||
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
|
||||
return MapSystem.GetMapChunks(Owner, this, worldArea);
|
||||
}
|
||||
|
||||
#endregion ChunkAccess
|
||||
|
||||
#region SnapGridAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AnchoredEntityCount(Vector2i pos)
|
||||
{
|
||||
var gridChunkPos = GridTileToChunkIndices(pos);
|
||||
|
||||
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return 0;
|
||||
|
||||
var (x, y) = chunk.GridTileToChunkTile(pos);
|
||||
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(MapCoordinates coords)
|
||||
{
|
||||
return GetAnchoredEntities(TileIndicesFor(coords));
|
||||
return MapSystem.GetAnchoredEntities(Owner, this, coords);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(EntityCoordinates coords)
|
||||
{
|
||||
return GetAnchoredEntities(TileIndicesFor(coords));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(Vector2i pos)
|
||||
{
|
||||
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
|
||||
// create an entire chunk for it.
|
||||
var gridChunkPos = GridTileToChunkIndices(pos);
|
||||
|
||||
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
return MapSystem.GetAnchoredEntities(Owner, this, pos);
|
||||
}
|
||||
|
||||
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos)
|
||||
{
|
||||
var gridChunkPos = GridTileToChunkIndices(pos);
|
||||
|
||||
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
|
||||
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
return snapgrid == null
|
||||
? AnchoredEntitiesEnumerator.Empty
|
||||
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
|
||||
return MapSystem.GetAnchoredEntitiesEnumerator(Owner, this, pos);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetLocalAnchoredEntities(Box2 localAABB)
|
||||
{
|
||||
foreach (var tile in GetLocalTilesIntersecting(localAABB, true, null))
|
||||
{
|
||||
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
return MapSystem.GetLocalAnchoredEntities(Owner, this, localAABB);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB)
|
||||
{
|
||||
foreach (var tile in GetTilesIntersecting(worldAABB))
|
||||
{
|
||||
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
return MapSystem.GetAnchoredEntities(Owner, this, worldAABB);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetAnchoredEntities(Box2Rotated worldBounds)
|
||||
{
|
||||
foreach (var tile in GetTilesIntersecting(worldBounds))
|
||||
{
|
||||
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
yield return ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i TileIndicesFor(EntityCoordinates coords)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
|
||||
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
|
||||
#endif
|
||||
|
||||
return SnapGridLocalCellFor(LocalToGrid(coords));
|
||||
return MapSystem.TileIndicesFor(Owner, this, coords);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i TileIndicesFor(MapCoordinates worldPos)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
|
||||
DebugTools.Assert(mapId == worldPos.MapId);
|
||||
#endif
|
||||
|
||||
var localPos = WorldToLocal(worldPos.Position);
|
||||
return SnapGridLocalCellFor(localPos);
|
||||
return MapSystem.TileIndicesFor(Owner, this, worldPos);
|
||||
}
|
||||
|
||||
private Vector2i SnapGridLocalCellFor(Vector2 localPos)
|
||||
{
|
||||
var x = (int)Math.Floor(localPos.X / TileSize);
|
||||
var y = (int)Math.Floor(localPos.Y / TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public bool IsAnchored(EntityCoordinates coords, EntityUid euid)
|
||||
{
|
||||
var tilePos = TileIndicesFor(coords);
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(tilePos);
|
||||
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
return snapgrid?.Contains(euid) == true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AddToSnapGridCell(Vector2i pos, EntityUid euid)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
|
||||
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
|
||||
return false;
|
||||
|
||||
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AddToSnapGridCell(EntityCoordinates coords, EntityUid euid)
|
||||
{
|
||||
return AddToSnapGridCell(TileIndicesFor(coords), euid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(Vector2i pos, EntityUid euid)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(EntityCoordinates coords, EntityUid euid)
|
||||
{
|
||||
RemoveFromSnapGridCell(TileIndicesFor(coords), euid);
|
||||
}
|
||||
|
||||
private (MapChunk, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
|
||||
{
|
||||
var gridChunkIndices = GridTileToChunkIndices(pos);
|
||||
var chunk = GetOrAddChunk(gridChunkIndices);
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
return (chunk, chunkTile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetInDir(EntityCoordinates position, Direction dir)
|
||||
{
|
||||
var pos = SharedMapSystem.GetDirection(TileIndicesFor(position), dir);
|
||||
return GetAnchoredEntities(pos);
|
||||
return MapSystem.GetInDir(Owner, this, position, dir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetOffset(EntityCoordinates coords, Vector2i offset)
|
||||
{
|
||||
var pos = TileIndicesFor(coords) + offset;
|
||||
return GetAnchoredEntities(pos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetLocal(EntityCoordinates coords)
|
||||
{
|
||||
return GetAnchoredEntities(TileIndicesFor(coords));
|
||||
return MapSystem.GetLocal(Owner, this, coords);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityCoordinates DirectionToGrid(EntityCoordinates coords, Direction direction)
|
||||
{
|
||||
return GridTileToLocal(SharedMapSystem.GetDirection(TileIndicesFor(coords), direction));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityCoordinates coords)
|
||||
{
|
||||
var position = TileIndicesFor(coords);
|
||||
foreach (var cell in GetAnchoredEntities(position))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, 1)))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, -1)))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(position + new Vector2i(1, 0)))
|
||||
yield return cell;
|
||||
foreach (var cell in GetAnchoredEntities(position + new Vector2i(-1, 0)))
|
||||
yield return cell;
|
||||
return MapSystem.GetCardinalNeighborCells(Owner, this, coords);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityCoordinates coords, int n)
|
||||
{
|
||||
var position = TileIndicesFor(coords);
|
||||
|
||||
for (var y = -n; y <= n; ++y)
|
||||
for (var x = -n; x <= n; ++x)
|
||||
{
|
||||
var enumerator = GetAnchoredEntitiesEnumerator(position + new Vector2i(x, y));
|
||||
|
||||
while (enumerator.MoveNext(out var cell))
|
||||
{
|
||||
yield return cell.Value;
|
||||
}
|
||||
}
|
||||
return MapSystem.GetCellsInSquareArea(Owner, this, coords, n);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transforms
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldToLocal(Vector2 posWorld)
|
||||
{
|
||||
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
|
||||
return matrix.Transform(posWorld);
|
||||
return MapSystem.WorldToLocal(Owner, this, posWorld);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityCoordinates MapToGrid(MapCoordinates posWorld)
|
||||
{
|
||||
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
|
||||
|
||||
if (posWorld.MapId != mapId)
|
||||
throw new ArgumentException(
|
||||
$"Grid {Owner} is on map {mapId}, but coords are on map {posWorld.MapId}.",
|
||||
nameof(posWorld));
|
||||
|
||||
if (!_mapManager.TryGetGrid(Owner, out var grid))
|
||||
{
|
||||
return new EntityCoordinates(_mapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
|
||||
}
|
||||
|
||||
return new EntityCoordinates(((Component) grid).Owner, WorldToLocal(posWorld.Position));
|
||||
return MapSystem.MapToGrid(Owner, posWorld);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 LocalToWorld(Vector2 posLocal)
|
||||
{
|
||||
var matrix = _entMan.GetComponent<TransformComponent>(Owner).WorldMatrix;
|
||||
return matrix.Transform(posLocal);
|
||||
return MapSystem.LocalToWorld(Owner, this, posLocal);
|
||||
}
|
||||
|
||||
public Vector2i WorldToTile(Vector2 posWorld)
|
||||
{
|
||||
var local = WorldToLocal(posWorld);
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new Vector2i(x, y);
|
||||
return MapSystem.WorldToTile(Owner, this, posWorld);
|
||||
}
|
||||
|
||||
public Vector2i LocalToTile(EntityCoordinates coordinates)
|
||||
{
|
||||
var position = LocalToGrid(coordinates);
|
||||
return new Vector2i((int) Math.Floor(position.X / TileSize), (int) Math.Floor(position.Y / TileSize));
|
||||
return MapSystem.LocalToTile(Owner, this, coordinates);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i CoordinatesToTile(MapCoordinates coords)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
|
||||
DebugTools.Assert(mapId == coords.MapId);
|
||||
#endif
|
||||
|
||||
var local = WorldToLocal(coords.Position);
|
||||
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i CoordinatesToTile(EntityCoordinates coords)
|
||||
{
|
||||
#if DEBUG
|
||||
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
|
||||
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
|
||||
#endif
|
||||
var local = LocalToGrid(coords);
|
||||
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i LocalToChunkIndices(EntityCoordinates gridPos)
|
||||
{
|
||||
var local = LocalToGrid(gridPos);
|
||||
|
||||
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
|
||||
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public Vector2 LocalToGrid(EntityCoordinates position)
|
||||
{
|
||||
return position.EntityId == Owner
|
||||
? position.Position
|
||||
: WorldToLocal(position.ToMapPos(_entMan));
|
||||
return MapSystem.CoordinatesToTile(Owner, this, coords);
|
||||
}
|
||||
|
||||
public bool CollidesWithGrid(Vector2i indices)
|
||||
{
|
||||
var chunkIndices = GridTileToChunkIndices(indices);
|
||||
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
return false;
|
||||
|
||||
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
||||
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
|
||||
return MapSystem.CollidesWithGrid(Owner, this, indices);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i GridTileToChunkIndices(Vector2i gridTile)
|
||||
{
|
||||
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
|
||||
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
|
||||
|
||||
return new Vector2i(x, y);
|
||||
return MapSystem.GridTileToChunkIndices(Owner, this, gridTile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityCoordinates GridTileToLocal(Vector2i gridTile)
|
||||
{
|
||||
return new(Owner,
|
||||
new Vector2(gridTile.X * TileSize + (TileSize / 2f), gridTile.Y * TileSize + (TileSize / 2f)));
|
||||
return MapSystem.GridTileToLocal(Owner, this, gridTile);
|
||||
}
|
||||
|
||||
public Vector2 GridTileToWorldPos(Vector2i gridTile)
|
||||
{
|
||||
var locX = gridTile.X * TileSize + (TileSize / 2f);
|
||||
var locY = gridTile.Y * TileSize + (TileSize / 2f);
|
||||
var xform = _entMan.GetComponent<TransformComponent>(Owner);
|
||||
|
||||
return xform.WorldMatrix.Transform(new Vector2(locX, locY));
|
||||
return MapSystem.GridTileToWorldPos(Owner, this, gridTile);
|
||||
}
|
||||
|
||||
public MapCoordinates GridTileToWorld(Vector2i gridTile)
|
||||
{
|
||||
var parentMapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
|
||||
|
||||
return new(GridTileToWorldPos(gridTile), parentMapId);
|
||||
return MapSystem.GridTileToWorld(Owner, this, gridTile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetTileRef(Vector2i indices, out TileRef tile)
|
||||
{
|
||||
var chunkIndices = GridTileToChunkIndices(indices);
|
||||
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
{
|
||||
tile = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
||||
tile = GetTileRef(chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
|
||||
return true;
|
||||
return MapSystem.TryGetTileRef(Owner, this, indices, out tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetTileRef(EntityCoordinates coords, out TileRef tile)
|
||||
{
|
||||
return TryGetTileRef(CoordinatesToTile(coords), out tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetTileRef(Vector2 worldPos, out TileRef tile)
|
||||
{
|
||||
return TryGetTileRef(WorldToTile(worldPos), out tile);
|
||||
}
|
||||
|
||||
#endregion Transforms
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the world space AABB for this chunk.
|
||||
/// </summary>
|
||||
internal Box2 CalcWorldAABB(MapChunk mapChunk)
|
||||
{
|
||||
var (position, rotation) =
|
||||
_entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
|
||||
|
||||
var chunkPosition = mapChunk.Indices;
|
||||
var tileScale = TileSize;
|
||||
var chunkScale = mapChunk.ChunkSize;
|
||||
|
||||
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
|
||||
|
||||
return new Box2Rotated(
|
||||
((Box2)mapChunk.CachedBounds
|
||||
.Scale(tileScale))
|
||||
.Translated(worldPos),
|
||||
rotation, worldPos).CalcBoundingBox();
|
||||
}
|
||||
|
||||
internal void OnTileModified(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
|
||||
bool shapeChanged)
|
||||
{
|
||||
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
|
||||
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
|
||||
mapChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
_entMan.Dirty(this);
|
||||
|
||||
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
|
||||
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
|
||||
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
|
||||
if (!_mapManager.SuppressOnTileChanged)
|
||||
{
|
||||
var newTileRef = new TileRef(Owner, gridTile, newTile);
|
||||
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
|
||||
}
|
||||
|
||||
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
|
||||
{
|
||||
RegenerateCollision(mapChunk);
|
||||
}
|
||||
return MapSystem.TryGetTileRef(Owner, this, coords, out tile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,66 +24,67 @@ namespace Robust.Shared.Map
|
||||
IoCManager.Resolve(ref entityManager, ref mapManager);
|
||||
|
||||
var gridId = coords.GetGridUid(entityManager);
|
||||
var mapSystem = entityManager.System<SharedMapSystem>();
|
||||
|
||||
if (mapManager.TryGetGrid(gridId, out var mapGrid))
|
||||
{
|
||||
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
|
||||
return mapSystem.GridTileToLocal(gridId.Value, mapGrid, mapSystem.CoordinatesToTile(gridId.Value, mapGrid, coords));
|
||||
}
|
||||
else
|
||||
|
||||
var mapCoords = coords.ToMap(entityManager);
|
||||
|
||||
if (mapManager.TryFindGridAt(mapCoords, out var gridUid, out mapGrid))
|
||||
{
|
||||
var mapCoords = coords.ToMap(entityManager);
|
||||
|
||||
if (mapManager.TryFindGridAt(mapCoords, out _, out mapGrid))
|
||||
{
|
||||
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
|
||||
}
|
||||
|
||||
// create a box around the cursor
|
||||
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
|
||||
|
||||
// find grids in search box
|
||||
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
|
||||
|
||||
// find closest grid intersecting our search box.
|
||||
MapGridComponent? closest = null;
|
||||
var distance = float.PositiveInfinity;
|
||||
var intersect = new Box2();
|
||||
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var grid in gridsInArea)
|
||||
{
|
||||
var gridXform = xformQuery.GetComponent(grid.Owner);
|
||||
// TODO: Use CollisionManager to get nearest edge.
|
||||
|
||||
// figure out closest intersect
|
||||
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
|
||||
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
|
||||
|
||||
if (gridDist >= distance)
|
||||
continue;
|
||||
|
||||
distance = gridDist;
|
||||
closest = grid;
|
||||
intersect = gridIntersect;
|
||||
}
|
||||
|
||||
if (closest != null) // stick to existing grid
|
||||
{
|
||||
// round to nearest cardinal dir
|
||||
var normal = mapCoords.Position - intersect.Center;
|
||||
|
||||
// round coords to center of tile
|
||||
var tileIndices = closest.WorldToTile(intersect.Center);
|
||||
var tileCenterWorld = closest.GridTileToWorldPos(tileIndices);
|
||||
|
||||
// move mouse one tile out along normal
|
||||
var newTilePos = tileCenterWorld + normal * closest.TileSize;
|
||||
|
||||
coords = new EntityCoordinates(closest.Owner, closest.WorldToLocal(newTilePos));
|
||||
}
|
||||
//else free place
|
||||
return mapSystem.GridTileToLocal(gridUid, mapGrid, mapSystem.CoordinatesToTile(gridUid, mapGrid, coords));
|
||||
}
|
||||
|
||||
// create a box around the cursor
|
||||
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
|
||||
|
||||
// find grids in search box
|
||||
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
|
||||
|
||||
// find closest grid intersecting our search box.
|
||||
gridUid = EntityUid.Invalid;
|
||||
MapGridComponent? closest = null;
|
||||
var distance = float.PositiveInfinity;
|
||||
var intersect = new Box2();
|
||||
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var grid in gridsInArea)
|
||||
{
|
||||
var gridXform = xformQuery.GetComponent(grid.Owner);
|
||||
// TODO: Use CollisionManager to get nearest edge.
|
||||
|
||||
// figure out closest intersect
|
||||
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
|
||||
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
|
||||
|
||||
if (gridDist >= distance)
|
||||
continue;
|
||||
|
||||
gridUid = grid.Owner;
|
||||
distance = gridDist;
|
||||
closest = grid;
|
||||
intersect = gridIntersect;
|
||||
}
|
||||
|
||||
if (closest != null) // stick to existing grid
|
||||
{
|
||||
// round to nearest cardinal dir
|
||||
var normal = mapCoords.Position - intersect.Center;
|
||||
|
||||
// round coords to center of tile
|
||||
var tileIndices = mapSystem.WorldToTile(gridUid, closest, intersect.Center);
|
||||
var tileCenterWorld = mapSystem.GridTileToWorldPos(gridUid, closest, tileIndices);
|
||||
|
||||
// move mouse one tile out along normal
|
||||
var newTilePos = tileCenterWorld + normal * closest.TileSize;
|
||||
|
||||
coords = new EntityCoordinates(gridUid, mapSystem.WorldToLocal(gridUid, closest, newTilePos));
|
||||
}
|
||||
//else free place
|
||||
|
||||
return coords;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,10 +191,12 @@ namespace Robust.Shared.Map
|
||||
if(!IsValid(entityManager))
|
||||
return new Vector2i();
|
||||
|
||||
var mapSystem = entityManager.System<SharedMapSystem>();
|
||||
var gridIdOpt = GetGridUid(entityManager);
|
||||
if (gridIdOpt is { } gridId && gridId.IsValid())
|
||||
{
|
||||
return mapManager.GetGrid(gridId).GetTileRef(this).GridIndices;
|
||||
var grid = mapManager.GetGrid(gridId);
|
||||
return mapSystem.GetTileRef(gridId, grid, this).GridIndices;
|
||||
}
|
||||
|
||||
var vec = ToMapPos(entityManager, transformSystem);
|
||||
|
||||
21
Robust.Shared/Map/Events/RegenerateGridBoundsEvent.cs
Normal file
21
Robust.Shared/Map/Events/RegenerateGridBoundsEvent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on a grid to get its bounds.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Really this exists to get around test dependency creeping.
|
||||
/// </remarks>
|
||||
[ByRefEvent]
|
||||
internal readonly record struct RegenerateGridBoundsEvent(EntityUid Entity, Dictionary<MapChunk, List<Box2i>> ChunkRectangles, List<MapChunk> RemovedChunks)
|
||||
{
|
||||
public readonly EntityUid Entity = Entity;
|
||||
|
||||
public readonly Dictionary<MapChunk, List<Box2i>> ChunkRectangles = ChunkRectangles;
|
||||
|
||||
public readonly List<MapChunk> RemovedChunks = RemovedChunks;
|
||||
}
|
||||
@@ -131,6 +131,11 @@ namespace Robust.Shared.Map
|
||||
|
||||
void FindGridsIntersecting<TState>(MapId mapId, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = false, bool includeMap = true);
|
||||
|
||||
void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, GridCallback callback, bool approx = false, bool includeMap = true);
|
||||
|
||||
void FindGridsIntersecting<TState>(MapId mapId, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback, bool approx = false, bool includeMap = true);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the grids intersecting this AABB.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,15 +22,9 @@ namespace Robust.Shared.Map
|
||||
|
||||
private readonly Vector2i _gridIndices;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Tile[,] _tiles;
|
||||
[ViewVariables] internal readonly Tile[,] Tiles;
|
||||
private readonly SnapGridCell[,] _snapGrid;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a tile is modified on this chunk.
|
||||
/// </summary>
|
||||
public event TileModifiedDelegate? TileModified;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps a running count of the number of filled tiles in this chunk.
|
||||
/// </summary>
|
||||
@@ -38,7 +32,7 @@ namespace Robust.Shared.Map
|
||||
/// This will always be between 1 and <see cref="ChunkSize"/>^2.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
internal int FilledTiles { get; private set; }
|
||||
internal int FilledTiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Chunk-local AABB of this chunk.
|
||||
@@ -73,7 +67,7 @@ namespace Robust.Shared.Map
|
||||
_gridIndices = new Vector2i(x, y);
|
||||
ChunkSize = chunkSize;
|
||||
|
||||
_tiles = new Tile[ChunkSize, ChunkSize];
|
||||
Tiles = new Tile[ChunkSize, ChunkSize];
|
||||
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
|
||||
}
|
||||
|
||||
@@ -113,58 +107,12 @@ namespace Robust.Shared.Map
|
||||
if (yIndex >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
|
||||
|
||||
return _tiles[xIndex, yIndex];
|
||||
return Tiles[xIndex, yIndex];
|
||||
}
|
||||
|
||||
public Tile GetTile(Vector2i indices)
|
||||
{
|
||||
return _tiles[indices.X, indices.Y];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces a single tile inside of the chunk.
|
||||
/// </summary>
|
||||
/// <param name="xIndex">The X tile index relative to the chunk.</param>
|
||||
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
|
||||
/// <param name="tile">The new tile to insert.</param>
|
||||
public void SetTile(ushort xIndex, ushort yIndex, Tile tile)
|
||||
{
|
||||
if (xIndex >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
|
||||
|
||||
if (yIndex >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
|
||||
|
||||
// same tile, no point to continue
|
||||
if (_tiles[xIndex, yIndex] == tile)
|
||||
return;
|
||||
|
||||
var oldTile = _tiles[xIndex, yIndex];
|
||||
var oldFilledTiles = FilledTiles;
|
||||
|
||||
if (oldTile.IsEmpty != tile.IsEmpty)
|
||||
{
|
||||
if (oldTile.IsEmpty)
|
||||
{
|
||||
FilledTiles += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
FilledTiles -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
var shapeChanged = oldFilledTiles != FilledTiles;
|
||||
DebugTools.Assert(FilledTiles >= 0);
|
||||
|
||||
_tiles[xIndex, yIndex] = tile;
|
||||
|
||||
var tileIndices = new Vector2i(xIndex, yIndex);
|
||||
|
||||
// God I hate C# events sometimes.
|
||||
DebugTools.Assert(TileModified == null || TileModified.GetInvocationList().Length <= 1);
|
||||
|
||||
TileModified?.Invoke(this, tileIndices, tile, oldTile, shapeChanged);
|
||||
return Tiles[indices.X, indices.Y];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -266,6 +214,48 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
public List<EntityUid>? Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the tile without any callbacks.
|
||||
/// Do not call this unless you know what you are doing.
|
||||
/// </summary>
|
||||
internal bool TrySetTile(ushort xIndex, ushort yIndex, Tile tile, out Tile oldTile, out bool shapeChanged)
|
||||
{
|
||||
if (xIndex >= Tiles.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
|
||||
|
||||
if (yIndex >= Tiles.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
|
||||
|
||||
// same tile, no point to continue
|
||||
if (Tiles[xIndex, yIndex] == tile)
|
||||
{
|
||||
oldTile = Tile.Empty;
|
||||
shapeChanged = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
oldTile = Tiles[xIndex, yIndex];
|
||||
var oldFilledTiles = FilledTiles;
|
||||
|
||||
if (oldTile.IsEmpty != tile.IsEmpty)
|
||||
{
|
||||
if (oldTile.IsEmpty)
|
||||
{
|
||||
FilledTiles += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
FilledTiles -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
shapeChanged = oldFilledTiles != FilledTiles;
|
||||
DebugTools.Assert(FilledTiles >= 0);
|
||||
|
||||
Tiles[xIndex, yIndex] = tile;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -276,5 +266,5 @@ namespace Robust.Shared.Map
|
||||
/// <param name="newTile">New version of the tile.</param>
|
||||
/// <param name="oldTile">Old version of the tile.</param>
|
||||
/// <param name="chunkShapeChanged">If changing this tile changed the shape of the chunk.</param>
|
||||
internal delegate void TileModifiedDelegate(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
|
||||
internal delegate void TileModifiedDelegate(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
@@ -29,24 +27,20 @@ internal partial class MapManager
|
||||
return;
|
||||
}
|
||||
|
||||
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xformSystem = EntityManager.System<SharedTransformSystem>();
|
||||
var state = (worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
|
||||
var state = (worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
|
||||
|
||||
gridTree.Tree.Query(ref state,
|
||||
static (ref (Box2 worldAABB,
|
||||
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
|
||||
GridCallback callback,
|
||||
bool approx,
|
||||
EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery,
|
||||
MapManager mapManager,
|
||||
SharedTransformSystem xformSystem) tuple,
|
||||
DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
var data = tuple.gridTree.GetUserData(proxy);
|
||||
|
||||
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
|
||||
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
|
||||
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -77,10 +71,7 @@ internal partial class MapManager
|
||||
callback(mapUid, grid, ref state);
|
||||
}
|
||||
|
||||
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xformSystem = EntityManager.System<SharedTransformSystem>();
|
||||
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
|
||||
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
|
||||
|
||||
gridTree.Tree.Query(ref state2, static (ref (
|
||||
TState state,
|
||||
@@ -88,15 +79,13 @@ internal partial class MapManager
|
||||
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
|
||||
GridCallback<TState> callback,
|
||||
bool approx,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
MapManager mapManager,
|
||||
SharedTransformSystem xformSystem) tuple,
|
||||
DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
var data = tuple.gridTree.GetUserData(proxy);
|
||||
|
||||
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
|
||||
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
|
||||
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -107,22 +96,30 @@ internal partial class MapManager
|
||||
state = state2.state;
|
||||
}
|
||||
|
||||
private static bool IsIntersecting(
|
||||
public void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, GridCallback callback, bool approx = false,
|
||||
bool includeMap = true)
|
||||
{
|
||||
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), callback, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(MapId mapId, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
|
||||
bool approx = false, bool includeMap = true)
|
||||
{
|
||||
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
private bool IsIntersecting(
|
||||
Box2 aabb,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSystem)
|
||||
MapGridComponent grid)
|
||||
{
|
||||
var xformComp = xformQuery.GetComponent(gridUid);
|
||||
var (worldPos, worldRot, matrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(xformComp, xformQuery);
|
||||
var (worldPos, worldRot, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridUid);
|
||||
var overlap = matrix.TransformBox(grid.LocalAABB).Intersect(aabb);
|
||||
var localAABB = invMatrix.TransformBox(overlap);
|
||||
|
||||
if (physicsQuery.HasComponent(gridUid))
|
||||
if (_physicsQuery.HasComponent(gridUid))
|
||||
{
|
||||
var enumerator = grid.GetLocalMapChunks(localAABB);
|
||||
var enumerator = _mapSystem.GetLocalMapChunks(gridUid, grid, localAABB);
|
||||
|
||||
var transform = new Transform(worldPos, worldRot);
|
||||
|
||||
@@ -174,14 +171,13 @@ internal partial class MapManager
|
||||
|
||||
uid = EntityUid.Invalid;
|
||||
grid = null;
|
||||
var xformSystem = EntityManager.System<SharedTransformSystem>();
|
||||
var state = (uid, grid, worldPos, xformQuery, xformSystem);
|
||||
var state = (uid, grid, worldPos, _mapSystem, _transformSystem);
|
||||
|
||||
FindGridsIntersecting(mapId, aabb, ref state, static (EntityUid iUid, MapGridComponent iGrid, ref (
|
||||
EntityUid uid,
|
||||
MapGridComponent? grid,
|
||||
Vector2 worldPos,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedMapSystem mapSystem,
|
||||
SharedTransformSystem xformSystem) tuple) =>
|
||||
{
|
||||
// Turn the worldPos into a localPos and work out the relevant chunk we need to check
|
||||
@@ -189,7 +185,7 @@ internal partial class MapManager
|
||||
// (though now we need some extra calcs up front).
|
||||
|
||||
// Doesn't use WorldBounds because it's just an AABB.
|
||||
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid, tuple.xformQuery);
|
||||
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid);
|
||||
var localPos = matrix.Transform(tuple.worldPos);
|
||||
|
||||
// NOTE:
|
||||
@@ -197,9 +193,10 @@ internal partial class MapManager
|
||||
// you account for the fact that fixtures are shrunk slightly!
|
||||
var chunkIndices = SharedMapSystem.GetChunkIndices(localPos, iGrid.ChunkSize);
|
||||
|
||||
if (!iGrid.HasChunk(chunkIndices)) return true;
|
||||
if (!tuple.mapSystem.HasChunk(iUid, iGrid, chunkIndices))
|
||||
return true;
|
||||
|
||||
var chunk = iGrid.GetOrAddChunk(chunkIndices);
|
||||
var chunk = tuple.mapSystem.GetOrAddChunk(iUid, iGrid, chunkIndices);
|
||||
var chunkRelative = SharedMapSystem.GetChunkRelative(localPos, iGrid.ChunkSize);
|
||||
var chunkTile = chunk.GetTile(chunkRelative);
|
||||
|
||||
@@ -229,8 +226,7 @@ internal partial class MapManager
|
||||
/// </summary>
|
||||
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
return TryFindGridAt(mapId, worldPos, xformQuery, out uid, out grid);
|
||||
return TryFindGridAt(mapId, worldPos, _xformQuery, out uid, out grid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,8 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -19,9 +21,17 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private SharedMapSystem _mapSystem = default!;
|
||||
private SharedTransformSystem _transformSystem = default!;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
_physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
_sawmill = Logger.GetSawmill("map");
|
||||
|
||||
#if DEBUG
|
||||
@@ -35,6 +45,9 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
/// <inheritdoc />
|
||||
public void Startup()
|
||||
{
|
||||
_transformSystem = EntityManager.System<SharedTransformSystem>();
|
||||
_mapSystem = EntityManager.System<SharedMapSystem>();
|
||||
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardInit);
|
||||
_dbgGuardRunning = true;
|
||||
@@ -51,9 +64,12 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
#endif
|
||||
_sawmill.Debug("Stopping...");
|
||||
|
||||
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
|
||||
// TODO: AllEntityQuery instead???
|
||||
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
{
|
||||
EntityManager.DeleteEntity(mapComp.Owner);
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +80,11 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
|
||||
// Don't just call Shutdown / Startup because we don't want to touch the subscriptions on gridtrees
|
||||
// Restart can be called any time during a game, whereas shutdown / startup are typically called upon connection.
|
||||
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
|
||||
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
{
|
||||
EntityManager.DeleteEntity(mapComp.Owner);
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -845,7 +845,13 @@ namespace Robust.Shared.Network
|
||||
return true;
|
||||
}
|
||||
|
||||
var channel = _channels[msg.SenderConnection];
|
||||
if (!_channels.TryGetValue(msg.SenderConnection, out var channel))
|
||||
{
|
||||
_logger.Warning($"{msg.SenderConnection.RemoteEndPoint}: Got unexpected data packet before handshake completion.");
|
||||
|
||||
msg.SenderConnection.Disconnect("Unexpected packet before handshake completion");
|
||||
return true;
|
||||
}
|
||||
|
||||
var encryption = IsServer ? channel.Encryption : _clientEncryption;
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ public abstract class Joint : IEquatable<Joint>
|
||||
|
||||
[DataField("breakpoint")]
|
||||
private float _breakpoint = float.MaxValue;
|
||||
private double _breakpointSquared = Double.MaxValue;
|
||||
private double _breakpointSquared = double.MaxValue;
|
||||
|
||||
// TODO: Later nerd
|
||||
// serializer.DataField(this, x => x.BodyA, "bodyA", EntityUid.Invalid);
|
||||
@@ -191,10 +191,10 @@ public abstract class Joint : IEquatable<Joint>
|
||||
IoCManager.Resolve(ref entMan);
|
||||
|
||||
if (entMan.TryGetComponent(BodyAUid, out PhysicsComponent? physics))
|
||||
entMan.Dirty(physics);
|
||||
entMan.Dirty(BodyAUid, physics);
|
||||
|
||||
if (entMan.TryGetComponent(BodyBUid, out physics))
|
||||
entMan.Dirty(physics);
|
||||
entMan.Dirty(BodyBUid, physics);
|
||||
}
|
||||
|
||||
protected Joint() {}
|
||||
|
||||
19
Robust.Shared/Physics/Events/CollisionLayerChangeEvent.cs
Normal file
19
Robust.Shared/Physics/Events/CollisionLayerChangeEvent.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Shared.Physics.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// These events are broadcast (not directed) whenever an entity's ability to collide changes.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct CollisionLayerChangeEvent
|
||||
{
|
||||
public readonly PhysicsComponent Body;
|
||||
|
||||
public CollisionLayerChangeEvent(PhysicsComponent body)
|
||||
{
|
||||
Body = body;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -355,6 +356,9 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (resetMass)
|
||||
_physics.ResetMassData(uid, manager, body);
|
||||
|
||||
// Save the old layer to see if an event should be raised later.
|
||||
var oldLayer = body.CollisionLayer;
|
||||
|
||||
// Normally this method is called when fixtures need to be dirtied anyway so no point in returning early I think
|
||||
body.CollisionMask = mask;
|
||||
body.CollisionLayer = layer;
|
||||
@@ -363,6 +367,12 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (manager.FixtureCount == 0)
|
||||
_physics.SetCanCollide(uid, false, manager: manager, body: body);
|
||||
|
||||
if (oldLayer != layer)
|
||||
{
|
||||
var ev = new CollisionLayerChangeEvent(body);
|
||||
RaiseLocalEvent(ref ev);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(manager);
|
||||
}
|
||||
|
||||
@@ -20,15 +20,19 @@ namespace Robust.Shared.Physics.Systems
|
||||
{
|
||||
public abstract class SharedBroadphaseSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
|
||||
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private ISawmill _logger = default!;
|
||||
private EntityQuery<BroadphaseComponent> _broadphaseQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
/*
|
||||
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
|
||||
@@ -53,9 +57,12 @@ namespace Robust.Shared.Physics.Systems
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_logger = Logger.GetSawmill("physics");
|
||||
UpdatesOutsidePrediction = true;
|
||||
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
UpdatesAfter.Add(typeof(SharedTransformSystem));
|
||||
|
||||
_cfg.OnValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand, true);
|
||||
@@ -79,27 +86,24 @@ namespace Robust.Shared.Physics.Systems
|
||||
PhysicsMapComponent component,
|
||||
MapId mapId,
|
||||
HashSet<EntityUid> movedGrids,
|
||||
Dictionary<FixtureProxy, Box2> gridMoveBuffer,
|
||||
EntityQuery<BroadphaseComponent> broadQuery,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
Dictionary<FixtureProxy, Box2> gridMoveBuffer)
|
||||
{
|
||||
// None moved this tick
|
||||
if (movedGrids.Count == 0) return;
|
||||
|
||||
var mapBroadphase = broadQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
|
||||
var mapBroadphase = _broadphaseQuery.GetComponent(_mapManager.GetMapEntityId(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 = component.MoveBuffer;
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
|
||||
foreach (var gridUid in movedGrids)
|
||||
{
|
||||
var grid = gridQuery.GetComponent(gridUid);
|
||||
var xform = xformQuery.GetComponent(gridUid);
|
||||
var grid = _gridQuery.GetComponent(gridUid);
|
||||
var xform = _xformQuery.GetComponent(gridUid);
|
||||
|
||||
DebugTools.Assert(xform.MapID == mapId);
|
||||
var worldAABB = _transform.GetWorldMatrix(xform, xformQuery).TransformBox(grid.LocalAABB);
|
||||
var worldAABB = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB);
|
||||
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
|
||||
var state = (moveBuffer, gridMoveBuffer);
|
||||
|
||||
@@ -111,7 +115,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
{
|
||||
moveBuffer[proxy] = worldAABB;
|
||||
// If something is in our AABB then try grid traversal for it
|
||||
_traversal.CheckTraverse(proxy.Entity, xformQuery.GetComponent(proxy.Entity));
|
||||
_traversal.CheckTraverse(proxy.Entity, _xformQuery.GetComponent(proxy.Entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,12 +161,8 @@ namespace Robust.Shared.Physics.Systems
|
||||
var movedGrids = Comp<MovedGridsComponent>(mapUid).MovedGrids;
|
||||
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
|
||||
|
||||
var broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Find any entities being driven over that might need to be considered
|
||||
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer, broadphaseQuery, xformQuery);
|
||||
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
|
||||
@@ -174,7 +174,7 @@ 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(mapId, movedGrids, physicsQuery, xformQuery);
|
||||
HandleGridCollisions(mapId, movedGrids);
|
||||
|
||||
// EZ
|
||||
if (moveBuffer.Count == 0)
|
||||
@@ -212,7 +212,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
var proxyBody = proxy.Body;
|
||||
DebugTools.Assert(!proxyBody.Deleted);
|
||||
|
||||
var state = (this, proxy, worldAABB, buffer, xformQuery, broadphaseQuery);
|
||||
var state = (this, proxy, worldAABB, buffer);
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), ref state,
|
||||
@@ -220,18 +220,16 @@ namespace Robust.Shared.Physics.Systems
|
||||
SharedBroadphaseSystem system,
|
||||
FixtureProxy proxy,
|
||||
Box2 worldAABB,
|
||||
List<FixtureProxy> pairBuffer,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<BroadphaseComponent> broadphaseQuery) tuple) =>
|
||||
List<FixtureProxy> pairBuffer) tuple) =>
|
||||
{
|
||||
ref var buffer = ref tuple.pairBuffer;
|
||||
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer, tuple.xformQuery, tuple.broadphaseQuery);
|
||||
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Struct ref moment, I have no idea what's fastest.
|
||||
buffer = state.buffer;
|
||||
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer, xformQuery, broadphaseQuery);
|
||||
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -272,27 +270,23 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
private void HandleGridCollisions(
|
||||
MapId mapId,
|
||||
HashSet<EntityUid> movedGrids,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
HashSet<EntityUid> movedGrids)
|
||||
{
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
|
||||
foreach (var gridUid in movedGrids)
|
||||
{
|
||||
var grid = gridQuery.GetComponent(gridUid);
|
||||
var xform = xformQuery.GetComponent(gridUid);
|
||||
var grid = _gridQuery.GetComponent(gridUid);
|
||||
var xform = _xformQuery.GetComponent(gridUid);
|
||||
DebugTools.Assert(xform.MapID == mapId);
|
||||
|
||||
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform, xformQuery);
|
||||
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform);
|
||||
|
||||
var aabb = new Box2Rotated(grid.LocalAABB, worldRot).CalcBoundingBox().Translated(worldPos);
|
||||
|
||||
// TODO: Need to handle grids colliding with non-grid entities with the same layer
|
||||
// (nothing in SS14 does this yet).
|
||||
|
||||
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: xformQuery);
|
||||
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _physicsSystem, _transform, physicsQuery, xformQuery);
|
||||
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: _xformQuery);
|
||||
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
|
||||
static (EntityUid uid, MapGridComponent component,
|
||||
@@ -301,6 +295,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
Transform transform,
|
||||
Matrix3 worldMatrix,
|
||||
Matrix3 invWorldMatrix,
|
||||
SharedMapSystem _map,
|
||||
SharedPhysicsSystem _physicsSystem,
|
||||
SharedTransformSystem xformSystem,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
@@ -320,7 +315,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
var aabb1 = tuple.grid.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
|
||||
|
||||
// TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem.
|
||||
var ourChunks = tuple.grid.GetLocalMapChunks(aabb1);
|
||||
var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1);
|
||||
var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid);
|
||||
var physicsB = tuple.physicsQuery.GetComponent(uid);
|
||||
|
||||
@@ -331,7 +326,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
tuple.worldMatrix.TransformBox(
|
||||
ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize));
|
||||
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
|
||||
var collidingChunks = component.GetLocalMapChunks(ourChunkOtherRef);
|
||||
var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef);
|
||||
|
||||
while (collidingChunks.MoveNext(out var collidingChunk))
|
||||
{
|
||||
@@ -372,14 +367,12 @@ namespace Robust.Shared.Physics.Systems
|
||||
FixtureProxy proxy,
|
||||
Box2 worldAABB,
|
||||
EntityUid broadphase,
|
||||
List<FixtureProxy> pairBuffer,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<BroadphaseComponent> broadphaseQuery)
|
||||
List<FixtureProxy> pairBuffer)
|
||||
{
|
||||
DebugTools.Assert(proxy.Body.CanCollide);
|
||||
|
||||
// Broadphase can't intersect with entities on itself so skip.
|
||||
if (proxy.Entity == broadphase || !xformQuery.TryGetComponent(proxy.Entity, out var xform))
|
||||
if (proxy.Entity == broadphase || !_xformQuery.TryGetComponent(proxy.Entity, out var xform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -389,7 +382,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
DebugTools.AssertNotNull(xform.Broadphase);
|
||||
if (!_lookup.TryGetCurrentBroadphase(xform, out var proxyBroad))
|
||||
{
|
||||
_logger.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
|
||||
Log.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
|
||||
DebugTools.Assert(false);
|
||||
return;
|
||||
}
|
||||
@@ -401,10 +394,10 @@ namespace Robust.Shared.Physics.Systems
|
||||
}
|
||||
else
|
||||
{
|
||||
aabb = _transform.GetInvWorldMatrix(broadphase, xformQuery).TransformBox(worldAABB);
|
||||
aabb = _transform.GetInvWorldMatrix(broadphase).TransformBox(worldAABB);
|
||||
}
|
||||
|
||||
var broadphaseComp = broadphaseQuery.GetComponent(broadphase);
|
||||
var broadphaseComp = _broadphaseQuery.GetComponent(broadphase);
|
||||
var state = (pairBuffer, proxy);
|
||||
|
||||
QueryBroadphase(broadphaseComp.DynamicTree, state, aabb);
|
||||
@@ -512,7 +505,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
}
|
||||
|
||||
// Won't worry about accurate bounds checks as it's probably slower in most use cases.
|
||||
var chunkEnumerator = mapGrid.GetMapChunks(aabb);
|
||||
var chunkEnumerator = _map.GetMapChunks(bUid, mapGrid, aabb);
|
||||
|
||||
if (chunkEnumerator.MoveNext(out _))
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ public abstract partial class SharedJointSystem
|
||||
if (relayTarget.Relayed.Remove(uid))
|
||||
{
|
||||
// TODO: Comp cleanup.
|
||||
Dirty(relayTarget);
|
||||
Dirty(component.Relay.Value, relayTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +91,10 @@ public abstract partial class SharedJointSystem
|
||||
if (relayTarget.Relayed.Add(uid))
|
||||
{
|
||||
_physics.WakeBody(relay.Value);
|
||||
Dirty(relayTarget);
|
||||
Dirty(relay.Value, relayTarget);
|
||||
}
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user