mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
380ccfacd8 | ||
|
|
cc0cc6afb1 | ||
|
|
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 |
@@ -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 -->
|
||||
|
||||
|
||||
114
RELEASE-NOTES.md
114
RELEASE-NOTES.md
@@ -54,7 +54,119 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 136.0.2
|
||||
## 141.2.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix component trait dictionaries not clearing on reconnect leading to bad GetComponent in areas (e.g. entire game looks black due to no entities).
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -23,7 +23,7 @@ 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)
|
||||
@@ -59,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
@@ -764,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -74,7 +77,10 @@ namespace Robust.Shared.GameObjects
|
||||
_netComponents.Clear();
|
||||
_entCompIndex.Clear();
|
||||
_deleteSet.Clear();
|
||||
FillComponentDict();
|
||||
foreach (var dict in _entTraitDict.Values)
|
||||
{
|
||||
dict.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddComponentRefType(CompIdx type)
|
||||
@@ -700,15 +706,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 +1040,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 +1049,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 +1060,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 +1073,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 +1094,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 +1303,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 +1368,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 +1378,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 +1391,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 +1405,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 +1434,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1? CompOrNull(EntityUid uid)
|
||||
{
|
||||
if (TryGetComponent(uid, out var comp))
|
||||
@@ -1437,6 +1442,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 +1550,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 +1578,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 +1610,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 +1641,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 +1681,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 +1715,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 +1766,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 +1803,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
|
||||
@@ -745,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)
|
||||
@@ -757,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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -119,22 +108,18 @@ internal partial class MapManager
|
||||
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
private static bool IsIntersecting(
|
||||
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);
|
||||
|
||||
@@ -186,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
|
||||
@@ -201,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:
|
||||
@@ -209,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);
|
||||
|
||||
@@ -241,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>
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
|
||||
@@ -20,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
|
||||
@@ -36,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;
|
||||
@@ -52,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,6 @@ namespace Robust.Shared.Replays;
|
||||
|
||||
public interface IReplayRecordingManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the replay manager.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a replay recording can currently be started.
|
||||
/// </summary>
|
||||
@@ -51,6 +46,14 @@ public interface IReplayRecordingManager
|
||||
/// </summary>
|
||||
bool IsRecording { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>state</c> object passed into <see cref="TryStartRecording"/> for the current recording.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns <see langword="null"/> if there is no active replay recording.
|
||||
/// </remarks>
|
||||
public object? ActiveRecordingState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Processes pending write tasks and saves the replay data for the current tick. This should be called even if a
|
||||
/// replay is not currently being recorded.
|
||||
@@ -62,20 +65,20 @@ public interface IReplayRecordingManager
|
||||
/// to the recording's metadata file, as well as to provide serializable messages that get replayed when the replay
|
||||
/// is initially loaded. E.g., this should contain networked events that would get sent to a newly connected client.
|
||||
/// </summary>
|
||||
event Action<MappingDataNode, List<object>>? RecordingStarted;
|
||||
event Action<MappingDataNode, List<object>> RecordingStarted;
|
||||
|
||||
/// <summary>
|
||||
/// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra yaml data to the
|
||||
/// recording's metadata file.
|
||||
/// </summary>
|
||||
event Action<MappingDataNode>? RecordingStopped;
|
||||
event Action<MappingDataNode> RecordingStopped;
|
||||
|
||||
/// <summary>
|
||||
/// This gets invoked after a replay recording has finished and provides information about where the replay data
|
||||
/// was saved. Note that this only means that all write tasks have started, however some of the file tasks may not
|
||||
/// have finished yet. See <see cref="WaitWriteTasks"/>.
|
||||
/// </summary>
|
||||
event Action<IWritableDirProvider, ResPath>? RecordingFinished;
|
||||
event Action<ReplayRecordingFinished> RecordingFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to starts a replay recording.
|
||||
@@ -93,12 +96,16 @@ public interface IReplayRecordingManager
|
||||
/// <param name="duration">
|
||||
/// Optional time limit for the recording.
|
||||
/// </param>
|
||||
/// <param name="state">
|
||||
/// An arbitrary object that is available in <see cref="ActiveRecordingState"/> and <see cref="RecordingFinished"/>.
|
||||
/// </param>
|
||||
/// <returns>Returns true if the recording was successfully started.</returns>
|
||||
bool TryStartRecording(
|
||||
IWritableDirProvider directory,
|
||||
string? name = null,
|
||||
bool overwrite = false,
|
||||
TimeSpan? duration = null);
|
||||
TimeSpan? duration = null,
|
||||
object? state = null);
|
||||
|
||||
/// <summary>
|
||||
/// Stops an ongoing replay recording.
|
||||
@@ -106,10 +113,9 @@ public interface IReplayRecordingManager
|
||||
void StopRecording();
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the currently ongoing replay recording, including the currently elapsed time and the
|
||||
/// compressed replay size.
|
||||
/// Returns information about the currently ongoing replay recording.
|
||||
/// </summary>
|
||||
(float Minutes, int Ticks, float Size, float UncompressedSize) GetReplayStats();
|
||||
ReplayRecordingStats GetReplayStats();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that will wait for all the current writing tasks to finish.
|
||||
@@ -119,3 +125,36 @@ public interface IReplayRecordingManager
|
||||
/// </exception>
|
||||
Task WaitWriteTasks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data for <see cref="IReplayRecordingManager.RecordingFinished"/>.
|
||||
/// </summary>
|
||||
/// <param name="Directory">The writable dir provider in which the replay is being recorded.</param>
|
||||
/// <param name="Path">The path to the replay in <paramref name="Directory"/>.</param>
|
||||
/// <param name="State">The state object passed to <see cref="IReplayRecordingManager.TryStartRecording"/>.</param>
|
||||
public record ReplayRecordingFinished(IWritableDirProvider Directory, ResPath Path, object? State);
|
||||
|
||||
/// <summary>
|
||||
/// Statistics for an active replay recording.
|
||||
/// </summary>
|
||||
/// <param name="Time">The simulation time the replay has been recording for.</param>
|
||||
/// <param name="Ticks">The amount of simulation ticks the replay has recorded.</param>
|
||||
/// <param name="Size">The total compressed size of the replay data blobs.</param>
|
||||
/// <param name="UncompressedSize">The total uncompressed size of the replay data blobs.</param>
|
||||
public record struct ReplayRecordingStats(TimeSpan Time, uint Ticks, long Size, long UncompressedSize);
|
||||
|
||||
/// <summary>
|
||||
/// Engine-internal functions for <see cref="IReplayRecordingManager"/>.
|
||||
/// </summary>
|
||||
internal interface IReplayRecordingManagerInternal : IReplayRecordingManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the replay manager.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Shut down any active replay recording, at engine shutdown.
|
||||
/// </summary>
|
||||
void Shutdown();
|
||||
}
|
||||
|
||||
@@ -99,11 +99,15 @@ internal sealed class ReplayStatsCommand : LocalizedCommands
|
||||
{
|
||||
if (_replay.IsRecording)
|
||||
{
|
||||
var (time, tick, size, _) = _replay.GetReplayStats();
|
||||
var stats = _replay.GetReplayStats();
|
||||
var sizeMb = stats.Size / (1024f * 1024f);
|
||||
var minutes = stats.Time.TotalMinutes;
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-replay-recording-stats-result",
|
||||
("time", time.ToString("F1")),
|
||||
("ticks", tick), ("size", size.ToString("F1")),
|
||||
("rate", (size/time).ToString("F2"))));
|
||||
("time", minutes.ToString("F1")),
|
||||
("ticks", stats.Ticks),
|
||||
("size", sizeMb.ToString("F1")),
|
||||
("rate", (sizeMb / minutes).ToString("F2"))));
|
||||
}
|
||||
else
|
||||
shell.WriteLine(Loc.GetString("cmd-replay-recording-stop-not-recording"));
|
||||
|
||||
@@ -21,13 +21,14 @@ using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Network;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static Robust.Shared.Replays.ReplayConstants;
|
||||
|
||||
namespace Robust.Shared.Replays;
|
||||
|
||||
internal abstract partial class SharedReplayRecordingManager : IReplayRecordingManager
|
||||
internal abstract partial class SharedReplayRecordingManager : IReplayRecordingManagerInternal
|
||||
{
|
||||
// date format for default replay names. Like the sortable template, but without colons.
|
||||
public const string DefaultReplayNameFormat = "yyyy-MM-dd_HH-mm-ss";
|
||||
@@ -38,10 +39,11 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
|
||||
public event Action<MappingDataNode, List<object>>? RecordingStarted;
|
||||
public event Action<MappingDataNode>? RecordingStopped;
|
||||
public event Action<IWritableDirProvider, ResPath>? RecordingFinished;
|
||||
public event Action<ReplayRecordingFinished>? RecordingFinished;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private List<object> _queuedMessages = new();
|
||||
@@ -53,9 +55,9 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
private bool _enabled;
|
||||
|
||||
public bool IsRecording => _recState != null;
|
||||
public object? ActiveRecordingState => _recState?.State;
|
||||
private RecordingState? _recState;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("replay");
|
||||
@@ -66,6 +68,18 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
NetConf.OnValueChanged(CVars.NetPVSCompressLevel, OnCompressionChanged);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
if (IsRecording)
|
||||
{
|
||||
StopRecording();
|
||||
|
||||
DebugTools.Assert(!IsRecording);
|
||||
}
|
||||
|
||||
_taskManager.BlockWaitOnTask(WaitWriteTasks());
|
||||
}
|
||||
|
||||
public virtual bool CanStartRecording()
|
||||
{
|
||||
return !IsRecording && _enabled;
|
||||
@@ -137,7 +151,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
IWritableDirProvider directory,
|
||||
string? name = null,
|
||||
bool overwrite = false,
|
||||
TimeSpan? duration = null)
|
||||
TimeSpan? duration = null,
|
||||
object? state = null)
|
||||
{
|
||||
if (!CanStartRecording())
|
||||
return false;
|
||||
@@ -152,7 +167,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
filePath = filePath.WithName(filePath.Filename + ".zip");
|
||||
|
||||
var basePath = new ResPath(NetConf.GetCVar(CVars.ReplayDirectory)).ToRootedPath();
|
||||
filePath = basePath / filePath.ToRelativePath();
|
||||
filePath = basePath / filePath;
|
||||
|
||||
// Make sure to create parent directory.
|
||||
directory.CreateDir(filePath.Directory);
|
||||
@@ -203,7 +218,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
commandQueue.Writer,
|
||||
writeTask,
|
||||
directory,
|
||||
filePath
|
||||
filePath,
|
||||
state
|
||||
);
|
||||
|
||||
try
|
||||
@@ -348,8 +364,10 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
var document = new YamlDocument(yamlMetadata.ToYaml());
|
||||
WriteYaml(recState, ReplayZipFolder / FileMetaFinal, document);
|
||||
UpdateWriteTasks();
|
||||
RecordingFinished?.Invoke(recState.DestDir, recState.DestPath);
|
||||
Reset();
|
||||
|
||||
var finishedData = new ReplayRecordingFinished(recState.DestDir, recState.DestPath, recState.State);
|
||||
RecordingFinished?.Invoke(finishedData);
|
||||
}
|
||||
|
||||
private void WriteContentBundleInfo(RecordingState recState)
|
||||
@@ -403,17 +421,17 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
return info;
|
||||
}
|
||||
|
||||
public (float Minutes, int Ticks, float Size, float UncompressedSize) GetReplayStats()
|
||||
public ReplayRecordingStats GetReplayStats()
|
||||
{
|
||||
if (_recState == null)
|
||||
throw new InvalidOperationException("Not recording replay!");
|
||||
|
||||
var time = (Timing.CurTime - _recState.StartTime).TotalMinutes;
|
||||
var time = Timing.CurTime - _recState.StartTime;
|
||||
var tick = Timing.CurTick.Value - _recState.StartTick.Value;
|
||||
var size = _recState.CompressedSize / (1024f * 1024f);
|
||||
var altSize = _recState.UncompressedSize / (1024f * 1024f);
|
||||
var size = _recState.CompressedSize;
|
||||
var altSize = _recState.UncompressedSize;
|
||||
|
||||
return ((float)time, (int)tick, size, altSize);
|
||||
return new ReplayRecordingStats(time, tick, size, altSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -428,6 +446,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
public readonly Task WriteTask;
|
||||
public readonly IWritableDirProvider DestDir;
|
||||
public readonly ResPath DestPath;
|
||||
public readonly object? State;
|
||||
|
||||
// Tick and time when the recording was started.
|
||||
public readonly GameTick StartTick;
|
||||
@@ -437,8 +456,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
public readonly TimeSpan? EndTime;
|
||||
|
||||
public int Index;
|
||||
public int CompressedSize;
|
||||
public int UncompressedSize;
|
||||
public long CompressedSize;
|
||||
public long UncompressedSize;
|
||||
|
||||
public RecordingState(
|
||||
ZipArchive zip,
|
||||
@@ -450,11 +469,13 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
ChannelWriter<Action> writeCommandChannel,
|
||||
Task writeTask,
|
||||
IWritableDirProvider destDir,
|
||||
ResPath destPath)
|
||||
ResPath destPath,
|
||||
object? state)
|
||||
{
|
||||
WriteTask = writeTask;
|
||||
DestDir = destDir;
|
||||
DestPath = destPath;
|
||||
State = state;
|
||||
Zip = zip;
|
||||
Buffer = buffer;
|
||||
CompressionContext = compressionContext;
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
|
||||
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
|
||||
@@ -2,13 +2,13 @@ using System;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.Maths;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Profiling;
|
||||
|
||||
namespace Robust.Shared.Timing
|
||||
{
|
||||
public interface IGameLoop
|
||||
internal interface IGameLoop
|
||||
{
|
||||
event EventHandler<FrameEventArgs> Input;
|
||||
event EventHandler<FrameEventArgs> Tick;
|
||||
@@ -46,8 +46,10 @@ namespace Robust.Shared.Timing
|
||||
/// <summary>
|
||||
/// Manages the main game loop for a GameContainer.
|
||||
/// </summary>
|
||||
public sealed class GameLoop : IGameLoop
|
||||
internal sealed class GameLoop : IGameLoop
|
||||
{
|
||||
private static readonly TimeSpan DelayTime = TimeSpan.FromMilliseconds(1);
|
||||
|
||||
public const string ProfTextStartFrame = "Start Frame";
|
||||
|
||||
private static readonly Histogram _frameTimeHistogram = Metrics.CreateHistogram(
|
||||
@@ -100,18 +102,27 @@ namespace Robust.Shared.Timing
|
||||
private readonly ProfManager _prof;
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private readonly PrecisionSleep _precisionSleep;
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
private int _tickExceptions;
|
||||
|
||||
private const int MaxSoftLockExceptions = 10;
|
||||
#endif
|
||||
|
||||
public GameLoop(IGameTiming timing, IRuntimeLog runtimeLog, ProfManager prof, ISawmill sawmill)
|
||||
public GameLoop(
|
||||
IGameTiming timing,
|
||||
IRuntimeLog runtimeLog,
|
||||
ProfManager prof,
|
||||
ISawmill sawmill,
|
||||
GameLoopOptions options)
|
||||
{
|
||||
_timing = timing;
|
||||
_runtimeLog = runtimeLog;
|
||||
_prof = prof;
|
||||
_sawmill = sawmill;
|
||||
|
||||
_precisionSleep = options.Precise ? PrecisionSleep.Create() : new PrecisionSleepUniversal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -208,6 +219,8 @@ namespace Robust.Shared.Timing
|
||||
using var tickGroup = _prof.Group("Tick");
|
||||
_prof.WriteValue("Tick", ProfData.Int64(_timing.CurTick.Value));
|
||||
|
||||
// System.Console.WriteLine($"Tick started at: {_timing.RealTime - _timing.LastTick}");
|
||||
|
||||
if (EnableMetrics)
|
||||
{
|
||||
using (_frameTimeHistogram.NewTimer())
|
||||
@@ -304,12 +317,36 @@ namespace Robust.Shared.Timing
|
||||
// Set sleep to 1 if you want to be nice and give the rest of the timeslice up to the os scheduler.
|
||||
// Set sleep to 0 if you want to use 100% cpu, but still cooperate with the scheduler.
|
||||
// do not call sleep if you want to be 'that thread' and hog 100% cpu.
|
||||
if (SleepMode != SleepMode.None)
|
||||
Thread.Sleep((int)SleepMode);
|
||||
switch (SleepMode)
|
||||
{
|
||||
case SleepMode.Yield:
|
||||
Thread.Sleep(0);
|
||||
break;
|
||||
|
||||
case SleepMode.Delay:
|
||||
// We try to sleep exactly until the next tick.
|
||||
// But no longer than 1ms so input can keep processing.
|
||||
var timeToSleep = (_timing.LastTick + _timing.TickPeriod) - _timing.RealTime;
|
||||
if (timeToSleep > DelayTime)
|
||||
timeToSleep = DelayTime;
|
||||
|
||||
if (timeToSleep.Ticks > 0)
|
||||
_precisionSleep.Sleep(timeToSleep);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record GameLoopOptions(bool Precise)
|
||||
{
|
||||
public static GameLoopOptions FromCVars(IConfigurationManager cfg)
|
||||
{
|
||||
return new GameLoopOptions(cfg.GetCVar(CVars.SysPreciseSleep));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Methods the GameLoop can use to limit the Update rate.
|
||||
/// </summary>
|
||||
|
||||
@@ -78,7 +78,8 @@ namespace Robust.Shared.Timing
|
||||
GameTick CurTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timespan for the last tick.
|
||||
/// Time, relative to <see cref="RealTime"/>, the last tick started at.
|
||||
/// If we're currently in simulation, that's THIS tick.
|
||||
/// </summary>
|
||||
TimeSpan LastTick { get; set; }
|
||||
|
||||
|
||||
154
Robust.Shared/Timing/PrecisionSleep.cs
Normal file
154
Robust.Shared/Timing/PrecisionSleep.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Shared.Timing;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for more precise sleeping functionality than <see cref="Thread.Sleep(int)"/>.
|
||||
/// </summary>
|
||||
internal abstract class PrecisionSleep : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Sleep for the specified amount of time.
|
||||
/// </summary>
|
||||
public abstract void Sleep(TimeSpan time);
|
||||
|
||||
/// <summary>
|
||||
/// Create the most optimal optimization for the current platform.
|
||||
/// </summary>
|
||||
public static PrecisionSleep Create()
|
||||
{
|
||||
// Check Windows 10 1803
|
||||
if (OperatingSystem.IsWindows() && Environment.OSVersion.Version.Build >= 17134)
|
||||
return new PrecisionSleepWindowsHighResolution();
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
return new PrecisionSleepLinuxNanosleep();
|
||||
|
||||
return new PrecisionSleepUniversal();
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Universal cross-platform implementation of <see cref="PrecisionSleep"/>. Not very precise!
|
||||
/// </summary>
|
||||
internal sealed class PrecisionSleepUniversal : PrecisionSleep
|
||||
{
|
||||
public override void Sleep(TimeSpan time)
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High-precision implementation of <see cref="PrecisionSleep"/> that is available since Windows 10 1803.
|
||||
/// </summary>
|
||||
internal sealed unsafe class PrecisionSleepWindowsHighResolution : PrecisionSleep
|
||||
{
|
||||
private HANDLE _timerHandle;
|
||||
|
||||
public PrecisionSleepWindowsHighResolution()
|
||||
{
|
||||
// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is only supported since Windows 10 1803
|
||||
_timerHandle = Windows.CreateWaitableTimerExW(
|
||||
null,
|
||||
null,
|
||||
CREATE.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
|
||||
Windows.TIMER_ALL_ACCESS);
|
||||
|
||||
if (_timerHandle == HANDLE.NULL)
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
public override void Sleep(TimeSpan time)
|
||||
{
|
||||
LARGE_INTEGER due;
|
||||
// negative = relative time.
|
||||
due.QuadPart = -time.Ticks;
|
||||
|
||||
var success = Windows.SetWaitableTimer(
|
||||
_timerHandle,
|
||||
&due,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
BOOL.FALSE
|
||||
);
|
||||
|
||||
if (!success)
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
|
||||
var waitResult = Windows.WaitForSingleObject(_timerHandle, Windows.INFINITE);
|
||||
if (waitResult == WAIT.WAIT_FAILED)
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
|
||||
GC.KeepAlive(this);
|
||||
}
|
||||
|
||||
private void DisposeCore()
|
||||
{
|
||||
Windows.CloseHandle(_timerHandle);
|
||||
|
||||
_timerHandle = default;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
DisposeCore();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~PrecisionSleepWindowsHighResolution()
|
||||
{
|
||||
DisposeCore();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High-precision implementation of <see cref="PrecisionSleep"/> that is available on Linux.
|
||||
/// </summary>
|
||||
internal sealed unsafe class PrecisionSleepLinuxNanosleep : PrecisionSleep
|
||||
{
|
||||
public override void Sleep(TimeSpan time)
|
||||
{
|
||||
timespec timeSpec;
|
||||
timeSpec.tv_sec = Math.DivRem(time.Ticks, TimeSpan.TicksPerSecond, out var ticksRem);
|
||||
timeSpec.tv_nsec = ticksRem * TimeSpan.NanosecondsPerTick;
|
||||
|
||||
while (true)
|
||||
{
|
||||
timespec rem;
|
||||
var result = nanosleep(&timeSpec, &rem);
|
||||
if (result == 0)
|
||||
return;
|
||||
|
||||
var error = Marshal.GetLastSystemError();
|
||||
if (error != 4) // EINTR
|
||||
throw new Exception($"nanosleep failed: {error}");
|
||||
|
||||
timeSpec = rem;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("libc.so.6", SetLastError=true)]
|
||||
private static extern int nanosleep(timespec* req, timespec* rem);
|
||||
|
||||
private struct timespec
|
||||
{
|
||||
public long tv_sec;
|
||||
public long tv_nsec;
|
||||
}
|
||||
|
||||
private struct timeval
|
||||
{
|
||||
public long tv_sec;
|
||||
public long tv_usec;
|
||||
}
|
||||
}
|
||||
70
Robust.Shared/Utility/FileHelper.cs
Normal file
70
Robust.Shared/Utility/FileHelper.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Shared.Utility;
|
||||
|
||||
internal static class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to open a file for reading. If the file does not exist, the operation fails without exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This API is not atomic and can thus be vulnerable to TOCTOU attacks. Don't use it if that's relevant.
|
||||
/// </remarks>
|
||||
/// <param name="path">The path to try to open.</param>
|
||||
/// <param name="stream">The resulting file stream.</param>
|
||||
/// <returns>True if the file existed and was opened.</returns>
|
||||
public static bool TryOpenFileRead(string path, [NotNullWhen(true)] out FileStream? stream)
|
||||
{
|
||||
// On Windows, the separate File.Exists() call alone adds a ton of weight.
|
||||
// The alternative however (opening the file and catching the error) is extremely slow because of .NET exceptions.
|
||||
// So we manually call the windows API and make the file handle from that. Problem solved!
|
||||
if (OperatingSystem.IsWindows())
|
||||
return TryGetFileWindows(path, out stream);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
stream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
stream = File.OpenRead(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe bool TryGetFileWindows(string path, [NotNullWhen(true)] out FileStream? stream)
|
||||
{
|
||||
HANDLE file;
|
||||
fixed (char* pPath = path)
|
||||
{
|
||||
file = Windows.CreateFileW(
|
||||
(ushort*)pPath,
|
||||
Windows.GENERIC_READ,
|
||||
FILE.FILE_SHARE_READ,
|
||||
null,
|
||||
OPEN.OPEN_EXISTING,
|
||||
FILE.FILE_ATTRIBUTE_NORMAL,
|
||||
HANDLE.NULL);
|
||||
}
|
||||
|
||||
if (file == HANDLE.INVALID_VALUE)
|
||||
{
|
||||
var lastError = Marshal.GetLastWin32Error();
|
||||
if (lastError is ERROR.ERROR_FILE_NOT_FOUND or ERROR.ERROR_PATH_NOT_FOUND)
|
||||
{
|
||||
stream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(lastError));
|
||||
}
|
||||
|
||||
var sf = new SafeFileHandle(file, ownsHandle: true);
|
||||
stream = new FileStream(sf, FileAccess.Read);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
// ReSharper disable CommentTypo
|
||||
@@ -49,34 +50,15 @@ namespace Robust.Shared.Utility
|
||||
|
||||
PROCESS_MEMORY_COUNTERS_EX counters;
|
||||
|
||||
if (GetProcessMemoryInfo(process.Handle, &counters, sizeof(PROCESS_MEMORY_COUNTERS_EX)) == 0)
|
||||
if (Windows.GetProcessMemoryInfo(
|
||||
(HANDLE)process.Handle,
|
||||
(PROCESS_MEMORY_COUNTERS*)(&counters),
|
||||
(uint)sizeof(PROCESS_MEMORY_COUNTERS_EX)) == 0)
|
||||
return 0;
|
||||
|
||||
var count = counters.PrivateUsage;
|
||||
|
||||
return count;
|
||||
return (long)count;
|
||||
}
|
||||
|
||||
private struct PROCESS_MEMORY_COUNTERS_EX
|
||||
{
|
||||
public int cb;
|
||||
public int PageFaultCount;
|
||||
public nint PeakWorkingSetSize;
|
||||
public nint WorkingSetSize;
|
||||
public nint QuotaPeakPagedPoolUsage;
|
||||
public nint QuotaPagedPoolUsage;
|
||||
public nint QuotaPeakNonPagedPoolUsage;
|
||||
public nint QuotaNonPagedPoolUsage;
|
||||
public nint PagefileUsage;
|
||||
public nint PeakPagefileUsage;
|
||||
public nint PrivateUsage;
|
||||
}
|
||||
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
[DllImport("psapi.dll", SetLastError = true)]
|
||||
private static extern int GetProcessMemoryInfo(
|
||||
IntPtr Process,
|
||||
PROCESS_MEMORY_COUNTERS_EX* ppsmemCounters,
|
||||
int cb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
|
||||
var (sim, gridIdA, gridIdB) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var xformSystem = entMan.System<SharedTransformSystem>();
|
||||
|
||||
var gridA = mapMan.GetGrid(gridIdA);
|
||||
var gridB = mapMan.GetGrid(gridIdB);
|
||||
@@ -51,18 +52,22 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
|
||||
var child = entMan.SpawnEntity(null, initialPos);
|
||||
var parentTrans = entMan.GetComponent<TransformComponent>(parent);
|
||||
var childTrans = entMan.GetComponent<TransformComponent>(child);
|
||||
ComponentHandleState handleState;
|
||||
|
||||
var compState = new TransformComponentState(new Vector2(5, 5), new Angle(0), gridB.Owner, false, false);
|
||||
parentTrans.HandleComponentState(compState, null);
|
||||
handleState = new ComponentHandleState(compState, null);
|
||||
xformSystem.OnHandleState(parent, parentTrans, ref handleState);
|
||||
|
||||
compState = new TransformComponentState(new Vector2(6, 6), new Angle(0), gridB.Owner, false, false);
|
||||
childTrans.HandleComponentState(compState, null);
|
||||
handleState = new ComponentHandleState(compState, null);
|
||||
xformSystem.OnHandleState(child, childTrans, ref handleState);
|
||||
// World pos should be 6, 6 now.
|
||||
|
||||
// Act
|
||||
var oldWpos = childTrans.WorldPosition;
|
||||
compState = new TransformComponentState(new Vector2(1, 1), new Angle(0), parent, false, false);
|
||||
childTrans.HandleComponentState(compState, null);
|
||||
handleState = new ComponentHandleState(compState, null);
|
||||
xformSystem.OnHandleState(child, childTrans, ref handleState);
|
||||
var newWpos = childTrans.WorldPosition;
|
||||
|
||||
// Assert
|
||||
|
||||
@@ -27,6 +27,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
using EyeComponent = Robust.Server.GameObjects.EyeComponent;
|
||||
using MapSystem = Robust.Server.GameObjects.MapSystem;
|
||||
|
||||
namespace Robust.UnitTesting
|
||||
{
|
||||
@@ -86,6 +87,7 @@ namespace Robust.UnitTesting
|
||||
|
||||
var systems = deps.Resolve<IEntitySystemManager>();
|
||||
// Required systems
|
||||
systems.LoadExtraSystemType<MapSystem>();
|
||||
systems.LoadExtraSystemType<EntityLookupSystem>();
|
||||
|
||||
// uhhh so maybe these are the wrong system for the client, but I CBF adding sprite system and all the rest,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user