Compare commits

...

58 Commits

Author SHA1 Message Date
metalgearsloth
21e1b75f5d Remove ICollideSpecial entirely (#1782)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-05-31 09:41:11 +02:00
metalgearsloth
1f6ddd96a6 Optimise the shit out of showpos (#1788) 2021-05-31 09:11:29 +02:00
metalgearsloth
b4d2cd26aa Named joints (#1787)
1 step closer to ECS physics.
2021-05-31 09:01:02 +02:00
metalgearsloth
ee6aee57bd Immediate broadphase updates (#1687) 2021-05-31 08:58:23 +02:00
DrSmugleaf
ac1705e41f Make NetMessage name and group virtual to remove required boilerplate (#1770) 2021-05-31 08:53:46 +02:00
metalgearsloth
196a6047f1 Use Box2D FindMaxSeparation (#1786) 2021-05-31 08:52:01 +02:00
ShadowCommander
b98b36a461 Add loglevel command line arg (#1769) 2021-05-31 08:41:34 +02:00
mirrorcult
9980a98a94 Add new fluent functions for words that change based on gender (#1775) 2021-05-31 08:41:06 +02:00
Pieter-Jan Briers
5a1a1d0420 Add component names to string serializer. 2021-05-30 22:55:33 +02:00
ShadowCommander
1c99678fe9 Change other physics awake event calls to RaiseLocalEvent 2021-05-30 12:16:17 -07:00
ShadowCommander
4049f10faa Make awake dispatch local events (#1785) 2021-05-30 21:05:16 +02:00
metalgearsloth
331c0bd43f Fix PreventCollideEvent (#1781)
With directed bus it's better to raise it both ways for a collision.
2021-05-30 18:38:28 +02:00
metalgearsloth
604cd2f93d Fix physics nullrefs (#1780)
* Fix physics nullrefs

* woops
2021-05-30 05:15:22 -07:00
Vera Aguilera Puerto
a87db2e57c EntitySystemManager uses IRuntimeLog for exception logging. 2021-05-30 12:27:36 +02:00
ShadowCommander
4c7f0a8d6b Add container helper for entity storage interaction (#1777) 2021-05-29 11:40:36 +02:00
20kdc
9eaf52886a Make RenderingTreeSystem handle parent recursion properly, fixing space-station-14#4040 and possibly other bugs (#1776) 2021-05-29 11:39:35 +02:00
Vera Aguilera Puerto
fce6f6c714 Adds ActorSystem to handle Attaching/Detaching players to/from entities sanely. (#1774) 2021-05-29 11:37:34 +02:00
DrSmugleaf
c04d51d489 Add test for YAML hot reloading (#1773)
* Add test for YAML hot reloading

* Perhaps test the event firing as well
2021-05-27 15:50:44 +02:00
Vera Aguilera Puerto
d310871aa6 CVar for MIDI volume. 2021-05-26 19:27:02 +02:00
Vera Aguilera Puerto
5fa422865f AudioSystem warning now prints audio stream name, if any. 2021-05-26 18:56:30 +02:00
Vera Aguilera Puerto
c137823355 Proper cleanup when detaching player from a deleted entity. 2021-05-26 18:44:37 +02:00
Vera Aguilera Puerto
0a0026b9ae Add formatted message newline helper method. 2021-05-26 10:18:27 +02:00
Pieter-Jan Briers
97a2a5cfae Block loading of R2R'd .NET assemblies in sandboxing. 2021-05-25 16:57:54 +02:00
Pieter-Jan Briers
a266e21d9e Add single-type IoC register call. 2021-05-24 22:05:15 +02:00
Pieter-Jan Briers
ad8bbe6401 Clyde audio improvements:
1. Allow loading audio directly from a sample buffer
2. SetVolumeDirect that takes a 0 -> 1 scale similar to OpenAL's AL_GAIN.
3. Fix OpenAL threading bugs with logging by caching the sawmill.
2021-05-23 22:58:32 +02:00
Pieter-Jan Briers
71ce4749fe Show active input context on DebugInputPanel. 2021-05-23 22:58:32 +02:00
Pieter-Jan Briers
1e33c3c843 MIDI renderer debugging code. 2021-05-23 22:58:32 +02:00
Vera Aguilera Puerto
b3f3ca9725 MIDI renderer uses NumericsHelpers to convert stereo audio to mono. 2021-05-23 13:40:06 +02:00
Vera Aguilera Puerto
33c37393ba Fixes incorrect comment in MidiRenderer.
Pain.
2021-05-23 13:15:31 +02:00
metalgearsloth
ae36529744 Emit line for mapping node exceptions (#1764)
Makes it a billion times more useful when I dun screwed up
2021-05-22 11:36:28 +02:00
20kdc
ed2be48864 Fix Coordinates setter destroying local position during a parent change by migrating parent changes to it (#1763)
This fixes computer boards going missing. (Seriously.)
2021-05-22 11:36:18 +02:00
Vera Aguilera Puerto
7ad5ce302e Directed event improvements (#1761)
* IComponentManager holds a reference to IComponentFactory.

* Directed events for a same <TComp, TEvent> can be duplicated, component references of a component are added to the entity's event tables.

* Fix tests.

* Improvements, duplicated subscriptions are no more

* Cache component factory for faster access

* when the
2021-05-21 17:38:52 -07:00
Vera Aguilera Puerto
647f1a6808 Fixes bug where deleting things causes their sprite to appear and pile on 0,0.
- Cleans up an unneeded event.
2021-05-20 21:17:20 +02:00
metalgearsloth
6c44dd9665 RenderingTreeSystem cleanup (#1759)
* RenderingTreeSystem cleanup

10% less bad but 100% still boilerplate

* Slight changies

* Even better

* New shit just got made

* Apply revews
2021-05-20 18:57:29 +10:00
Acruid
c81413b0b4 Fixes bug where the net_entityreport red PVS range square was drawn at half the actual range. 2021-05-19 15:52:44 -07:00
Vera Aguilera Puerto
88b3a557da Fixes bug with PrototypeIdListSerializer where lists wouldn't be copied at all. 2021-05-19 12:12:13 +02:00
Vera Aguilera Puerto
572eb01290 Opens SCSI window centered
- Kinda fixes it getting NaN'd on resize... This isn't a proper fix, however.
2021-05-19 11:11:35 +02:00
Vera Aguilera Puerto
9dab74c9d5 Fixes SS14Window going off-screen. 2021-05-19 10:55:41 +02:00
Paul
e1cb1e1b9c fixes xamlui nuking itself when one (1) (singular) partial declaration is missing 2021-05-18 20:19:50 +02:00
Vera Aguilera Puerto
a23da702b1 MidiManager now has a Volume property which changes the general midi volume.
- Adds VV attributes to MidiManager and MidiRenderer.
2021-05-18 20:03:20 +02:00
Vera Aguilera Puerto
ae9c2423ff Fixes EntityLookup not being restarted correctly on reconnect. 2021-05-18 17:55:24 +02:00
metalgearsloth
a6dae8e30a GridId caching (#1678)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-05-17 11:43:22 +02:00
Pieter-Jan Briers
96c0a4ae1f Automatically unsubscribe event bus registrations in entity system shutdown. (#1758)
* Automatically unsubscribe event bus registrations in entity system shutdown.

* Fix incorrect unsubscription of local events, obsolete unsubscribe methods.

That's what we got tests for.

* = null instead of .Clear()
2021-05-17 11:01:22 +02:00
Vera Aguilera Puerto
c26ebcbc78 QueueDelete method for entities (#1757) 2021-05-17 09:20:55 +02:00
Vera Aguilera Puerto
8334050411 MIDI improvements (#1756) 2021-05-16 19:28:14 +02:00
Vera Aguilera Puerto
cc67edfc2c Unsubscribe from directed event in UI System. 2021-05-15 12:40:35 +02:00
Vera Aguilera Puerto
943ea9e6c8 Shutdown and dispose of BoundUserInterfaces correctly when the UI component gets shutdown. 2021-05-14 14:25:53 +02:00
Pieter-Jan Briers
3aa5cefe03 Stop using component messages in bound UI code. 2021-05-13 03:29:38 +02:00
Pieter-Jan Briers
c5b34bab69 More obsoletions component messages. 2021-05-13 02:24:35 +02:00
Pieter-Jan Briers
e4f24ec125 Remove IPlayerSession on client. 2021-05-13 02:16:55 +02:00
Pieter-Jan Briers
250971ade7 Remove anchored APIs from physics. 2021-05-13 02:13:18 +02:00
Pieter-Jan Briers
718adf9740 Deprecate component messages harder. 2021-05-13 01:24:20 +02:00
20kdc
5d63aa8c95 Deferred Input Context Switches (fixes spawn entities menu while moving) (#1755) 2021-05-12 18:40:26 +02:00
Vera Aguilera Puerto
17af3612a5 Remove IActorComponent, rename BasicActorComponent to ActorComponent. (#1752)
Rename playerSession to PlayerSession.
2021-05-12 13:40:16 +02:00
Pieter-Jan Briers
d2ecf6b9b1 Remove CollidesOnMask
Only usage was a unit test.
2021-05-12 01:58:12 +02:00
Pieter-Jan Briers
2a1eda0d38 Fix tests 2021-05-12 01:57:03 +02:00
Pieter-Jan Briers
f0180abeb0 Missed a spot while fixing warnings. 2021-05-12 00:26:47 +02:00
Pieter-Jan Briers
720f1b1d05 Fix a bunch of compiler warnings. 2021-05-12 00:16:12 +02:00
185 changed files with 2923 additions and 2044 deletions

View File

@@ -247,7 +247,6 @@ namespace {nameSpace}
DiagnosticSeverity.Error,
true),
Location.None));
return null;
}
}

View File

@@ -6,14 +6,18 @@ using System.Threading;
using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
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.Broadphase;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Logger = Robust.Shared.Log.Logger;
namespace Robust.Client.Audio.Midi
@@ -32,24 +36,6 @@ namespace Robust.Client.Audio.Midi
/// </returns>
IMidiRenderer? GetNewRenderer();
/*
/// <summary>
/// Checks whether the file at the given path is a valid midi file or not.
/// </summary>
/// <remarks>
/// We add this here so content doesn't need to reference NFluidsynth.
/// </remarks>
bool IsMidiFile(string filename);
/// <summary>
/// Checks whether the file at the given path is a valid midi file or not.
/// </summary>
/// <remarks>
/// We add this here so content doesn't need to reference NFluidsynth.
/// </remarks>
bool IsSoundfontFile(string filename);
*/
/// <summary>
/// Method called every frame.
/// Should be used to update positional audio.
@@ -57,6 +43,11 @@ namespace Robust.Client.Audio.Midi
/// <param name="frameTime"></param>
void FrameUpdate(float frameTime);
/// <summary>
/// Volume, in db.
/// </summary>
float Volume { get; set; }
/// <summary>
/// If true, MIDI support is available.
/// </summary>
@@ -72,9 +63,11 @@ namespace Robust.Client.Audio.Midi
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
{
get
@@ -85,12 +78,29 @@ namespace Robust.Client.Audio.Midi
}
}
private readonly List<MidiRenderer> _renderers = new();
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
private bool _alive = true;
private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private bool _volumeDirty = true;
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
get => _volume;
set
{
if (MathHelper.CloseTo(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volumeDirty = true;
}
}
private static readonly string[] LinuxSoundfonts =
{
@@ -117,12 +127,19 @@ namespace Robust.Client.Audio.Midi
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private void InitializeFluidsynth()
{
if (FluidsynthInitialized || _failedInitialize) return;
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = Logger.GetSawmill("midi");
_sawmill = Logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
@@ -175,18 +192,6 @@ namespace Robust.Client.Audio.Midi
_sawmill.Log(rLevel, message);
}
/*
public bool IsMidiFile(string filename)
{
return SoundFont.IsMidiFile(filename);
}
public bool IsSoundfontFile(string filename)
{
return SoundFont.IsSoundFont(filename);
}
*/
public IMidiRenderer? GetNewRenderer()
{
if (!FluidsynthInitialized)
@@ -250,7 +255,9 @@ namespace Robust.Client.Audio.Midi
}
lock (_renderers)
{
_renderers.Add(renderer);
}
return renderer;
}
@@ -268,74 +275,79 @@ namespace Robust.Client.Audio.Midi
}
// Update positions of streams every frame.
lock (_renderers)
foreach (var renderer in _renderers)
foreach (var renderer in _renderers)
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
{
if (renderer.Disposed)
continue;
if (!renderer.Mono)
{
renderer.Source.SetGlobal();
continue;
}
MapCoordinates? mapPos = null;
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
else if (renderer.TrackingEntity != null)
{
mapPos = renderer.TrackingEntity.Transform.MapPosition;
}
if (mapPos != null)
{
var pos = mapPos.Value;
if (pos.MapId != _eyeManager.CurrentMap)
{
renderer.Source.SetVolume(-10000000);
}
else
{
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
}
if (renderer.Source.SetPosition(pos.Position))
{
continue;
}
if (renderer.TrackingEntity != null)
{
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
}
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
{
// just duck out instead of move to NaN
renderer.Source.SetOcclusion(float.MaxValue);
continue;
}
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
renderer.Source.StopPlaying();
}
renderer.Source.SetGlobal();
continue;
}
MapCoordinates? mapPos = null;
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
else if (renderer.TrackingEntity != null)
{
mapPos = renderer.TrackingEntity.Transform.MapPosition;
}
if (mapPos != null)
{
var pos = mapPos.Value;
if (pos.MapId != _eyeManager.CurrentMap)
{
renderer.Source.SetVolume(-10000000);
}
else
{
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
}
if (renderer.Source.SetPosition(pos.Position))
{
continue;
}
if (renderer.TrackingEntity != null)
{
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
}
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
{
// just duck out instead of move to NaN
renderer.Source.SetOcclusion(float.MaxValue);
continue;
}
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
renderer.Source.StopPlaying();
}
}
_volumeDirty = false;
}
/// <summary>
@@ -346,6 +358,7 @@ namespace Robust.Client.Audio.Midi
while (_alive)
{
lock (_renderers)
{
for (var i = 0; i < _renderers.Count; i++)
{
var renderer = _renderers[i];
@@ -353,10 +366,11 @@ namespace Robust.Client.Audio.Midi
renderer.Render();
else
{
((IMidiRenderer)renderer).InternalDispose();
renderer.InternalDispose();
_renderers.Remove(renderer);
}
}
}
Thread.Sleep(1);
}
@@ -367,9 +381,13 @@ namespace Robust.Client.Audio.Midi
_alive = false;
_midiThread?.Join();
_settings?.Dispose();
foreach (var renderer in _renderers)
lock (_renderers)
{
renderer?.Dispose();
foreach (var renderer in _renderers)
{
renderer?.Dispose();
}
}
if (FluidsynthInitialized && !_failedInitialize)
@@ -424,6 +442,7 @@ namespace Robust.Client.Audio.Midi
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
@@ -447,6 +466,7 @@ namespace Robust.Client.Audio.Midi
{
return -1;
}
return 0;
}
@@ -468,10 +488,12 @@ namespace Robust.Client.Audio.Midi
public override int Close(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
_openStreams.Remove((int) sfHandle);
return 0;
}
}
}

View File

@@ -7,7 +7,9 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using MidiEvent = NFluidsynth.MidiEvent;
namespace Robust.Client.Audio.Midi
@@ -21,6 +23,17 @@ namespace Robust.Client.Audio.Midi
public interface IMidiRenderer : IDisposable
{
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
internal IClydeBufferedAudioSource Source { get; }
/// <summary>
/// Whether this renderer has been disposed or not.
/// </summary>
bool Disposed { get; }
/// <summary>
/// This controls whether the midi file being played will loop or not.
/// </summary>
@@ -110,6 +123,11 @@ namespace Robust.Client.Audio.Midi
/// </summary>
void StopAllNotes();
/// <summary>
/// Render and play MIDI to the audio source.
/// </summary>
internal void Render();
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
@@ -159,7 +177,7 @@ namespace Robust.Client.Audio.Midi
internal void InternalDispose();
}
public class MidiRenderer : IMidiRenderer
internal class MidiRenderer : IMidiRenderer
{
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
@@ -186,10 +204,16 @@ namespace Robust.Client.Audio.Midi
private const int SampleRate = 44100;
private const int Buffers = SampleRate / 2205;
private readonly object _playerStateLock = new();
private bool _debugEvents = false;
private SequencerClientId _synthRegister;
private SequencerClientId _debugRegister;
public IClydeBufferedAudioSource Source { get; set; }
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
[ViewVariables]
public bool Disposed { get; private set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public byte MidiProgram
{
get => _midiProgram;
@@ -203,6 +227,7 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public byte MidiBank
{
get => _midiBank;
@@ -216,6 +241,7 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public uint MidiSoundfont
{
get => _midiSoundfont;
@@ -229,10 +255,16 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool DisablePercussionChannel { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool DisableProgramChangeEvent { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTick
{
get => _player?.CurrentTick ?? 0;
@@ -243,12 +275,19 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public uint SequencerTick => _sequencer?.Tick ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public bool Mono { get; set; }
[ViewVariables]
public MidiRendererStatus Status { get; private set; } = MidiRendererStatus.None;
[ViewVariables(VVAccess.ReadWrite)]
public bool LoopMidi
{
get => _loopMidi;
@@ -260,10 +299,11 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public IEntity? TrackingEntity { get; set; } = null;
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
internal bool Free { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono = true)
{
@@ -276,6 +316,7 @@ namespace Robust.Client.Audio.Midi
_soundFontLoader = soundFontLoader;
_synth = new Synth(_settings);
_sequencer = new Sequencer(false);
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
_synth.AddSoundFontLoader(soundFontLoader);
@@ -285,6 +326,27 @@ namespace Robust.Client.Audio.Midi
Source.StartPlaying();
}
private void DumpSequencerEvent(uint time, SequencerEvent @event)
{
// ReSharper disable once UseStringInterpolation
_midiSawmill.Debug(string.Format(
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
time,
@event.Type.ToString().PadLeft(22),
@event.Channel,
@event.Key,
@event.Bank,
@event.Control,
@event.Duration,
@event.Pitch,
@event.Program,
@event.Value,
@event.Velocity));
@event.Dest = _synthRegister;
_sequencer.SendNow(@event);
}
public bool OpenInput()
{
if (Disposed)
@@ -294,7 +356,11 @@ namespace Robust.Client.Audio.Midi
Status = MidiRendererStatus.Input;
StopAllNotes();
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
lock (_playerStateLock)
{
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
}
return true;
}
@@ -332,8 +398,13 @@ namespace Robust.Client.Audio.Midi
{
if (Status != MidiRendererStatus.Input) return false;
Status = MidiRendererStatus.None;
_driver?.Dispose();
_driver = null;
lock (_playerStateLock)
{
_driver?.Dispose();
_driver = null;
}
StopAllNotes();
return true;
}
@@ -357,7 +428,8 @@ namespace Robust.Client.Audio.Midi
public void StopAllNotes()
{
_synth.AllNotesOff(-1);
lock(_playerStateLock)
_synth.AllNotesOff(-1);
}
public void LoadSoundfont(string filename, bool resetPresets = false)
@@ -372,13 +444,15 @@ namespace Robust.Client.Audio.Midi
public event Action<Shared.Audio.Midi.MidiEvent>? OnMidiEvent;
public event Action? OnMidiPlayerFinished;
internal void Render(int length = SampleRate / 250)
void IMidiRenderer.Render()
{
Render();
}
private void Render(int length = SampleRate / 250)
{
if (Disposed) return;
// SSE needs this.
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
if (buffersProcessed == 0) return;
@@ -393,36 +467,16 @@ namespace Robust.Client.Audio.Midi
Source.GetBuffersProcessed(buffers);
lock (_playerStateLock)
{
// _sequencer.Process(10);
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
}
if (Mono) // Turn audio to mono
{
var l = length * buffers.Length;
if (Sse.IsSupported)
{
fixed (float* ptr = audio)
{
for (var j = 0; j < l; j += 4)
{
var k = j + l;
var jV = Sse.LoadVector128(ptr + j);
var kV = Sse.LoadVector128(ptr + k);
Sse.Store(j + ptr, Sse.Add(jV, kV));
}
}
}
else
{
for (var j = 0; j < l; j++)
{
var k = j + l;
audio[j] = ((audio[k] + audio[j]));
}
}
NumericsHelpers.Add(audio[..l], audio[l..]);
}
for (var i = 0; i < buffers.Length; i++)
@@ -452,6 +506,7 @@ namespace Robust.Client.Audio.Midi
var timestamp = SequencerTick;
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
midiEv.Tick = timestamp;
midiEvent.Dispose();
SendMidiEvent(midiEv);
return 0;
}
@@ -462,6 +517,7 @@ namespace Robust.Client.Audio.Midi
var timestamp = SequencerTick;
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
midiEv.Tick = timestamp;
midiEvent.Dispose();
SendMidiEvent(midiEv);
return 0;
}
@@ -478,16 +534,16 @@ namespace Robust.Client.Audio.Midi
lock(_playerStateLock)
switch (midiEvent.Type)
{
// Note On 0x80
case 144:
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
// Note Off - 0x90
// Note Off - 0x80
case 128:
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// Note On 0x90
case 144:
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
// After Touch - 0xA
case 160:
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
@@ -522,6 +578,12 @@ namespace Robust.Client.Audio.Midi
case 81:
// System Messages - 0xF0
case 240:
switch (midiEvent.Control)
{
case 11:
_synth.AllNotesOff(midiEvent.Channel);
break;
}
return;
default:
@@ -543,7 +605,7 @@ namespace Robust.Client.Audio.Midi
if (Disposed) return;
var seqEv = (SequencerEvent) midiEvent;
seqEv.Dest = _synthRegister;
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
_sequencer.SendAt(seqEv, time, absolute);
}

View File

@@ -13,6 +13,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -25,6 +26,7 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -165,7 +167,7 @@ namespace Robust.Client
/// receiving states when they join the lobby.
/// </summary>
/// <param name="session">Session of the player.</param>
private void OnPlayerJoinedServer(IPlayerSession session)
private void OnPlayerJoinedServer(ICommonSession session)
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.Connected);
@@ -175,20 +177,11 @@ namespace Robust.Client
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
}
private void GameStartedSetup()
{
_entityManager.Startup();
_mapManager.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
}
/// <summary>
/// Player is joining the game
/// </summary>
/// <param name="session">Session of the player.</param>
private void OnPlayerJoinedGame(IPlayerSession session)
private void OnPlayerJoinedGame(ICommonSession session)
{
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.InGame);
@@ -218,12 +211,22 @@ namespace Robust.Client
GameStoppedReset();
}
private void GameStartedSetup()
{
_entityManager.Startup();
_mapManager.Startup();
_entityLookup.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
}
private void GameStoppedReset()
{
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityLookup.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();
@@ -295,12 +298,12 @@ namespace Robust.Client
/// <summary>
/// The session that triggered the event.
/// </summary>
private IPlayerSession? Session { get; }
private ICommonSession? Session { get; }
/// <summary>
/// Constructs a new instance of the class.
/// </summary>
public PlayerEventArgs(IPlayerSession? session)
public PlayerEventArgs(ICommonSession? session)
{
Session = session;
}

View File

@@ -45,7 +45,7 @@ namespace Robust.Client
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, SharedEntityLookup>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();

View File

@@ -17,6 +17,7 @@ namespace Robust.Client
public bool Launcher { get; }
public string? Username { get; }
public IReadOnlyCollection<(string key, string value)> CVars { get; }
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
// Also I don't like spending 100ms parsing command line args. Do you?
@@ -31,6 +32,7 @@ namespace Robust.Client
var launcher = false;
string? username = null;
var cvars = new List<(string, string)>();
var logLevels = new List<(string, string)>();
var mountOptions = new MountOptions();
using var enumerator = args.GetEnumerator();
@@ -124,6 +126,26 @@ namespace Robust.Client
mountOptions.DirMounts.Add(enumerator.Current);
}
else if (arg == "--loglevel")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing loglevel sawmill.");
return false;
}
var loglevel = enumerator.Current;
DebugTools.AssertNotNull(loglevel);
var pos = loglevel.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in loglevel.");
return false;
}
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
}
else if (arg == "--help")
{
PrintHelp();
@@ -142,6 +164,7 @@ namespace Robust.Client
launcher,
username,
cvars,
logLevels,
connectAddress,
ss14Address,
mountOptions);
@@ -162,6 +185,7 @@ Options:
--launcher Run in launcher mode (no main menu, auto connect).
--username Override username.
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
--mount-dir Resource directory to mount.
--mount-zip Resource zip to mount.
--help Display this help text and exit.
@@ -175,6 +199,7 @@ Options:
bool launcher,
string? username,
IReadOnlyCollection<(string key, string value)> cVars,
IReadOnlyCollection<(string key, string value)> logLevels,
string connectAddress, string? ss14Address,
MountOptions mountOptions)
{
@@ -184,6 +209,7 @@ Options:
Launcher = launcher;
Username = username;
CVars = cVars;
LogLevels = logLevels;
ConnectAddress = connectAddress;
Ss14Address = ss14Address;
MountOptions = mountOptions;

View File

@@ -36,7 +36,7 @@ namespace Robust.Client.Console
Message = message;
}
}
/// <inheritdoc cref="IClientConsoleHost" />
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
{
@@ -45,9 +45,9 @@ namespace Robust.Client.Console
/// <inheritdoc />
public void Initialize()
{
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
Reset();
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));

View File

@@ -17,11 +17,11 @@ namespace Robust.Client.Console
public void Initialize()
{
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME);
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME);
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME);
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME, ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME, ReceiveScriptStartAckResponse);
_netManager.RegisterNetMessage<MsgScriptStop>();
_netManager.RegisterNetMessage<MsgScriptEval>();
_netManager.RegisterNetMessage<MsgScriptStart>();
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
}
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)
@@ -30,7 +30,8 @@ namespace Robust.Client.Console
var console = new ScriptConsoleServer(this, session);
_activeConsoles.Add(session, console);
console.Open();
// FIXME: When this is Open(), resizing the window will cause its position to get NaN'd.
console.OpenCentered();
}
private void ReceiveScriptResponse(MsgScriptResponse message)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -23,7 +23,7 @@ namespace Robust.Client.Debugging
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
@@ -70,7 +70,7 @@ namespace Robust.Client.Debugging
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager));
}
else
{
@@ -171,7 +171,7 @@ namespace Robust.Client.Debugging
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;
var worldBox = physBody.GetWorldAABB(_mapManager);
var worldBox = physBody.GetWorldAABB();
if (worldBox.IsEmpty()) continue;
foreach (var fixture in physBody.Fixtures)
@@ -270,14 +270,14 @@ namespace Robust.Client.Debugging
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityManager _entityManager;
private readonly IEntityLookup _lookup;
private readonly IEyeManager _eyeManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
{
_entityManager = entityManager;
_lookup = lookup;
_eyeManager = eyeManager;
}
@@ -286,18 +286,17 @@ namespace Robust.Client.Debugging
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var entity in _entityManager.GetEntities())
var viewport = _eyeManager.GetWorldViewport();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
{
var transform = entity.Transform;
if (transform.MapID != _eyeManager.CurrentMap ||
!_eyeManager.GetWorldViewport().Contains(transform.WorldPosition))
{
continue;
}
var center = transform.WorldPosition;
var xLine = transform.WorldRotation.RotateVec(Vector2.UnitX);
var yLine = transform.WorldRotation.RotateVec(Vector2.UnitY);
var worldRotation = transform.WorldRotation;
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);

View File

@@ -1,12 +1,12 @@
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
namespace Robust.Client.Debugging
{
@@ -54,7 +54,7 @@ namespace Robust.Client.Debugging
public void Initialize()
{
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME, HandleDrawRay);
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
}
private void HandleDrawRay(MsgRay msg)

View File

@@ -125,7 +125,6 @@ namespace Robust.Client
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
IoCManager.Resolve<IEntityLookup>().Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();
@@ -203,6 +202,26 @@ namespace Robust.Client
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
if (_commandLineArgs != null)
{
foreach (var (sawmill, level) in _commandLineArgs.LogLevels)
{
LogLevel? logLevel;
if (level == "null")
logLevel = null;
else
{
if (!Enum.TryParse<LogLevel>(level, out var result))
{
System.Console.WriteLine($"LogLevel {level} does not exist!");
continue;
}
logLevel = result;
}
_logManager.GetSawmill(sawmill).Level = logLevel;
}
}
// Figure out user data directory.
var userDataDir = GetUserDataDir();

View File

@@ -9,58 +9,30 @@ namespace Robust.Client.GameObjects
public ClientComponentFactory()
{
// Required for the engine to work
Register<MetaDataComponent>();
RegisterReference<MetaDataComponent, IMetaDataComponent>();
// Required for the engine to work
Register<TransformComponent>();
RegisterReference<TransformComponent, ITransformComponent>();
Register<MapComponent>();
RegisterReference<MapComponent, IMapComponent>();
Register<MapGridComponent>();
RegisterReference<MapGridComponent, IMapGridComponent>();
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
RegisterIgnore("KeyBindingInput");
Register<InputComponent>();
Register<SpriteComponent>();
RegisterReference<SpriteComponent, SharedSpriteComponent>();
RegisterReference<SpriteComponent, ISpriteComponent>();
Register<ClientOccluderComponent>();
RegisterReference<ClientOccluderComponent, OccluderComponent>();
Register<EyeComponent>();
RegisterReference<EyeComponent, SharedEyeComponent>();
Register<AppearanceComponent>();
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
Register<AppearanceTestComponent>();
Register<SnapGridComponent>();
Register<ClientUserInterfaceComponent>();
RegisterReference<ClientUserInterfaceComponent, SharedUserInterfaceComponent>();
Register<AnimationPlayerComponent>();
Register<TimerComponent>();
RegisterClass<MetaDataComponent>();
RegisterClass<TransformComponent>();
RegisterClass<MapComponent>();
RegisterClass<MapGridComponent>();
RegisterClass<PhysicsComponent>();
RegisterClass<CollisionWakeComponent>();
RegisterClass<ClientUserInterfaceComponent>();
RegisterClass<ContainerManagerComponent>();
RegisterClass<InputComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<ClientOccluderComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();
RegisterClass<SnapGridComponent>();
RegisterClass<AnimationPlayerComponent>();
RegisterClass<TimerComponent>();
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
RegisterClass<DebugExceptionOnAddComponent>();
RegisterClass<DebugExceptionInitializeComponent>();
RegisterClass<DebugExceptionStartupComponent>();
#endif
}

View File

@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
@@ -67,7 +62,7 @@ namespace Robust.Client.GameObjects
/// <inheritdoc />
public void SetupNetworking()
{
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
}
public override void TickUpdate(float frameTime, Histogram? histogram)
@@ -109,6 +104,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
if (!component.NetID.HasValue)

View File

@@ -10,6 +10,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedAppearanceComponent))]
public sealed class AppearanceComponent : SharedAppearanceComponent
{
[ViewVariables]

View File

@@ -10,6 +10,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedEyeComponent))]
public class EyeComponent : SharedEyeComponent
{
[Dependency] private readonly IEyeManager _eyeManager = default!;

View File

@@ -8,10 +8,11 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(OccluderComponent))]
internal sealed class ClientOccluderComponent : OccluderComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables] private (GridId, Vector2i) _lastPosition;
[ViewVariables] internal OccluderDir Occluding { get; private set; }
[ViewVariables] internal uint UpdateGeneration { get; set; }

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
@@ -167,7 +168,7 @@ namespace Robust.Client.GameObjects
set
{
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedMessage(this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
}
}
@@ -179,6 +180,18 @@ namespace Robust.Client.GameObjects
Mask = null;
}
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
/// <summary>
/// What grids we're on for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal List<GridId> IntersectingGrids = new();
void ISerializationHooks.AfterDeserialization()
{
if (_maskPath != null)
@@ -230,7 +243,7 @@ namespace Robust.Client.GameObjects
if (map != MapId.Nullspace)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new RenderTreeRemoveLightMessage(this, map));
new RenderTreeRemoveLightEvent(this, map));
}
}
@@ -248,11 +261,11 @@ namespace Robust.Client.GameObjects
}
}
public struct PointLightRadiusChangedMessage
public class PointLightRadiusChangedEvent : EntityEventArgs
{
public PointLightComponent PointLightComponent { get; }
public PointLightRadiusChangedMessage(PointLightComponent pointLightComponent)
public PointLightRadiusChangedEvent(PointLightComponent pointLightComponent)
{
PointLightComponent = pointLightComponent;
}

View File

@@ -27,6 +27,8 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedSpriteComponent))]
[ComponentReference(typeof(ISpriteComponent))]
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
IComponentDebug, ISerializationHooks
{
@@ -123,6 +125,18 @@ namespace Robust.Client.GameObjects
[DataField("directional")]
private bool _directional = true;
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
/// <summary>
/// What grids we're on for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal List<GridId> IntersectingGrids { get; } = new();
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
@@ -1345,18 +1359,6 @@ namespace Robust.Client.GameObjects
return texture;
}
public override void OnRemove()
{
base.OnRemove();
var map = Owner.Transform.MapID;
if (map != MapId.Nullspace)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new RenderTreeRemoveSpriteMessage(this, map));
}
}
public void FrameUpdate(float delta)
{
foreach (var t in Layers)
@@ -1576,9 +1578,10 @@ namespace Robust.Client.GameObjects
{
var builder = new StringBuilder();
builder.AppendFormat(
"vis/depth/scl/rot/ofs/col/diral/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{7}\n",
"vis/depth/scl/rot/ofs/col/norot/override/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{8}/{7}\n",
Visible, DrawDepth, Scale, Rotation, Offset,
Color, Directional, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation)
Color, NoRotation, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation),
DirectionOverride
);
foreach (var layer in Layers)
@@ -2176,6 +2179,10 @@ namespace Robust.Client.GameObjects
return null;
}
public void QueueDelete()
{
}
public void Delete()
{
}
@@ -2190,10 +2197,12 @@ namespace Robust.Client.GameObjects
return Enumerable.Empty<T>();
}
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendMessage(IComponent? owner, ComponentMessage message)
{
}
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null)
{
}

View File

@@ -2,14 +2,14 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedUserInterfaceComponent))]
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
@@ -23,6 +23,9 @@ namespace Robust.Client.GameObjects
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
[ViewVariables]
public IEnumerable<BoundUserInterface> Interfaces => _openInterfaces.Values;
void ISerializationHooks.AfterDeserialization()
{
_interfaces.Clear();
@@ -33,48 +36,40 @@ namespace Robust.Client.GameObjects
}
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
ICommonSession? session = null)
internal void MessageReceived(BoundUIWrapMessage msg)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
switch (msg.Message)
{
case BoundInterfaceMessageWrapMessage wrapped:
// Double nested switches who needs readability anyways.
switch (wrapped.Message)
case OpenBoundInterfaceMessage _:
if (_openInterfaces.ContainsKey(msg.UiKey))
{
case OpenBoundInterfaceMessage _:
if (_openInterfaces.ContainsKey(wrapped.UiKey))
{
return;
}
return;
}
OpenInterface(wrapped);
break;
OpenInterface(msg);
break;
case CloseBoundInterfaceMessage _:
Close(wrapped.UiKey, true);
break;
case CloseBoundInterfaceMessage _:
Close(msg.UiKey, true);
break;
default:
if (_openInterfaces.TryGetValue(wrapped.UiKey, out var bi))
{
bi.InternalReceiveMessage(wrapped.Message);
}
break;
default:
if (_openInterfaces.TryGetValue(msg.UiKey, out var bi))
{
bi.InternalReceiveMessage(msg.Message);
}
break;
}
}
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
private void OpenInterface(BoundUIWrapMessage wrapped)
{
var data = _interfaces[wrapped.UiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});
var boundInterface =
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
boundInterface.Open();
_openInterfaces[wrapped.UiKey] = boundInterface;
}
@@ -86,7 +81,7 @@ namespace Robust.Client.GameObjects
return;
}
if(!remoteCall)
if (!remoteCall)
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
_openInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
@@ -94,7 +89,8 @@ namespace Robust.Client.GameObjects
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)
{
SendNetworkMessage(new BoundInterfaceMessageWrapMessage(message, uiKey));
EntitySystem.Get<UserInterfaceSystem>()
.Send(new BoundUIWrapMessage(Owner.Uid, message, uiKey));
}
}

View File

@@ -41,17 +41,6 @@ namespace Robust.Client.GameObjects
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<PlayAudioEntityMessage>();
UnsubscribeNetworkEvent<PlayAudioGlobalMessage>();
UnsubscribeNetworkEvent<PlayAudioPositionalMessage>();
UnsubscribeNetworkEvent<StopAudioMessageClient>();
UnsubscribeLocalEvent<SoundSystem.QueryAudioSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
{
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);
@@ -265,7 +254,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(entity.Transform.WorldPosition))
{
source.Dispose();
Logger.Warning("Can't play positional audio, can't set position.");
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}
@@ -312,7 +301,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
{
source.Dispose();
Logger.Warning("Can't play positional audio, can't set position.");
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}

View File

@@ -38,15 +38,6 @@ namespace Robust.Client.GameObjects
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
}
/// <inheritdoc />
public override void Shutdown()
{
UnsubscribeLocalEvent<OccluderDirtyEvent>();
UnsubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>();
base.Shutdown();
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);

View File

@@ -0,0 +1,134 @@
#if DEBUG
using System;
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
internal sealed class DebugGridTileLookupSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
_label.Visible = true;
LastTile = default;
}
else
{
_label.Text = null;
_label.Visible = false;
}
}
}
private bool _enabled;
private (GridId Grid, Vector2i Indices) LastTile;
// Label and shit that follows cursor
private Label _label = new()
{
Visible = false,
};
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<SendGridTileLookupMessage>(HandleSentEntities);
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<SendGridTileLookupMessage>();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label);
}
private void RequestEntities(GridId gridId, Vector2i indices)
{
if (gridId == GridId.Invalid) return;
RaiseNetworkEvent(new RequestGridTileLookupMessage(gridId, indices));
}
private void HandleSentEntities(SendGridTileLookupMessage message)
{
if (!Enabled) return;
var text = new StringBuilder();
text.AppendLine($"GridId: {LastTile.Grid}, Tile: {LastTile.Indices}");
for (var i = 0; i < message.Entities.Count; i++)
{
var uid = message.Entities[i];
if (!EntityManager.TryGetEntity(uid, out var entity)) continue;
text.AppendLine(entity.ToString());
}
_label.Text = text.ToString();
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled) return;
var mousePos = _inputManager.MouseScreenPosition;
var worldPos = _eyeManager.ScreenToMap(mousePos);
GridId gridId;
Vector2i tile;
if (_mapManager.TryFindGridAt(worldPos, out var grid))
{
gridId = grid.Index;
tile = grid.WorldToTile(worldPos.Position);
}
else
{
gridId = GridId.Invalid;
tile = new Vector2i((int) MathF.Floor(worldPos.Position.X), (int) MathF.Floor(worldPos.Position.Y));
}
LayoutContainer.SetPosition(_label, mousePos.Position);
if ((gridId, tile).Equals(LastTile)) return;
_label.Text = null;
LastTile = (gridId, tile);
RequestEntities(gridId, tile);
}
}
internal sealed class RequestTileEntities : IConsoleCommand
{
public string Command => "tilelookup";
public string Description => "Used for debugging GridTileLookupSystem";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugGridTileLookupSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Physics;
using Robust.Shared.GameObjects;
@@ -6,6 +8,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
@@ -47,14 +50,171 @@ namespace Robust.Client.GameObjects
_mapManager.OnGridCreated += MapManagerOnGridCreated;
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
SubscribeLocalEvent<EntMapIdChangedMessage>(EntMapIdChanged);
SubscribeLocalEvent<MoveEvent>(EntMoved);
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<PointLightRadiusChangedMessage>(PointLightRadiusChanged);
SubscribeLocalEvent<RenderTreeRemoveSpriteMessage>(RemoveSprite);
SubscribeLocalEvent<RenderTreeRemoveLightMessage>(RemoveLight);
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
SubscribeLocalEvent<SpriteComponent, MoveEvent>(SpriteMoved);
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
SubscribeLocalEvent<PointLightComponent, MoveEvent>(LightMoved);
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
}
private void AnythingMoved(MoveEvent args)
{
AnythingMovedSubHandler(args.Sender.Transform);
}
private void AnythingMovedSubHandler(ITransformComponent sender)
{
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
// (Struct-based events ok though)
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
QueueSpriteUpdate(sprite);
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
QueueLightUpdate(light);
foreach (ITransformComponent child in sender.Children)
{
AnythingMovedSubHandler(child);
}
}
// For the RemoveX methods
// If the Transform is removed BEFORE the Sprite/Light,
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
// Otherwise these will still have their past MapId and that's all we need..
#region SpriteHandlers
private void SpriteMapChanged(EntityUid uid, SpriteComponent component, EntMapIdChangedMessage args)
{
QueueSpriteUpdate(component);
}
private void SpriteMoved(EntityUid uid, SpriteComponent component, MoveEvent args)
{
QueueSpriteUpdate(component);
}
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
{
QueueSpriteUpdate(component);
}
private void RemoveSprite(EntityUid uid, SpriteComponent component, ComponentRemove args)
{
ClearSprite(component);
}
private void ClearSprite(SpriteComponent component)
{
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
{
foreach (var gridId in component.IntersectingGrids)
{
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
tree.SpriteTree.Remove(component);
}
}
component.IntersectingGrids.Clear();
}
private void QueueSpriteUpdate(SpriteComponent component)
{
if (component.TreeUpdateQueued) return;
component.TreeUpdateQueued = true;
_spriteQueue.Add(component);
foreach (var child in component.Owner.Transform.Children)
{
QueueSpriteUpdate(child.Owner);
}
}
private void QueueSpriteUpdate(IEntity entity)
{
if (!entity.TryGetComponent(out SpriteComponent? spriteComponent)) return;
QueueSpriteUpdate(spriteComponent);
foreach (var child in entity.Transform.Children)
{
QueueSpriteUpdate(child.Owner);
}
}
#endregion
#region LightHandlers
private void LightMapChanged(EntityUid uid, PointLightComponent component, EntMapIdChangedMessage args)
{
QueueLightUpdate(component);
}
private void LightMoved(EntityUid uid, PointLightComponent component, MoveEvent args)
{
QueueLightUpdate(component);
}
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
{
QueueLightUpdate(component);
}
private void PointLightRadiusChanged(EntityUid uid, PointLightComponent component, PointLightRadiusChangedEvent args)
{
QueueLightUpdate(component);
}
private void RemoveLight(EntityUid uid, PointLightComponent component, RenderTreeRemoveLightEvent args)
{
ClearLight(component);
}
private void ClearLight(PointLightComponent component)
{
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
{
foreach (var gridId in component.IntersectingGrids)
{
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
tree.LightTree.Remove(component);
}
}
component.IntersectingGrids.Clear();
}
private void QueueLightUpdate(PointLightComponent component)
{
if (component.TreeUpdateQueued) return;
component.TreeUpdateQueued = true;
_lightQueue.Add(component);
foreach (var child in component.Owner.Transform.Children)
{
QueueLightUpdate(child.Owner);
}
}
private void QueueLightUpdate(IEntity entity)
{
if (!entity.TryGetComponent(out PointLightComponent? lightComponent)) return;
QueueLightUpdate(lightComponent);
foreach (var child in entity.Transform.Children)
{
QueueLightUpdate(child.Owner);
}
}
#endregion
public override void Shutdown()
{
base.Shutdown();
@@ -62,136 +222,27 @@ namespace Robust.Client.GameObjects
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
UnsubscribeLocalEvent<EntMapIdChangedMessage>();
UnsubscribeLocalEvent<MoveEvent>();
UnsubscribeLocalEvent<EntParentChangedMessage>();
UnsubscribeLocalEvent<PointLightRadiusChangedMessage>();
UnsubscribeLocalEvent<RenderTreeRemoveSpriteMessage>();
UnsubscribeLocalEvent<RenderTreeRemoveLightMessage>();
}
// For these next 2 methods (the Remove* ones):
// If the Transform is removed BEFORE the Sprite/Light,
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
// Otherwise these will still have their past MapId and that's all we need..
private void RemoveLight(RenderTreeRemoveLightMessage ev)
{
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.LightAabbFunc(ev.Light), true))
{
_gridTrees[ev.Map][gridId].LightTree.Remove(ev.Light);
}
}
private void RemoveSprite(RenderTreeRemoveSpriteMessage ev)
{
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.SpriteAabbFunc(ev.Sprite), true))
{
_gridTrees[ev.Map][gridId].SpriteTree.Remove(ev.Sprite);
}
}
private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev)
{
QueueUpdateLight(ev.PointLightComponent);
}
private void EntParentChanged(EntParentChangedMessage ev)
{
UpdateEntity(ev.Entity);
}
private void EntMoved(MoveEvent ev)
{
UpdateEntity(ev.Sender);
}
private void UpdateEntity(IEntity entity)
{
if (entity.TryGetComponent(out SpriteComponent? spriteComponent))
{
if (!spriteComponent.TreeUpdateQueued)
{
spriteComponent.TreeUpdateQueued = true;
_spriteQueue.Add(spriteComponent);
}
}
if (entity.TryGetComponent(out PointLightComponent? light))
{
QueueUpdateLight(light);
}
foreach (var child in entity.Transform.ChildEntityUids)
{
UpdateEntity(EntityManager.GetEntity(child));
}
}
private void QueueUpdateLight(PointLightComponent light)
{
if (!light.TreeUpdateQueued)
{
light.TreeUpdateQueued = true;
_lightQueue.Add(light);
}
}
private void EntMapIdChanged(EntMapIdChangedMessage ev)
{
// Nullspace is a valid map ID for stuff to have but we also aren't gonna bother indexing it.
// So that's why there's a GetValueOrDefault.
var oldMapTrees = _gridTrees.GetValueOrDefault(ev.OldMapId);
var newMapTrees = _gridTrees.GetValueOrDefault(ev.Entity.Transform.MapID);
// TODO: MMMM probably a better way to do this.
if (ev.Entity.TryGetComponent(out SpriteComponent? sprite))
{
if (oldMapTrees != null)
{
foreach (var (_, gridTree) in oldMapTrees)
{
gridTree.SpriteTree.Remove(sprite);
}
}
var bounds = MapTrees.SpriteAabbFunc(sprite);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
{
var gridBounds = gridId == GridId.Invalid
? bounds : bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
newMapTrees?[gridId].SpriteTree.AddOrUpdate(sprite, gridBounds);
}
}
if (ev.Entity.TryGetComponent(out PointLightComponent? light))
{
if (oldMapTrees != null)
{
foreach (var (_, gridTree) in oldMapTrees)
{
gridTree.LightTree.Remove(light);
}
}
var bounds = MapTrees.LightAabbFunc(light);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
{
var gridBounds = gridId == GridId.Invalid
? bounds : bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
newMapTrees?[gridId].LightTree.AddOrUpdate(light, gridBounds);
}
}
}
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
{
foreach (var (_, gridTree) in _gridTrees[e.Map])
{
foreach (var comp in gridTree.LightTree)
{
comp.IntersectingGrids.Clear();
}
foreach (var comp in gridTree.SpriteTree)
{
comp.IntersectingGrids.Clear();
}
// Just in case?
gridTree.LightTree.Clear();
gridTree.SpriteTree.Clear();
}
_gridTrees.Remove(e.Map);
}
@@ -215,47 +266,109 @@ namespace Robust.Client.GameObjects
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
{
var gridTree = _gridTrees[mapId][gridId];
foreach (var sprite in gridTree.SpriteTree)
{
sprite.IntersectingGrids.Remove(gridId);
}
foreach (var light in gridTree.LightTree)
{
light.IntersectingGrids.Remove(gridId);
}
// Clear in case
gridTree.LightTree.Clear();
gridTree.SpriteTree.Clear();
_gridTrees[mapId].Remove(gridId);
}
public override void FrameUpdate(float frameTime)
{
foreach (var queuedUpdateSprite in _spriteQueue)
foreach (var sprite in _spriteQueue)
{
var map = queuedUpdateSprite.Owner.Transform.MapID;
if (map == MapId.Nullspace)
var mapId = sprite.Owner.Transform.MapID;
// If we're on a new map then clear the old one.
if (sprite.IntersectingMapId != mapId)
{
continue;
ClearSprite(sprite);
}
var mapTree = _gridTrees[map];
sprite.IntersectingMapId = mapId;
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
MapTrees.SpriteAabbFunc(queuedUpdateSprite), true))
if (mapId == MapId.Nullspace) continue;
var mapTree = _gridTrees[mapId];
var aabb = MapTrees.SpriteAabbFunc(sprite);
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
// Remove from old
foreach (var gridId in sprite.IntersectingGrids)
{
mapTree[gridId].SpriteTree.AddOrUpdate(queuedUpdateSprite);
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].SpriteTree.Remove(sprite);
}
queuedUpdateSprite.TreeUpdateQueued = false;
// Rebuild in the update below
sprite.IntersectingGrids.Clear();
// Update / add to new
foreach (var gridId in intersectingGrids)
{
var translated = aabb.Translated(gridId == GridId.Invalid
? Vector2.Zero
: -_mapManager.GetGrid(gridId).WorldPosition);
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
sprite.IntersectingGrids.Add(gridId);
}
sprite.TreeUpdateQueued = false;
}
foreach (var queuedUpdateLight in _lightQueue)
foreach (var light in _lightQueue)
{
var map = queuedUpdateLight.Owner.Transform.MapID;
if (map == MapId.Nullspace)
var mapId = light.Owner.Transform.MapID;
// If we're on a new map then clear the old one.
if (light.IntersectingMapId != mapId)
{
continue;
ClearLight(light);
}
var mapTree = _gridTrees[map];
light.IntersectingMapId = mapId;
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
MapTrees.LightAabbFunc(queuedUpdateLight), true))
if (mapId == MapId.Nullspace) continue;
var mapTree = _gridTrees[mapId];
var aabb = MapTrees.LightAabbFunc(light);
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
// Remove from old
foreach (var gridId in intersectingGrids)
{
mapTree[gridId].LightTree.AddOrUpdate(queuedUpdateLight);
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].LightTree.Remove(light);
}
queuedUpdateLight.TreeUpdateQueued = false;
// Rebuild in the update below
light.IntersectingGrids.Clear();
// Update / add to new
foreach (var gridId in intersectingGrids)
{
var translated = aabb.Translated(gridId == GridId.Invalid
? Vector2.Zero
: -_mapManager.GetGrid(gridId).WorldPosition);
mapTree[gridId].LightTree.AddOrUpdate(light, translated);
light.IntersectingGrids.Add(gridId);
}
light.TreeUpdateQueued = false;
}
_spriteQueue.Clear();
@@ -290,21 +403,9 @@ namespace Robust.Client.GameObjects
}
}
internal struct RenderTreeRemoveSpriteMessage
internal class RenderTreeRemoveLightEvent : EntityEventArgs
{
public RenderTreeRemoveSpriteMessage(SpriteComponent sprite, MapId map)
{
Sprite = sprite;
Map = map;
}
public SpriteComponent Sprite { get; }
public MapId Map { get; }
}
internal struct RenderTreeRemoveLightMessage
{
public RenderTreeRemoveLightMessage(PointLightComponent light, MapId map)
public RenderTreeRemoveLightEvent(PointLightComponent light, MapId map)
{
Light = light;
Map = map;

View File

@@ -16,11 +16,17 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private RenderingTreeSystem _treeSystem = default!;
public override void Initialize()
{
base.Initialize();
_treeSystem = Get<RenderingTreeSystem>();
}
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
var renderTreeSystem = EntitySystemManager.GetEntitySystem<RenderingTreeSystem>();
// So we could calculate the correct size of the entities based on the contents of their sprite...
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5);
@@ -33,18 +39,9 @@ namespace Robust.Client.GameObjects
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
{
Box2 gridBounds;
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = pvsBounds;
}
else
{
gridBounds = pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap, gridId);
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{

View File

@@ -0,0 +1,37 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public sealed class UserInterfaceSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
}
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.Interfaces)
{
bui.Dispose();
}
}
private void MessageReceived(BoundUIWrapMessage ev)
{
var cmp = ComponentManager.GetComponent<ClientUserInterfaceComponent>(ev.Entity);
cmp.MessageReceived(ev);
}
internal void Send(BoundUIWrapMessage msg)
{
RaiseNetworkEvent(msg);
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class PlayerAttachedMsg : ComponentMessage
{
}
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class PlayerDetachedMsg : ComponentMessage
{

View File

@@ -1,3 +1,5 @@
// ReSharper disable once RedundantUsingDirective
// Used in EXCEPTION_TOLERANCE preprocessor
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -5,19 +7,18 @@ using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -80,8 +81,8 @@ namespace Robust.Client.GameStates
{
_processor = new GameStateProcessor(_timing);
_network.RegisterNetMessage<MsgState>(MsgState.NAME, HandleStateMessage);
_network.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME);
_network.RegisterNetMessage<MsgState>(HandleStateMessage);
_network.RegisterNetMessage<MsgStateAck>();
_client.RunLevelChanged += RunLevelChanged;
_config.OnValueChanged(CVars.NetInterp, b => _processor.Interpolation = b, true);

View File

@@ -146,13 +146,12 @@ namespace Robust.Client.GameStates
private void DrawWorld(in OverlayDrawArgs args)
{
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
if(!pvsEnabled)
return;
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
var pvsCenter = _eyeManager.CurrentEye.Position;
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange * 2, pvsRange * 2));
var worldHandle = args.WorldHandle;

View File

@@ -44,8 +44,12 @@ namespace Robust.Client.Graphics.Clyde
internal bool IsEfxSupported;
private ISawmill _openALSawmill = default!;
private void _initializeAudio()
{
_openALSawmill = Logger.GetSawmill("clyde.oal");
_audioOpenDevice();
// Create OpenAL context.
@@ -74,9 +78,9 @@ namespace Robust.Client.Graphics.Clyde
_alContextExtensions.Add(extension);
}
Logger.DebugS("clyde.oal", "OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
Logger.DebugS("clyde.oal", "OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
Logger.DebugS("clyde.oal", "OpenAL Version: {0}", AL.Get(ALGetString.Version));
_openALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
_openALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
_openALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
}
private void _audioOpenDevice()
@@ -89,7 +93,7 @@ namespace Robust.Client.Graphics.Clyde
_openALDevice = ALC.OpenDevice(preferredDevice);
if (_openALDevice == IntPtr.Zero)
{
Logger.WarningS("clyde.oal", "Unable to open preferred audio device '{0}': {1}. Falling back default.",
_openALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
preferredDevice, ALC.GetError(ALDevice.Null));
_openALDevice = ALC.OpenDevice(null);
@@ -153,7 +157,7 @@ namespace Robust.Client.Graphics.Clyde
// Clear out finalized audio sources.
while (_sourceDisposeQueue.TryDequeue(out var handles))
{
Logger.DebugS("clyde.oal", "Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
_openALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
@@ -163,7 +167,7 @@ namespace Robust.Client.Graphics.Clyde
// Clear out finalized buffered audio sources.
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
{
Logger.DebugS("clyde.oal", "Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
_openALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
@@ -211,24 +215,24 @@ namespace Robust.Client.Graphics.Clyde
return audioSource;
}
private static void _checkAlcError(ALDevice device,
private void _checkAlcError(ALDevice device,
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = ALC.GetError(device);
if (error != AlcError.NoError)
{
Logger.ErrorS("clyde.oal", "[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
_openALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
}
}
private static void _checkAlError([CallerMemberName] string callerMember = "",
private void _checkAlError([CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
{
Logger.ErrorS("clyde.oal", "[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
_openALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
}
}
@@ -330,6 +334,35 @@ namespace Robust.Client.Graphics.Clyde
return new AudioStream(handle, length, wav.NumChannels, name);
}
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
var fmt = channels switch
{
1 => ALFormat.Mono16,
2 => ALFormat.Stereo16,
_ => throw new ArgumentOutOfRangeException(
nameof(channels), "Only stereo and mono is currently supported")
};
var buffer = AL.GenBuffer();
_checkAlError();
unsafe
{
fixed (short* ptr = samples)
{
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
}
}
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
return new AudioStream(handle, length, channels, name);
}
private sealed class LoadedAudioSample
{
public readonly int BufferHandle;
@@ -381,14 +414,14 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.SourcePlay(SourceHandle);
_checkAlError();
_master._checkAlError();
}
public void StopPlaying()
{
_checkDisposed();
AL.SourceStop(SourceHandle);
_checkAlError();
_master._checkAlError();
}
public bool IsPlaying
@@ -407,14 +440,14 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
_checkAlError();
_master._checkAlError();
return ret;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.Looping, value);
_checkAlError();
_master._checkAlError();
}
}
@@ -422,7 +455,7 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
_checkAlError();
_master._checkAlError();
}
public void SetVolume(float decibels)
@@ -436,7 +469,21 @@ namespace Robust.Client.Graphics.Clyde
}
_gain = MathF.Pow(10, decibels / 10);
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_checkAlError();
_master._checkAlError();
}
public void SetVolumeDirect(float scale)
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = scale;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_master._checkAlError();
}
public void SetOcclusion(float blocks)
@@ -453,7 +500,7 @@ namespace Robust.Client.Graphics.Clyde
gain *= gain * gain;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
}
_checkAlError();
_master._checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
@@ -473,7 +520,7 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
_checkAlError();
_master._checkAlError();
}
public bool SetPosition(Vector2 position)
@@ -499,7 +546,7 @@ namespace Robust.Client.Graphics.Clyde
#endif
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
_checkAlError();
_master._checkAlError();
return true;
}
@@ -526,14 +573,14 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
_checkAlError();
_master._checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
_checkAlError();
_master._checkAlError();
}
~AudioSource()
@@ -559,7 +606,7 @@ namespace Robust.Client.Graphics.Clyde
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
_master._audioSources.Remove(SourceHandle);
_checkAlError();
_master._checkAlError();
}
SourceHandle = -1;
@@ -609,7 +656,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
_checkAlError();
_master._checkAlError();
}
public void StopPlaying()
@@ -617,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.SourceStop(SourceHandle!.Value);
_checkAlError();
_master._checkAlError();
}
public bool IsPlaying
@@ -643,7 +690,7 @@ namespace Robust.Client.Graphics.Clyde
_mono = false;
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
_checkAlError();
_master._checkAlError();
}
public void SetLooping()
@@ -662,7 +709,21 @@ namespace Robust.Client.Graphics.Clyde
}
_gain = MathF.Pow(10, decibels / 10);
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
_checkAlError();
_master._checkAlError();
}
public void SetVolumeDirect(float scale)
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = scale;
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
_master._checkAlError();
}
public void SetOcclusion(float blocks)
@@ -680,7 +741,7 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
}
_checkAlError();
_master._checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
@@ -700,7 +761,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
_checkAlError();
_master._checkAlError();
}
public bool SetPosition(Vector2 position)
@@ -717,7 +778,7 @@ namespace Robust.Client.Graphics.Clyde
_mono = true;
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
_checkAlError();
_master._checkAlError();
return true;
}
@@ -744,7 +805,7 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
_checkAlError();
_master._checkAlError();
}
public void SetPitch(float pitch)
@@ -752,7 +813,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
_checkAlError();
_master._checkAlError();
}
~BufferedAudioSource()
@@ -783,7 +844,7 @@ namespace Robust.Client.Graphics.Clyde
AL.DeleteSource(SourceHandle.Value);
AL.DeleteBuffers(BufferHandles);
_master._bufferedAudioSources.Remove(SourceHandle.Value);
_checkAlError();
_master._checkAlError();
}
SourceHandle = null;

View File

@@ -59,7 +59,6 @@ namespace Robust.Client.Graphics.Clyde
private bool _enableSoftShadows = true;
private bool _checkGLErrors;
private bool _initialized;
private Thread? _gameThread;
@@ -105,7 +104,6 @@ namespace Robust.Client.Graphics.Clyde
return false;
_initializeAudio();
_initialized = true;
return true;
}

View File

@@ -48,14 +48,14 @@ namespace Robust.Client.Graphics.Clyde
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<WindowClosedEventArgs>? CloseWindow;
public event Action<WindowDestroyedEventArgs>? DestroyWindow;
public event Action<TextEventArgs>? TextEntered { add { } remove { } }
public event Action<MouseMoveEventArgs>? MouseMove { add { } remove { } }
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave { add { } remove { } }
public event Action<KeyEventArgs>? KeyUp { add { } remove { } }
public event Action<KeyEventArgs>? KeyDown { add { } remove { } }
public event Action<MouseWheelEventArgs>? MouseWheel { add { } remove { } }
public event Action<WindowClosedEventArgs>? CloseWindow { add { } remove { } }
public event Action<WindowDestroyedEventArgs>? DestroyWindow { add { } remove { } }
public Texture GetStockTexture(ClydeStockTexture stockTexture)
{
@@ -248,6 +248,12 @@ namespace Robust.Client.Graphics.Clyde
return new(default, default, 1, name);
}
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
// TODO: Might wanna actually load this so the length gets reported correctly.
return new(default, default, channels, name);
}
public IClydeAudioSource CreateAudioSource(AudioStream stream)
{
return DummyAudioSource.Instance;
@@ -323,6 +329,11 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void SetVolumeDirect(float scale)
{
// Nada.
}
public void SetOcclusion(float blocks)
{
// Nada.
@@ -594,7 +605,7 @@ namespace Robust.Client.Graphics.Clyde
public bool IsVisible { get; set; } = true;
public Vector2 ContentScale => Vector2.One;
public bool DisposeOnClose { get; set; }
public event Action<WindowClosedEventArgs>? Closed;
public event Action<WindowClosedEventArgs>? Closed { add { } remove { } }
public void MaximizeOnMonitor(IClydeMonitor monitor)
{

View File

@@ -71,7 +71,7 @@ namespace Robust.Client.Graphics.Clyde
var impl = (CursorImpl) cursor;
DebugTools.Assert(impl.Owner == this);
if (impl.Id == null)
if (impl.Id == default)
{
throw new ObjectDisposedException(nameof(cursor));
}

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using Robust.Client.Audio;
namespace Robust.Client.Graphics
@@ -8,6 +9,7 @@ namespace Robust.Client.Graphics
// AUDIO SYSTEM DOWN BELOW.
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
AudioStream LoadAudioWav(Stream stream, string? name = null);
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
void SetMasterVolume(float newVolume);

View File

@@ -18,6 +18,7 @@ namespace Robust.Client.Graphics
void SetPitch(float pitch);
void SetGlobal();
void SetVolume(float decibels);
void SetVolumeDirect(float decibels);
void SetOcclusion(float blocks);
void SetPlaybackPosition(float seconds);
void SetVelocity(Vector2 velocity);

View File

@@ -334,6 +334,7 @@ namespace Robust.Client.Input
{
// christ this crap *is* re-entrant thanks to PlacementManager and
// I honestly have no idea what the best solution here is.
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
try
@@ -341,6 +342,8 @@ namespace Robust.Client.Input
// This is terrible but anyways.
// This flag keeps track of "did a viewport fire the key up for us" so we know we don't do it again.
_currentlyFindingViewport = true;
// And this stops context switches from causing crashes
Contexts.DeferringEnabled = true;
binding.State = state;
@@ -361,12 +364,14 @@ namespace Robust.Client.Input
finally
{
_currentlyFindingViewport = false;
Contexts.DeferringEnabled = false;
}
}
public void ViewportKeyEvent(Control? viewport, BoundKeyEventArgs eventArgs)
{
_currentlyFindingViewport = false;
Contexts.DeferringEnabled = false;
var cmd = GetInputCommand(eventArgs.Function);
// TODO: Allow input commands to still get forwarded to server if necessary.

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.Physics
public override void Shutdown()
{
base.Shutdown();
UnsubscribeLocalEvent<IslandSolveMessage>();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}

View File

@@ -156,7 +156,7 @@ namespace Robust.Client.Placement
{
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
NetworkManager.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, HandlePlacementMessage);
NetworkManager.RegisterNetMessage<MsgPlacement>(HandlePlacementMessage);
_modeDictionary.Clear();
foreach (var type in ReflectionManager.GetAllChildren<PlacementMode>())

View File

@@ -2,13 +2,14 @@ using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
namespace Robust.Client.Player
{
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
{
new IEnumerable<IPlayerSession> Sessions { get; }
IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict { get; }
new IEnumerable<ICommonSession> Sessions { get; }
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
LocalPlayer? LocalPlayer { get; }

View File

@@ -1,11 +0,0 @@
using System;
using Robust.Shared.Players;
namespace Robust.Client.Player
{
/// <summary>
/// Client side session of a player.
/// </summary>
[Obsolete("Use the base " + nameof(ICommonSession))]
public interface IPlayerSession : ICommonSession { }
}

View File

@@ -4,6 +4,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
@@ -36,7 +37,7 @@ namespace Robust.Client.Player
/// Session of the local client.
/// </summary>
[ViewVariables]
public IPlayerSession Session => InternalSession;
public ICommonSession Session => InternalSession;
internal PlayerSession InternalSession { get; set; } = default!;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -28,7 +27,7 @@ namespace Robust.Client.Player
/// <summary>
/// Active sessions of connected clients to the server.
/// </summary>
private readonly Dictionary<NetUserId, IPlayerSession> _sessions = new();
private readonly Dictionary<NetUserId, ICommonSession> _sessions = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions
@@ -69,10 +68,10 @@ namespace Robust.Client.Player
/// <inheritdoc />
[ViewVariables]
IEnumerable<IPlayerSession> IPlayerManager.Sessions => _sessions.Values;
IEnumerable<ICommonSession> IPlayerManager.Sessions => _sessions.Values;
/// <inheritdoc />
public IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict => _sessions;
public IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict => _sessions;
/// <inheritdoc />
public event EventHandler? PlayerListUpdated;
@@ -82,8 +81,8 @@ namespace Robust.Client.Player
{
_client.RunLevelChanged += OnRunLevelChanged;
_network.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME);
_network.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME, HandlePlayerList);
_network.RegisterNetMessage<MsgPlayerListReq>();
_network.RegisterNetMessage<MsgPlayerList>(HandlePlayerList);
}
/// <inheritdoc />

View File

@@ -5,7 +5,7 @@ using Robust.Shared.Players;
namespace Robust.Client.Player
{
internal sealed class PlayerSession : IPlayerSession
internal sealed class PlayerSession : ICommonSession
{
internal SessionStatus Status { get; set; } = SessionStatus.Connecting;

View File

@@ -30,7 +30,7 @@ namespace Robust.Client.Prototypes
{
base.Initialize();
_netManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, accept: NetMessageAccept.Server);
_netManager.RegisterNetMessage<MsgReloadPrototypes>(accept: NetMessageAccept.Server);
_clyde.OnWindowFocused += WindowFocusedChanged;

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.UserInterface.Controls
public bool HideRoot { get; set; }
public Item? Root => _root;
public Item? TreeRoot => _root;
public Item? Selected => _selectedIndex == null ? null : _itemList[_selectedIndex.Value];

View File

@@ -288,7 +288,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
catch (Exception e)
{
sawmill.Warning("Failed to load debug console history due to exception!\n{e}");
sawmill.Warning($"Failed to load debug console history due to exception!\n{e}");
return null;
}
finally

View File

@@ -36,7 +36,8 @@ namespace Robust.Client.UserInterface.CustomControls
return;
}
_label.Text = string.Join("\n", _inputManager.DownKeyFunctions);
var functionsText = string.Join("\n", _inputManager.DownKeyFunctions);
_label.Text = $"Context: {_inputManager.Contexts.ActiveContext.Name}\n{functionsText}";
}
}
}

View File

@@ -96,7 +96,7 @@ namespace Robust.Client.UserInterface.CustomControls
var (spaceX, spaceY) = Parent!.Size;
if (Position.Y > spaceY)
{
LayoutContainer.SetPosition(this, (Position.X, spaceY - HEADER_SIZE_Y));
LayoutContainer.SetPosition(this, (Position.X, spaceY + HEADER_SIZE_Y));
}
if (Position.X > spaceX)
@@ -104,6 +104,16 @@ namespace Robust.Client.UserInterface.CustomControls
// 50 is arbitrary here. As long as it's bumped back into view.
LayoutContainer.SetPosition(this, (spaceX - 50, Position.Y));
}
if (Position.Y < 0)
{
LayoutContainer.SetPosition(this, (Position.X, 0));
}
if (Position.X < 0)
{
LayoutContainer.SetPosition(this, (0, Position.Y));
}
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)

View File

@@ -25,8 +25,6 @@ namespace Robust.Client.UserInterface
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;

View File

@@ -16,7 +16,7 @@ using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using NumberType = Robust.Client.ViewVariables.Editors.VVPropEditorNumeric.NumberType;
using static Robust.Client.ViewVariables.Editors.VVPropEditorNumeric;
namespace Robust.Client.ViewVariables
{
@@ -43,17 +43,13 @@ namespace Robust.Client.ViewVariables
public void Initialize()
{
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>(MsgViewVariablesOpenSession.NAME,
_netMessageOpenSession);
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>(MsgViewVariablesRemoteData.NAME,
_netMessageRemoteData);
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(MsgViewVariablesCloseSession.NAME,
_netMessageCloseSession);
_netManager.RegisterNetMessage<MsgViewVariablesDenySession>(MsgViewVariablesDenySession.NAME,
_netMessageDenySession);
_netManager.RegisterNetMessage<MsgViewVariablesModifyRemote>(MsgViewVariablesModifyRemote.NAME);
_netManager.RegisterNetMessage<MsgViewVariablesReqSession>(MsgViewVariablesReqSession.NAME);
_netManager.RegisterNetMessage<MsgViewVariablesReqData>(MsgViewVariablesReqData.NAME);
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>(_netMessageOpenSession);
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>(_netMessageRemoteData);
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(_netMessageCloseSession);
_netManager.RegisterNetMessage<MsgViewVariablesDenySession>(_netMessageDenySession);
_netManager.RegisterNetMessage<MsgViewVariablesModifyRemote>();
_netManager.RegisterNetMessage<MsgViewVariablesReqSession>();
_netManager.RegisterNetMessage<MsgViewVariablesReqData>();
}
public VVPropEditor PropertyFor(Type? type)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime;
@@ -217,6 +217,26 @@ namespace Robust.Server
_log.RootSawmill.AddHandler(_logHandler!);
}
if (_commandLineArgs != null)
{
foreach (var (sawmill, level) in _commandLineArgs.LogLevels)
{
LogLevel? logLevel;
if (level == "null")
logLevel = null;
else
{
if (!Enum.TryParse<LogLevel>(level, out var result))
{
System.Console.WriteLine($"LogLevel {level} does not exist!");
continue;
}
logLevel = result;
}
_log.GetSawmill(sawmill).Level = logLevel;
}
}
SelfLog.Enable(s => { System.Console.WriteLine("SERILOG ERROR: {0}", s); });
if (!SetupLoki())
@@ -306,7 +326,6 @@ namespace Robust.Server
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_entityManager.Initialize();
IoCManager.Resolve<IEntityLookup>().Initialize();
IoCManager.Resolve<ISerializationManager>().Initialize();
@@ -319,6 +338,7 @@ namespace Robust.Server
IoCManager.Resolve<IServerConsoleHost>().Initialize();
_entityManager.Startup();
IoCManager.Resolve<IEntityLookup>().Startup();
_stateManager.Initialize();
_scriptHost.Initialize();
@@ -330,6 +350,7 @@ namespace Robust.Server
_watchdogApi.Initialize();
AddFinalStringsToSerializer();
_stringSerializer.LockStrings();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
@@ -342,6 +363,16 @@ namespace Robust.Server
return false;
}
private void AddFinalStringsToSerializer()
{
var factory = IoCManager.Resolve<IComponentFactory>();
foreach (var regType in factory.AllRegisteredTypes)
{
var reg = factory.GetRegistration(regType);
_stringSerializer.AddString(reg.Name);
}
}
private bool SetupLoki()
{
var enabled = _config.GetCVar(CVars.LokiEnabled);

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared;
using Robust.Shared.Utility;
@@ -12,6 +12,7 @@ namespace Robust.Server
public string? ConfigFile { get; }
public string? DataDir { get; }
public IReadOnlyCollection<(string key, string value)> CVars { get; }
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
// Also I don't like spending 100ms parsing command line args. Do you?
@@ -21,6 +22,7 @@ namespace Robust.Server
string? configFile = null;
string? dataDir = null;
var cvars = new List<(string, string)>();
var logLevels = new List<(string, string)>();
var mountOptions = new MountOptions();
using var enumerator = args.GetEnumerator();
@@ -93,13 +95,33 @@ namespace Robust.Server
mountOptions.DirMounts.Add(enumerator.Current);
}
else if (arg == "--loglevel")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing loglevel sawmill.");
return false;
}
var loglevel = enumerator.Current;
DebugTools.AssertNotNull(loglevel);
var pos = loglevel.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in loglevel.");
return false;
}
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
}
else
{
C.WriteLine("Unknown argument: {0}", arg);
}
}
parsed = new CommandLineArgs(configFile, dataDir, cvars, mountOptions);
parsed = new CommandLineArgs(configFile, dataDir, cvars, logLevels, mountOptions);
return true;
}
@@ -110,17 +132,19 @@ Options:
--config-file Path to the config file to read from.
--data-dir Path to the data directory to read/write from/to.
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
--mount-dir Resource directory to mount.
--mount-zip Resource zip to mount.
--help Display this help text and exit.
");
}
private CommandLineArgs(string? configFile, string? dataDir, IReadOnlyCollection<(string, string)> cVars, MountOptions mountOptions)
private CommandLineArgs(string? configFile, string? dataDir, IReadOnlyCollection<(string, string)> cVars, IReadOnlyCollection<(string, string)> logLevels, MountOptions mountOptions)
{
ConfigFile = configFile;
DataDir = dataDir;
CVars = cVars;
LogLevels = logLevels;
MountOptions = mountOptions;
}
}

View File

@@ -57,9 +57,8 @@ namespace Robust.Server.Console.Commands
else
{
var mapEnt = mapMgr.GetMapEntity(mapId);
transform.AttachParent(mapEnt);
transform.WorldPosition = position;
transform.AttachParent(mapEnt);
}
shell.WriteLine($"Teleported {player} to {mapId}:{posX},{posY}.");

View File

@@ -62,15 +62,14 @@ namespace Robust.Server.Console
var sudoShell = new SudoShell(this, localShell, shell);
ExecuteInShell(sudoShell, argStr.Substring("sudo ".Length));
});
LoadConsoleCommands();
// setup networking with clients
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdAck>();
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME,
message => HandleRegistrationRequest(message.MsgChannel));
NetManager.RegisterNetMessage<MsgConCmdReg>(message => HandleRegistrationRequest(message.MsgChannel));
}
private void ExecuteInShell(IConsoleShell shell, string command)

View File

@@ -14,7 +14,7 @@ namespace Robust.Server.Debugging
public void Initialize()
{
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME);
_net.RegisterNetMessage<MsgRay>();
// TODO _physics.DebugDrawRay += data => PhysicsOnDebugDrawRay(data);
}

View File

@@ -0,0 +1,45 @@
using System;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class ActorComponent : Component
{
public override string Name => "Actor";
[ViewVariables]
public IPlayerSession PlayerSession { get; internal set; } = default!;
}
/// <summary>
/// Raised on an entity whenever a player attaches to this entity.
/// </summary>
[Serializable]
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class PlayerAttachedMsg : ComponentMessage
{
public IPlayerSession NewPlayer { get; }
public PlayerAttachedMsg(IPlayerSession newPlayer)
{
NewPlayer = newPlayer;
}
}
/// <summary>
/// Raised on an entity whenever a player detaches from this entity.
/// </summary>
[Serializable]
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class PlayerDetachedMsg : ComponentMessage
{
public IPlayerSession OldPlayer { get; }
public PlayerDetachedMsg(IPlayerSession oldPlayer)
{
OldPlayer = oldPlayer;
}
}
}

View File

@@ -1,75 +0,0 @@
using System;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class BasicActorComponent : Component, IActorComponent
{
public override string Name => "BasicActor";
[ViewVariables] public IPlayerSession playerSession { get; internal set; } = default!;
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
// Warning: careful here, Detach removes this component, make sure this is after the base shutdown
// to prevent infinite recursion
// ReSharper disable once ConstantConditionalAccessQualifier
playerSession?.DetachFromEntity();
}
}
/// <summary>
/// Raised on an entity whenever a player attaches to this entity.
/// </summary>
[Serializable]
public class PlayerAttachedMsg : ComponentMessage
{
public IPlayerSession NewPlayer { get; }
public PlayerAttachedMsg(IPlayerSession newPlayer)
{
NewPlayer = newPlayer;
}
}
/// <summary>
/// Raised on an entity whenever a player detaches from this entity.
/// </summary>
[Serializable]
public class PlayerDetachedMsg : ComponentMessage
{
public IPlayerSession OldPlayer { get; }
public PlayerDetachedMsg(IPlayerSession oldPlayer)
{
OldPlayer = oldPlayer;
}
}
public class PlayerAttachSystemMessage : EntityEventArgs
{
public PlayerAttachSystemMessage(IEntity entity, IPlayerSession newPlayer)
{
Entity = entity;
NewPlayer = newPlayer;
}
public IEntity Entity { get; }
public IPlayerSession NewPlayer { get; }
}
public class PlayerDetachedSystemMessage : EntityEventArgs
{
public PlayerDetachedSystemMessage(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -1,10 +0,0 @@
using Robust.Server.Player;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
public interface IActorComponent : IComponent
{
IPlayerSession playerSession { get; }
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Players;
namespace Robust.Server.GameObjects
{
[ComponentReference(typeof(SharedAppearanceComponent))]
public sealed class AppearanceComponent : SharedAppearanceComponent
{
[ViewVariables]

View File

@@ -8,6 +8,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
[ComponentReference(typeof(SharedEyeComponent))]
public class EyeComponent : SharedEyeComponent
{
[DataField("drawFov")]

View File

@@ -14,6 +14,8 @@ using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
[ComponentReference(typeof(SharedSpriteComponent))]
[ComponentReference(typeof(ISpriteRenderableComponent))]
public class SpriteComponent : SharedSpriteComponent, ISpriteRenderableComponent, ISerializationHooks
{
const string LayerSerializationCache = "spritelayersrv";

View File

@@ -7,8 +7,6 @@ using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -20,6 +18,7 @@ namespace Robust.Server.GameObjects
/// </summary>
/// <seealso cref="BoundUserInterface"/>
[PublicAPI]
[ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
private readonly Dictionary<object, BoundUserInterface> _interfaces =
@@ -48,7 +47,8 @@ namespace Robust.Server.GameObjects
return _interfaces[uiKey];
}
public bool TryGetBoundUserInterface(object uiKey, [NotNullWhen(true)] out BoundUserInterface? boundUserInterface)
public bool TryGetBoundUserInterface(object uiKey,
[NotNullWhen(true)] out BoundUserInterface? boundUserInterface)
{
return _interfaces.TryGetValue(uiKey, out boundUserInterface);
}
@@ -67,32 +67,20 @@ namespace Robust.Server.GameObjects
internal void SendToSession(IPlayerSession session, BoundUserInterfaceMessage message, object uiKey)
{
SendNetworkMessage(new BoundInterfaceMessageWrapMessage(message, uiKey), session.ConnectedClient);
EntitySystem.Get<UserInterfaceSystem>()
.SendTo(session, new BoundUIWrapMessage(Owner.Uid, message, uiKey));
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
ICommonSession? session = null)
internal void ReceiveMessage(IPlayerSession session, BoundUIWrapMessage msg)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
if (!_interfaces.TryGetValue(msg.UiKey, out var @interface))
{
case BoundInterfaceMessageWrapMessage wrapped:
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
if (!_interfaces.TryGetValue(wrapped.UiKey, out var @interface))
{
Logger.DebugS("go.comp.ui", "Got BoundInterfaceMessageWrapMessage for unknown UI key: {0}",
wrapped.UiKey);
return;
}
@interface.ReceiveMessage(wrapped.Message, (IPlayerSession)session);
break;
Logger.DebugS("go.comp.ui", "Got BoundInterfaceMessageWrapMessage for unknown UI key: {0}",
msg.UiKey);
return;
}
@interface.ReceiveMessage(msg.Message, session);
}
}
@@ -153,6 +141,7 @@ namespace Robust.Server.GameObjects
{
_playerStateOverrides[session] = state;
}
_stateDirty = true;
}
@@ -178,7 +167,6 @@ namespace Robust.Server.GameObjects
}
/// <summary>
/// Opens this interface for a specific client.
/// </summary>

View File

@@ -0,0 +1,173 @@
using System;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
/// <summary>
/// System that handles players being attached/detached from entities.
/// </summary>
[UsedImplicitly]
public class ActorSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AttachPlayerEvent>(OnActorPlayerAttach);
SubscribeLocalEvent<ActorComponent, DetachPlayerEvent>(OnActorPlayerDetach);
SubscribeLocalEvent<ActorComponent, ComponentShutdown>(OnActorShutdown);
}
private void OnActorPlayerAttach(AttachPlayerEvent args)
{
// Cannot attach to a deleted entity.
if (args.Entity.Deleted)
{
args.Result = false;
return;
}
var uid = args.Entity.Uid;
// Check if there was a player attached to the entity already...
if (ComponentManager.TryGetComponent(uid, out ActorComponent actor))
{
// If we're not forcing the attach, this fails.
if (!args.Force)
{
args.Result = false;
return;
}
// Set the event's force-kicked session before detaching it.
args.ForceKicked = actor.PlayerSession;
// This detach cannot fail, as a player is attached to this entity.
// It's important to note that detaching the player removes the component.
RaiseLocalEvent(uid, new DetachPlayerEvent());
}
// We add the actor component.
actor = ComponentManager.AddComponent<ActorComponent>(args.Entity);
actor.PlayerSession = args.Player;
args.Player.SetAttachedEntity(args.Entity);
args.Result = true;
// TODO: Remove component message.
args.Entity.SendMessage(actor, new PlayerAttachedMsg(args.Player));
// The player is fully attached now, raise an event!
RaiseLocalEvent(uid, new PlayerAttachedEvent(args.Entity, args.Player));
}
private void OnActorPlayerDetach(EntityUid uid, ActorComponent component, DetachPlayerEvent args)
{
// Removing the component will call shutdown, and our subscription will handle the rest of the detach logic.
ComponentManager.RemoveComponent<ActorComponent>(uid);
args.Result = true;
}
private void OnActorShutdown(EntityUid uid, ActorComponent component, ComponentShutdown args)
{
component.PlayerSession.SetAttachedEntity(null);
var entity = EntityManager.GetEntity(uid);
// TODO: Remove component message.
entity.SendMessage(component, new PlayerDetachedMsg(component.PlayerSession));
// The player is fully detached now that the component has shut down.
RaiseLocalEvent(uid, new PlayerDetachedEvent(entity, component.PlayerSession));
}
}
/// <summary>
/// Raise this broadcast event to attach a player to an entity, optionally detaching the player attached to it.
/// </summary>
public class AttachPlayerEvent : EntityEventArgs
{
/// <summary>
/// Player to attach to the entity.
/// Input parameter.
/// </summary>
public IPlayerSession Player { get; }
/// <summary>
/// Entity to attach the player to.
/// Input parameter.
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// Whether to force-attach the player,
/// detaching any players attached to it if any.
/// Input parameter.
/// </summary>
public bool Force { get; }
/// <summary>
/// If the attach was forced and there was a player attached to the entity before, this will be it.
/// Output parameter.
/// </summary>
public IPlayerSession? ForceKicked { get; set; }
/// <summary>
/// Whether the player was attached correctly.
/// False if not forcing and the entity already had a player attached to it.
/// Output parameter.
/// </summary>
public bool Result { get; set; } = false;
public AttachPlayerEvent(IEntity entity, IPlayerSession player, bool force = false)
{
Entity = entity;
Player = player;
Force = force;
}
}
/// <summary>
/// Raise this directed event to detach a player from an entity.
/// </summary>
public class DetachPlayerEvent : EntityEventArgs
{
/// <summary>
/// Whether the player was detached correctly.
/// Fails if no player was attached to the entity.
/// Output parameter.
/// </summary>
public bool Result { get; set; } = false;
}
/// <summary>
/// Event for when a player has been attached to an entity.
/// </summary>
public class PlayerAttachedEvent : EntityEventArgs
{
public IEntity Entity { get; }
public IPlayerSession Player { get; }
public PlayerAttachedEvent(IEntity entity, IPlayerSession player)
{
Entity = entity;
Player = player;
}
}
/// <summary>
/// Event for when a player has been detached from an entity.
/// </summary>
public class PlayerDetachedEvent : EntityEventArgs
{
public IEntity Entity { get; }
public IPlayerSession Player { get; }
public PlayerDetachedEvent(IEntity entity, IPlayerSession player)
{
Entity = entity;
Player = player;
}
}
}

View File

@@ -30,6 +30,8 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
}

View File

@@ -16,6 +16,7 @@ namespace Robust.Server.GameObjects
[UsedImplicitly]
public sealed class GridTileLookupSystem : EntitySystem
{
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly Dictionary<GridId, Dictionary<Vector2i, GridTileLookupChunk>> _graph =
@@ -198,16 +199,16 @@ namespace Robust.Server.GameObjects
private Box2 GetEntityBox(IEntity entity)
{
// Need to clip the aabb as anything with an edge intersecting another tile might be picked up, such as walls.
if (entity.TryGetComponent(out IPhysBody? physics))
return new Box2(physics.GetWorldAABB().BottomLeft + 0.01f, physics.GetWorldAABB().TopRight - 0.01f);
// Don't want to accidentally get neighboring tiles unless we're near an edge
return Box2.CenteredAround(entity.Transform.Coordinates.ToMapPos(EntityManager), Vector2.One / 2);
var aabb = _lookup.GetWorldAabbFromEntity(entity);
return aabb.IsEmpty() ? aabb : aabb.Enlarged(-0.1f);
}
public override void Initialize()
{
base.Initialize();
#if DEBUG
SubscribeNetworkEvent<RequestGridTileLookupMessage>(HandleRequest);
#endif
SubscribeLocalEvent<MoveEvent>(HandleEntityMove);
SubscribeLocalEvent<EntityInitializedMessage>(HandleEntityInitialized);
SubscribeLocalEvent<EntityDeletedMessage>(HandleEntityDeleted);
@@ -219,14 +220,20 @@ namespace Robust.Server.GameObjects
public override void Shutdown()
{
base.Shutdown();
UnsubscribeLocalEvent<MoveEvent>();
UnsubscribeLocalEvent<EntityInitializedMessage>();
UnsubscribeLocalEvent<EntityDeletedMessage>();
_mapManager.OnGridCreated -= HandleGridCreated;
_mapManager.OnGridRemoved -= HandleGridRemoval;
_mapManager.TileChanged -= HandleTileChanged;
}
#if DEBUG
private void HandleRequest(RequestGridTileLookupMessage message, EntitySessionEventArgs args)
{
var entities = GetEntitiesIntersecting(message.GridId, message.Indices).Select(e => e.Uid).ToList();
RaiseNetworkEvent(new SendGridTileLookupMessage(message.GridId, message.Indices, entities), args.SenderSession.ConnectedClient);
}
#endif
private void HandleEntityInitialized(EntityInitializedMessage message)
{
HandleEntityAdd(message.Entity);
@@ -320,7 +327,12 @@ namespace Robust.Server.GameObjects
/// <param name="moveEvent"></param>
private void HandleEntityMove(MoveEvent moveEvent)
{
if (moveEvent.Sender.Deleted || moveEvent.NewPosition.GetGridId(EntityManager) == GridId.Invalid || !moveEvent.NewPosition.IsValid(EntityManager))
// TODO: When Acruid does TileEntities we may actually be able to delete this system if tile lookups become fast enough
var gridId = moveEvent.NewPosition.GetGridId(EntityManager);
if (moveEvent.Sender.Deleted ||
gridId == GridId.Invalid ||
!moveEvent.NewPosition.IsValid(EntityManager))
{
HandleEntityRemove(moveEvent.Sender);
return;
@@ -332,7 +344,7 @@ namespace Robust.Server.GameObjects
}
// Memory leak protection
var gridBounds = _mapManager.GetGrid(moveEvent.Sender.Transform.GridID).WorldBounds;
var gridBounds = _mapManager.GetGrid(gridId).WorldBounds;
if (!gridBounds.Contains(moveEvent.Sender.Transform.WorldPosition))
{
HandleEntityRemove(moveEvent.Sender);

View File

@@ -22,6 +22,31 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(OnMessageReceived);
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
}
private void OnUserInterfaceShutdown(EntityUid uid, ServerUserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.Interfaces)
{
DeactivateInterface(bui);
}
}
internal void SendTo(IPlayerSession session, BoundUIWrapMessage msg)
{
RaiseNetworkEvent(msg, session.ConnectedClient);
}
private void OnMessageReceived(BoundUIWrapMessage msg, EntitySessionEventArgs args)
{
if (!ComponentManager.TryGetComponent<ServerUserInterfaceComponent>(msg.Entity, out var uiComp))
return;
uiComp.ReceiveMessage((IPlayerSession) args.SenderSession, msg);
}
/// <inheritdoc />

View File

@@ -1,68 +1,37 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
namespace Robust.Server.GameObjects
{
public class ServerComponentFactory : ComponentFactory
{
public ServerComponentFactory()
{
// Required for the engine to work
Register<MetaDataComponent>();
RegisterReference<MetaDataComponent, IMetaDataComponent>();
// Required for the engine to work
Register<TransformComponent>();
RegisterReference<TransformComponent, ITransformComponent>();
Register<MapComponent>();
RegisterReference<MapComponent, IMapComponent>();
Register<MapGridComponent>();
RegisterReference<MapGridComponent, IMapGridComponent>();
Register<EyeComponent>();
RegisterReference<EyeComponent, SharedEyeComponent>();
Register<BasicActorComponent>();
RegisterReference<BasicActorComponent, IActorComponent>();
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<OccluderComponent>();
RegisterIgnore("Input");
Register<SpriteComponent>();
RegisterReference<SpriteComponent, SharedSpriteComponent>();
RegisterReference<SpriteComponent, ISpriteRenderableComponent>();
Register<AppearanceComponent>();
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
Register<SnapGridComponent>();
Register<ServerUserInterfaceComponent>();
RegisterReference<ServerUserInterfaceComponent, SharedUserInterfaceComponent>();
Register<TimerComponent>();
RegisterIgnore("AnimationPlayer");
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif
RegisterClass<MetaDataComponent>();
RegisterClass<TransformComponent>();
RegisterClass<MapComponent>();
RegisterClass<MapGridComponent>();
RegisterClass<EyeComponent>();
RegisterClass<ActorComponent>();
RegisterClass<PhysicsComponent>();
RegisterClass<CollisionWakeComponent>();
RegisterClass<ContainerManagerComponent>();
RegisterClass<OccluderComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<SnapGridComponent>();
RegisterClass<ServerUserInterfaceComponent>();
RegisterClass<TimerComponent>();
RegisterClass<MapSaveIdComponent>();
Register<MapSaveIdComponent>();
#if DEBUG
RegisterClass<DebugExceptionOnAddComponent>();
RegisterClass<DebugExceptionInitializeComponent>();
RegisterClass<DebugExceptionStartupComponent>();
#endif
}
}
}

View File

@@ -9,7 +9,6 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
@@ -108,7 +107,7 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public void SetupNetworking()
{
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
@@ -137,6 +136,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component,
ComponentMessage message)
{

View File

@@ -132,11 +132,11 @@ namespace Robust.Server.GameStates
if (session.Status != SessionStatus.InGame || session.AttachedEntityUid is null)
return viewers;
var query = _compMan.EntityQuery<BasicActorComponent>();
var query = _compMan.EntityQuery<ActorComponent>();
foreach (var actorComp in query)
{
if (actorComp.playerSession == session)
if (actorComp.PlayerSession == session)
viewers.Add(actorComp.Owner.Uid);
}

View File

@@ -12,7 +12,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
@@ -63,8 +62,8 @@ namespace Robust.Server.GameStates
/// <inheritdoc />
public void Initialize()
{
_networkManager.RegisterNetMessage<MsgState>(MsgState.NAME);
_networkManager.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME, HandleStateAck);
_networkManager.RegisterNetMessage<MsgState>();
_networkManager.RegisterNetMessage<MsgStateAck>(HandleStateAck);
_networkManager.Connected += HandleClientConnected;
_networkManager.Disconnect += HandleClientDisconnect;
@@ -211,7 +210,10 @@ namespace Robust.Server.GameStates
_logger.Log(LogLevel.Error, e, string.Empty);
}
return (new MsgState(session.ConnectedClient), null);
var msg = _networkManager.CreateNetMessage<MsgState>();
msg.MsgChannel = session.ConnectedClient;
return (msg, null);
}
var mailBag = _playerManager.GetAllPlayers()

View File

@@ -1,13 +1,13 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using System.Collections.Generic;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
@@ -34,7 +34,7 @@ namespace Robust.Server.Placement
public void Initialize()
{
_networkManager.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, HandleNetMessage);
_networkManager.RegisterNetMessage<MsgPlacement>(HandleNetMessage);
}
/// <summary>
@@ -212,7 +212,7 @@ namespace Robust.Server.Placement
foreach (IEntity entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))
{
if (entity.Deleted || entity.HasComponent<IMapGridComponent>() || entity.HasComponent<IActorComponent>())
if (entity.Deleted || entity.HasComponent<IMapGridComponent>() || entity.HasComponent<ActorComponent>())
continue;
entity.Delete();
}
@@ -223,10 +223,10 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBegin(IEntity mob, int range, string objectType, string alignOption)
{
if (!mob.TryGetComponent<IActorComponent>(out var actor))
if (!mob.TryGetComponent<ActorComponent>(out var actor))
return;
var playerConnection = actor.playerSession.ConnectedClient;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
@@ -244,10 +244,10 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBeginTile(IEntity mob, int range, string tileType, string alignOption)
{
if (!mob.TryGetComponent<IActorComponent>(out var actor))
if (!mob.TryGetComponent<ActorComponent>(out var actor))
return;
var playerConnection = actor.playerSession.ConnectedClient;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
@@ -265,10 +265,10 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementCancel(IEntity mob)
{
if (!mob.TryGetComponent<IActorComponent>(out var actor))
if (!mob.TryGetComponent<ActorComponent>(out var actor))
return;
var playerConnection = actor.playerSession.ConnectedClient;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;

View File

@@ -20,8 +20,8 @@ namespace Robust.Server.Player
/// NOTE: The content pack almost certainly has an alternative for this.
/// Do not call this directly for most content code.
/// </summary>
/// <param name="a">The entity to attach to.</param>
void AttachToEntity(IEntity? a);
/// <param name="entity">The entity to attach to.</param>
void AttachToEntity(IEntity? entity);
/// <summary>
/// Detaches this player from an entity.
@@ -36,5 +36,12 @@ namespace Robust.Server.Player
/// Persistent data for this player.
/// </summary>
IPlayerData Data { get; }
/// <summary>
/// Internal method to set <see cref="ICommonSession.AttachedEntity"/> and update the player's status.
/// Do NOT use this unless you know what you're doing, you probably want <see cref="AttachToEntity"/>
/// and <see cref="DetachFromEntity"/> instead.
/// </summary>
internal void SetAttachedEntity(IEntity? entity);
}
}

View File

@@ -10,10 +10,10 @@ namespace Robust.Server.Player
if (entity == null)
return null;
if (!entity.TryGetComponent(out IActorComponent? actorComponent))
if (!entity.TryGetComponent(out ActorComponent? actorComponent))
return null;
return actorComponent.playerSession;
return actorComponent.PlayerSession;
}
}
}

View File

@@ -107,8 +107,8 @@ namespace Robust.Server.Player
MaxPlayers = maxPlayers;
_network.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME, HandlePlayerListReq);
_network.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME);
_network.RegisterNetMessage<MsgPlayerListReq>(HandlePlayerListReq);
_network.RegisterNetMessage<MsgPlayerList>();
_network.Connecting += OnConnecting;
_network.Connected += NewSession;

View File

@@ -6,6 +6,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
using Logger = Robust.Shared.Log.Logger;
namespace Robust.Server.Player
{
@@ -36,7 +37,7 @@ namespace Robust.Server.Player
[ViewVariables] public INetChannel ConnectedClient { get; }
[ViewVariables] public IEntity? AttachedEntity { get; private set; }
[ViewVariables] public IEntity? AttachedEntity { get; set; }
[ViewVariables] public EntityUid? AttachedEntityUid => AttachedEntity?.Uid;
@@ -109,21 +110,21 @@ namespace Robust.Server.Player
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <inheritdoc />
public void AttachToEntity(IEntity? a)
public void AttachToEntity(IEntity? entity)
{
DetachFromEntity();
if (a == null)
{
if (entity == null)
return;
}
var actorComponent = a.AddComponent<BasicActorComponent>();
actorComponent.playerSession = this;
AttachedEntity = a;
a.SendMessage(actorComponent, new PlayerAttachedMsg(this));
a.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSystemMessage(a, this));
UpdatePlayerState();
// This event needs to be broadcast.
var attachPlayer = new AttachPlayerEvent(entity, this);
entity.EntityManager.EventBus.RaiseLocalEvent(entity.Uid, attachPlayer);
if (!attachPlayer.Result)
{
Logger.Warning($"Couldn't attach player \"{this}\" to entity \"{entity}\"! Did it have a player already attached to it?");
}
}
/// <inheritdoc />
@@ -132,22 +133,12 @@ namespace Robust.Server.Player
if (AttachedEntity == null)
return;
if (AttachedEntity.Deleted)
{
throw new InvalidOperationException("Tried to detach player, but my entity does not exist!");
}
var detachPlayer = new DetachPlayerEvent();
AttachedEntity.EntityManager.EventBus.RaiseLocalEvent(AttachedEntity.Uid, detachPlayer, false);
if (AttachedEntity.TryGetComponent<BasicActorComponent>(out var actor))
if (!detachPlayer.Result)
{
AttachedEntity.SendMessage(actor, new PlayerDetachedMsg(this));
AttachedEntity.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerDetachedSystemMessage(AttachedEntity));
AttachedEntity.RemoveComponent<BasicActorComponent>();
AttachedEntity = null;
UpdatePlayerState();
}
else
{
throw new InvalidOperationException("Tried to detach player, but entity does not have ActorComponent!");
Logger.Warning($"Couldn't detach player \"{this}\" to entity \"{AttachedEntity}\"! Is it missing an ActorComponent?");
}
}
@@ -190,6 +181,13 @@ namespace Robust.Server.Player
public LoginType AuthType => ConnectedClient.AuthType;
/// <inheritdoc />
void IPlayerSession.SetAttachedEntity(IEntity? entity)
{
AttachedEntity = entity;
UpdatePlayerState();
}
private void UpdatePlayerState()
{
PlayerState.Status = Status;

View File

@@ -24,7 +24,7 @@ namespace Robust.Server.Prototypes
{
base.Initialize();
_netManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, HandleReloadPrototypes, NetMessageAccept.Server);
_netManager.RegisterNetMessage<MsgReloadPrototypes>(HandleReloadPrototypes, NetMessageAccept.Server);
}
private void HandleReloadPrototypes(MsgReloadPrototypes msg)

View File

@@ -36,11 +36,11 @@ namespace Robust.Server.Scripting
public void Initialize()
{
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME, ReceiveScriptEnd);
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME, ReceiveScriptEval);
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME, ReceiveScriptStart);
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME);
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME);
_netManager.RegisterNetMessage<MsgScriptStop>(ReceiveScriptEnd);
_netManager.RegisterNetMessage<MsgScriptEval>(ReceiveScriptEval);
_netManager.RegisterNetMessage<MsgScriptStart>(ReceiveScriptStart);
_netManager.RegisterNetMessage<MsgScriptResponse>();
_netManager.RegisterNetMessage<MsgScriptStartAck>();
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
}

View File

@@ -46,7 +46,7 @@ namespace Robust.Server
IoCManager.Register<IMapManagerInternal, ServerMapManager>();
IoCManager.Register<IServerMapManager, ServerMapManager>();
IoCManager.Register<IEntityManager, ServerEntityManager>();
IoCManager.Register<IEntityLookup, SharedEntityLookup>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IEntityNetworkManager, ServerEntityManager>();
IoCManager.Register<IServerEntityNetworkManager, ServerEntityManager>();
IoCManager.Register<IMapLoader, MapLoader>();

View File

@@ -13,7 +13,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using DenyReason = Robust.Shared.Network.Messages.MsgViewVariablesDenySession.DenyReason;
using static Robust.Shared.Network.Messages.MsgViewVariablesDenySession;
namespace Robust.Server.ViewVariables
{
@@ -34,16 +34,13 @@ namespace Robust.Server.ViewVariables
public void Initialize()
{
_netManager.RegisterNetMessage<MsgViewVariablesReqSession>(MsgViewVariablesReqSession.NAME,
_msgReqSession);
_netManager.RegisterNetMessage<MsgViewVariablesReqData>(MsgViewVariablesReqData.NAME, _msgReqData);
_netManager.RegisterNetMessage<MsgViewVariablesModifyRemote>(MsgViewVariablesModifyRemote.NAME,
_msgModifyRemote);
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(MsgViewVariablesCloseSession.NAME,
_msgCloseSession);
_netManager.RegisterNetMessage<MsgViewVariablesDenySession>(MsgViewVariablesDenySession.NAME);
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>(MsgViewVariablesOpenSession.NAME);
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>(MsgViewVariablesRemoteData.NAME);
_netManager.RegisterNetMessage<MsgViewVariablesReqSession>(_msgReqSession);
_netManager.RegisterNetMessage<MsgViewVariablesReqData>(_msgReqData);
_netManager.RegisterNetMessage<MsgViewVariablesModifyRemote>(_msgModifyRemote);
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(_msgCloseSession);
_netManager.RegisterNetMessage<MsgViewVariablesDenySession>();
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>();
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>();
}
private void _msgCloseSession(MsgViewVariablesCloseSession message)

View File

@@ -463,5 +463,12 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<int> DebugTargetFps =
CVarDef.Create("debug.target_fps", 60, CVar.CLIENTONLY | CVar.ARCHIVE);
/*
* MIDI
*/
public static readonly CVarDef<float> MidiVolume =
CVarDef.Create("midi.volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
}
}

View File

@@ -82,7 +82,7 @@ namespace Robust.Shared.Configuration
_netManager.Disconnect += PeerDisconnected;
}
_netManager.RegisterNetMessage<MsgConVars>(MsgConVars.NAME, HandleNetVarMessage);
_netManager.RegisterNetMessage<MsgConVars>(HandleNetVarMessage);
}
private void PeerConnected(object? sender, NetChannelArgs e)

View File

@@ -1,3 +1,4 @@
using System;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
@@ -5,6 +6,7 @@ namespace Robust.Shared.Containers
/// <summary>
/// The contents of this container have been changed.
/// </summary>
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class ContainerContentsModifiedMessage : ComponentMessage
{
/// <summary>
@@ -35,4 +37,4 @@ namespace Robust.Shared.Containers
Removed = removed;
}
}
}
}

View File

@@ -197,6 +197,27 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
public static bool IsInSameOrParentContainer(this IEntity user, IEntity other)
{
DebugTools.AssertNotNull(user);
DebugTools.AssertNotNull(other);
var isUserContained = TryGetContainer(user, out var userContainer);
var isOtherContained = TryGetContainer(other, out var otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained) return true;
// One contains the other
if (userContainer?.Owner == other || otherContainer?.Owner == user) return true;
// Both entities are in different contained states
if (isUserContained != isOtherContained) return false;
// Both entities are in the same container
return userContainer == otherContainer;
}
/// <summary>
/// Shortcut method to make creation of containers easier.
/// Creates a new container on the entity and gives it back to you.

View File

@@ -16,8 +16,7 @@ namespace Robust.Shared.Containers
/// <summary>
/// Holds data about a set of entity containers on this entity.
/// </summary>
// [RegisterComponent]
// [ComponentReference(typeof(IContainerManager))]
[ComponentReference(typeof(IContainerManager))]
public class ContainerManagerComponent : Component, IContainerManager
{
[Dependency] private readonly IRobustSerializer _serializer = default!;

View File

@@ -106,6 +106,12 @@ namespace Robust.Shared.ContentPack
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
if (peReader.PEHeaders.CorHeader?.ManagedNativeHeaderDirectory is {Size: not 0})
{
_sawmill.Error($"Assembly {asmName} contains native code.");
return false;
}
if (VerifyIL)
{
if (!DoVerifyIL(asmName, resolver, peReader, reader))
@@ -404,7 +410,8 @@ namespace Robust.Shared.ContentPack
}
}
private bool IsTypeAccessAllowed(SandboxConfig sandboxConfig, MTypeReferenced type, [NotNullWhen(true)] out TypeConfig? cfg)
private bool IsTypeAccessAllowed(SandboxConfig sandboxConfig, MTypeReferenced type,
[NotNullWhen(true)] out TypeConfig? cfg)
{
if (type.Namespace == null)
{

View File

@@ -210,9 +210,11 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public virtual void HandleMessage(ComponentMessage message, IComponent? component) { }
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public virtual void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) { }
/// <param name="player"></param>

View File

@@ -21,6 +21,8 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
public IComponentFactory ComponentFactory => _componentFactory;
private const int TypeCapacity = 32;
private const int ComponentCollectionCapacity = 1024;
private const int EntityCapacity = 1024;

View File

@@ -7,6 +7,7 @@ namespace Robust.Shared.GameObjects
/// A message containing info to send through the component message system.
/// </summary>
[Serializable, NetSerializable]
[Obsolete("Component messages are deprecated. Use directed local events instead.")]
public abstract class ComponentMessage
{
/// <summary>

View File

@@ -1,7 +1,9 @@
using JetBrains.Annotations;
using System;
using JetBrains.Annotations;
namespace Robust.Shared.GameObjects
{
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class RelayMovementEntityMessage : ComponentMessage
{
[PublicAPI]
@@ -16,6 +18,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// The entity transform parent has been changed.
/// </summary>
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class ParentChangedMessage : ComponentMessage
{
/// <summary>

View File

@@ -30,12 +30,6 @@ namespace Robust.Shared.GameObjects
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
}
public interface ICollideSpecial
{
[Obsolete("Use PreventCollideEvent instead")]
bool PreventCollide(IPhysBody collidedwith);
}
[Serializable, NetSerializable]
public enum BodyStatus: byte
{

View File

@@ -24,6 +24,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -98,7 +99,6 @@ namespace Robust.Shared.GameObjects
if (_bodyType == value)
return;
var oldAnchored = _bodyType == BodyType.Static;
var oldType = _bodyType;
_bodyType = value;
@@ -121,14 +121,7 @@ namespace Robust.Shared.GameObjects
RegenerateContacts();
var anchored = value == BodyType.Static;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsBodyTypeChangedEvent(_bodyType, oldType), false);
if (oldAnchored != anchored)
{
SendMessage(new AnchoredChangedMessage(Anchored));
}
}
}
@@ -171,11 +164,11 @@ namespace Robust.Shared.GameObjects
if (value)
{
_sleepTime = 0.0f;
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(this));
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsWakeMessage(this));
}
else
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsSleepMessage(this));
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsSleepMessage(this));
ResetDynamics();
_sleepTime = 0.0f;
}
@@ -269,10 +262,7 @@ namespace Robust.Shared.GameObjects
{
fixture.Body = this;
fixture.ComputeProperties();
if (string.IsNullOrEmpty(fixture.Name))
{
fixture.Name = GetFixtureName(fixture);
}
fixture.ID = GetFixtureName(fixture);
}
ResetMassData();
@@ -312,60 +302,76 @@ namespace Robust.Shared.GameObjects
// We will pray that this deferred joint is handled properly.
// TODO: Crude as FUCK diffing here as well, fine for now.
/*
* -- Joints --
*/
var existingJoints = new List<Joint>();
// TODO: Iterating like this is inefficient and bloated as fuck but on the other hand the linked-list is very convenient
// for bodies with a large number of fixtures / joints.
// Probably store them in Dictionaries but still store the linked-list stuff on the fixture / joint itself.
var existingJoints = Joints.ToList();
var toAddJoints = new List<Joint>();
var toRemoveJoints = new List<Joint>();
for (var je = JointEdges; je != null; je = je.Next)
foreach (var newJoint in newState.Joints)
{
existingJoints.Add(je.Joint);
}
var jointFound = false;
var jointsDiff = true;
if (existingJoints.Count == newState.Joints.Count)
{
var anyDiff = false;
for (var i = 0; i < existingJoints.Count; i++)
foreach (var joint in existingJoints)
{
var existing = existingJoints[i];
var newJoint = newState.Joints[i];
if (!existing.Equals(newJoint))
if (joint.ID.Equals(newJoint.ID))
{
anyDiff = true;
if (!newJoint.Equals(joint) && TrySetupNetworkedJoint(newJoint))
{
toAddJoints.Add(newJoint);
toRemoveJoints.Add(joint);
}
jointFound = true;
break;
}
}
if (!anyDiff)
if (!jointFound && TrySetupNetworkedJoint(newJoint))
{
jointsDiff = false;
toAddJoints.Add(newJoint);
}
}
if (jointsDiff)
foreach (var joint in existingJoints)
{
ClearJoints();
var jointFound = false;
foreach (var joint in newState.Joints)
foreach (var newJoint in newState.Joints)
{
joint.EdgeA = new JointEdge();
joint.EdgeB = new JointEdge();
// Defer joints given it relies on 2 bodies.
AddJoint(joint);
if (joint.ID.Equals(newJoint.ID))
{
jointFound = true;
break;
}
}
if (jointFound) continue;
toRemoveJoints.Add(joint);
}
foreach (var joint in toRemoveJoints)
{
RemoveJoint(joint);
}
foreach (var joint in toAddJoints)
{
AddJoint(joint);
}
/*
* -- Fixtures --
*/
var toAdd = new List<Fixture>();
var toRemove = new List<Fixture>();
var toAddFixtures = new List<Fixture>();
var toRemoveFixtures = new List<Fixture>();
var computeProperties = false;
// Given a bunch of data isn't serialized need to sort of re-initialise it
@@ -385,12 +391,12 @@ namespace Robust.Shared.GameObjects
foreach (var existing in _fixtures)
{
if (!fixture.Name.Equals(existing.Name)) continue;
if (!fixture.ID.Equals(existing.ID)) continue;
if (!fixture.Equals(existing))
{
toAdd.Add(fixture);
toRemove.Add(existing);
toAddFixtures.Add(fixture);
toRemoveFixtures.Add(existing);
}
found = true;
@@ -399,7 +405,7 @@ namespace Robust.Shared.GameObjects
if (!found)
{
toAdd.Add(fixture);
toAddFixtures.Add(fixture);
}
}
@@ -410,7 +416,7 @@ namespace Robust.Shared.GameObjects
foreach (var fixture in newFixtures)
{
if (fixture.Name.Equals(existing.Name))
if (fixture.ID.Equals(existing.ID))
{
found = true;
break;
@@ -419,18 +425,18 @@ namespace Robust.Shared.GameObjects
if (!found)
{
toRemove.Add(existing);
toRemoveFixtures.Add(existing);
}
}
foreach (var fixture in toRemove)
foreach (var fixture in toRemoveFixtures)
{
computeProperties = true;
RemoveFixture(fixture);
}
// TODO: We also still need event listeners for shapes (Probably need C# events)
foreach (var fixture in toAdd)
foreach (var fixture in toAddFixtures)
{
computeProperties = true;
AddFixture(fixture);
@@ -454,6 +460,29 @@ namespace Robust.Shared.GameObjects
Predict = false;
}
private bool TrySetupNetworkedJoint(Joint joint)
{
// This can fail if we've already deleted the entity or remove physics from it I think?
if (!Owner.EntityManager.TryGetEntity(joint.BodyAUid, out var entityA) ||
!entityA.TryGetComponent(out PhysicsComponent? bodyA))
{
return false;
}
if (!Owner.EntityManager.TryGetEntity(joint.BodyBUid, out var entityB) ||
!entityB.TryGetComponent(out PhysicsComponent? bodyB))
{
return false;
}
joint.BodyA = bodyA;
joint.BodyB = bodyB;
joint.EdgeA = new JointEdge();
joint.EdgeB = new JointEdge();
return true;
}
public Fixture? GetFixture(string name)
{
// Sooo I'd rather have fixtures as a list in serialization but there's not really an easy way to have it as a
@@ -463,7 +492,7 @@ namespace Robust.Shared.GameObjects
// If we really need it then you just deserialize onto a dummy field that then just never gets used again.
foreach (var fixture in _fixtures)
{
if (fixture.Name.Equals(name))
if (fixture.ID.Equals(name))
{
return fixture;
}
@@ -485,35 +514,18 @@ namespace Robust.Shared.GameObjects
Dirty();
}
public Box2 GetWorldAABB(IMapManager? mapManager = null)
public Box2 GetWorldAABB(Vector2? worldPosition = null, Angle? worldRotation = null)
{
mapManager ??= IoCManager.Resolve<IMapManager>();
var bounds = new Box2();
worldRotation ??= Owner.Transform.WorldRotation;
worldPosition ??= Owner.Transform.WorldPosition;
var bounds = new Box2(worldPosition.Value, worldPosition.Value);
foreach (var fixture in _fixtures)
{
foreach (var (gridId, proxies) in fixture.Proxies)
{
Vector2 offset;
if (gridId == GridId.Invalid)
{
offset = Vector2.Zero;
}
else
{
offset = mapManager.GetGrid(gridId).WorldPosition;
}
foreach (var proxy in proxies)
{
var shapeBounds = proxy.AABB.Translated(offset);
bounds = bounds.IsEmpty() ? shapeBounds : bounds.Union(shapeBounds);
}
}
bounds = bounds.Union(fixture.Shape.CalculateLocalBounds(worldRotation.Value).Translated(worldPosition.Value));
}
return bounds.IsEmpty() ? Box2.UnitCentered.Translated(Owner.Transform.WorldPosition) : bounds;
return bounds;
}
/// <inheritdoc />
@@ -554,7 +566,6 @@ namespace Robust.Shared.GameObjects
return;
_canCollide = value;
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new CollisionChangeMessage(this, Owner.Uid, _canCollide));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsUpdateMessage(this));
Dirty();
@@ -900,35 +911,6 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Whether or not the entity is anchored in place.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use BodyType.Static instead")]
public bool Anchored
{
get => BodyType == BodyType.Static;
set
{
var anchored = BodyType == BodyType.Static;
if (anchored == value)
return;
if (value)
{
_bodyType = BodyType.Static;
}
else
{
_bodyType = BodyType.Dynamic;
}
SendMessage(new AnchoredChangedMessage(Anchored));
Dirty();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool Predict
{
@@ -1005,12 +987,46 @@ namespace Robust.Shared.GameObjects
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new FixtureUpdateMessage(this, fixture));
}
internal string GetFixtureName(Fixture fixture)
private string GetFixtureName(Fixture fixture)
{
if (!string.IsNullOrEmpty(fixture.ID)) return fixture.ID;
// For any fixtures that aren't named in the code we will assign one.
return $"fixture-{_fixtures.IndexOf(fixture)}";
}
private string GetJointName(Joint joint)
{
var id = joint.ID;
if (!string.IsNullOrEmpty(id)) return id;
var jointCount = Joints.Count();
for (var i = 0; i < jointCount + 1; i++)
{
id = $"joint-{i}";
if (GetJoint(id) != null) continue;
return id;
}
Logger.WarningS("physics", $"Unable to get a joint ID; using its hashcode instead.");
return joint.GetHashCode().ToString();
}
/// <summary>
/// Get a joint with the specified ID.
/// </summary>
public Joint? GetJoint(string id)
{
foreach (var joint in Joints)
{
if (joint.ID == id) return joint;
}
return null;
}
internal Transform GetTransform()
{
return new(Owner.Transform.WorldPosition, (float) Owner.Transform.WorldRotation.Theta);
@@ -1158,15 +1174,10 @@ namespace Robust.Shared.GameObjects
else
{
// TODO: Probably a bad idea but ehh future sloth's problem; namely that we have to duplicate code between here and CanCollide.
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new CollisionChangeMessage(this, Owner.Uid, _canCollide));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsUpdateMessage(this));
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new CollisionChangeMessage(this, Owner.Uid, _canCollide));
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsUpdateMessage(this));
}
}
if (EntitySystem.Get<SharedPhysicsSystem>().Maps.TryGetValue(Owner.Transform.MapID, out var map))
{
PhysicsMap = map;
}
}
public void ClearJoints()
@@ -1218,12 +1229,23 @@ namespace Robust.Shared.GameObjects
public void AddJoint(Joint joint)
{
var id = GetJointName(joint);
foreach (var existing in Joints)
{
// This can happen if a server created joint is sent and applied before the client can create it locally elsewhere
if (existing.ID.Equals(id)) return;
}
PhysicsMap.AddJoint(joint);
joint.ID = id;
Logger.DebugS("physics", $"Added joint id: {joint.ID} type: {joint.GetType().Name} to {Owner}");
}
public void RemoveJoint(Joint joint)
{
PhysicsMap.RemoveJoint(joint);
Logger.DebugS("physics", $"Removed joint id: {joint.ID} type: {joint.GetType().Name} from {Owner}");
}
public override void OnRemove()
@@ -1331,30 +1353,15 @@ namespace Robust.Shared.GameObjects
if (preventCollideMessage.Cancelled) return false;
foreach (var comp in Owner.GetAllComponents<ICollideSpecial>())
{
if (comp.PreventCollide(other)) return false;
}
preventCollideMessage = new PreventCollideEvent(other, this);
Owner.EntityManager.EventBus.RaiseLocalEvent(other.Owner.Uid, preventCollideMessage);
foreach (var comp in other.Owner.GetAllComponents<ICollideSpecial>())
{
if (comp.PreventCollide(this)) return false;
}
if (preventCollideMessage.Cancelled) return false;
return true;
}
}
public class AnchoredChangedMessage : ComponentMessage
{
public readonly bool Anchored;
public AnchoredChangedMessage(bool anchored)
{
Anchored = anchored;
}
}
/// <summary>
/// Directed event raised when an entity's physics BodyType changes.
/// </summary>
@@ -1370,11 +1377,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
public BodyType Old { get; }
/// <summary>
/// Whether the body is "anchored".
/// </summary>
public bool Anchored => New == BodyType.Static;
public PhysicsBodyTypeChangedEvent(BodyType newType, BodyType oldType)
{
New = newType;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Animations;
using Robust.Shared.Map;
@@ -93,9 +94,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
bool IsMapTransform { get; }
/// <summary>
///
/// </summary>
Vector2? LerpDestination { get; }
/// <summary>

View File

@@ -1,6 +1,5 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -18,6 +17,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc cref="IMapComponent"/>
[ComponentReference(typeof(IMapComponent))]
public class MapComponent : Component, IMapComponent
{
[ViewVariables(VVAccess.ReadOnly)]

View File

@@ -21,6 +21,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc cref="IMapGridComponent"/>
[ComponentReference(typeof(IMapGridComponent))]
internal class MapGridComponent : Component, IMapGridComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -51,6 +52,17 @@ namespace Robust.Shared.GameObjects
_gridIndex = GridId.Invalid;
}
public override void Initialize()
{
base.Initialize();
var mapId = Owner.Transform.MapID;
if (_mapManager.HasMapEntity(mapId))
{
Owner.Transform.AttachParent(_mapManager.GetMapEntity(mapId));
}
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)

View File

@@ -65,6 +65,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc cref="IMetaDataComponent"/>
[ComponentReference(typeof(IMetaDataComponent))]
internal class MetaDataComponent : Component, IMetaDataComponent
{
[Dependency] private readonly IPrototypeManager _prototypes = default!;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Animations;
using Robust.Shared.Containers;
@@ -14,6 +15,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[ComponentReference(typeof(ITransformComponent))]
internal class TransformComponent : Component, ITransformComponent, IComponentDebug
{
[DataField("parent")]
@@ -65,25 +67,21 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public GridId GridID
{
get
get => _gridId;
private set
{
// root node, grid id is undefined
if (Owner.HasComponent<IMapComponent>())
return GridId.Invalid;
// second level node, terminates recursion up the branch of the tree
if (Owner.TryGetComponent(out IMapGridComponent? gridComp))
return gridComp.GridIndex;
// branch or leaf node
if (_parent.IsValid())
return Parent!.GridID;
// Not on a grid
return GridId.Invalid;
if (_gridId.Equals(value)) return;
_gridId = value;
foreach (var transformComponent in Children)
{
var child = (TransformComponent) transformComponent;
child.GridID = value;
}
}
}
private GridId _gridId = GridId.Invalid;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool NoLocalRotation
@@ -117,7 +115,7 @@ namespace Robust.Shared.GameObjects
// Set _nextRotation to null to break any active lerps if this is a client side prediction.
_nextRotation = null;
SetRotation(value);
_localRotation = value;
Dirty();
if (!DeferUpdates)
@@ -260,17 +258,51 @@ namespace Robust.Shared.GameObjects
var valid = _parent.IsValid();
return new EntityCoordinates(valid ? _parent : Owner.Uid, valid ? LocalPosition : Vector2.Zero);
}
// NOTE: This setter must be callable from before initialize (inheriting from AttachParent's note)
set
{
var oldPosition = Coordinates;
_localPosition = value.Position;
var changedParent = false;
if (value.EntityId != _parent)
{
var newEntity = Owner.EntityManager.GetEntity(value.EntityId);
AttachParent(newEntity);
changedParent = true;
var newParentEnt = Owner.EntityManager.GetEntity(value.EntityId);
var newParent = newParentEnt.Transform;
DebugTools.Assert(newParent != this,
$"Can't parent a {nameof(ITransformComponent)} to itself.");
// That's already our parent, don't bother attaching again.
var oldParent = Parent;
var oldConcrete = (TransformComponent?) oldParent;
var uid = Owner.Uid;
oldConcrete?._children.Remove(uid);
var newConcrete = (TransformComponent) newParent;
newConcrete._children.Add(uid);
var oldParentOwner = oldParent?.Owner;
var entMessage = new EntParentChangedMessage(Owner, oldParentOwner);
var compMessage = new ParentChangedMessage(newParentEnt, oldParentOwner);
// offset position from world to parent
_parent = newParentEnt.Uid;
ChangeMapId(newConcrete.MapID);
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, entMessage);
Owner.SendMessage(this, compMessage);
GridID = GetGridIndex();
}
_localPosition = value.Position;
// These conditions roughly emulate the effects of the code before I changed things,
// in regards to when to rebuild matrices.
// This may not in fact be the right thing.
if (changedParent || !DeferUpdates)
RebuildMatrices();
Dirty();
if (!DeferUpdates)
@@ -278,7 +310,6 @@ namespace Robust.Shared.GameObjects
//TODO: This is a hack, look into WHY we can't call GridPosition before the comp is Running
if (Running)
{
RebuildMatrices();
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new MoveEvent(Owner, oldPosition, Coordinates));
}
}
@@ -306,7 +337,7 @@ namespace Robust.Shared.GameObjects
_nextPosition = null;
var oldGridPos = Coordinates;
SetPosition(value);
_localPosition = value;
Dirty();
if (!DeferUpdates)
@@ -424,6 +455,28 @@ namespace Robust.Shared.GameObjects
// so duplicate additions (which will happen) don't matter.
((TransformComponent) Parent!)._children.Add(Owner.Uid);
}
GridID = GetGridIndex();
}
private GridId GetGridIndex()
{
if (Owner.HasComponent<IMapComponent>())
{
return GridId.Invalid;
}
if (Owner.TryGetComponent(out IMapGridComponent? gridComponent))
{
return gridComponent.GridIndex;
}
if (_parent.IsValid())
{
return Parent!.GridID;
}
return _mapManager.TryFindGridAt(MapID, WorldPosition, out var mapgrid) ? mapgrid.Index : GridId.Invalid;
}
/// <inheritdoc />
@@ -536,49 +589,22 @@ namespace Robust.Shared.GameObjects
}
/// <summary>
/// Sets another entity as the parent entity.
/// Sets another entity as the parent entity, maintaining world position.
/// </summary>
/// <param name="newParent"></param>
public virtual void AttachParent(ITransformComponent newParent)
{
//NOTE: This function must be callable from before initialize
// nothing to attach to.
// don't attach to something we're already attached to
if (ParentUid == newParent.Owner.Uid)
return;
DebugTools.Assert(newParent != this,
$"Can't parent a {nameof(ITransformComponent)} to itself.");
// That's already our parent, don't bother attaching again.
var newParentEnt = newParent.Owner;
if (newParentEnt.Uid == _parent)
{
return;
}
var oldParent = Parent;
var oldConcrete = (TransformComponent?) oldParent;
var uid = Owner.Uid;
oldConcrete?._children.Remove(uid);
var newConcrete = (TransformComponent) newParent;
newConcrete._children.Add(uid);
var oldParentOwner = oldParent?.Owner;
var entMessage = new EntParentChangedMessage(Owner, oldParentOwner);
var compMessage = new ParentChangedMessage(newParentEnt, oldParentOwner);
// offset position from world to parent
SetPosition(newParent.InvWorldMatrix.Transform(WorldPosition));
_parent = newParentEnt.Uid;
ChangeMapId(newConcrete.MapID);
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, entMessage);
Owner.SendMessage(this, compMessage);
RebuildMatrices();
Dirty();
// offset position from world to parent, and set
Coordinates = new EntityCoordinates(newParent.Owner.Uid, newParent.InvWorldMatrix.Transform(WorldPosition));
}
internal void ChangeMapId(MapId newMapId)
@@ -693,14 +719,14 @@ namespace Robust.Shared.GameObjects
if (LocalRotation != newState.Rotation)
{
SetRotation(newState.Rotation);
_localRotation = newState.Rotation;
rebuildMatrices = true;
}
if (!_localPosition.EqualsApprox(newState.LocalPosition, 0.0001))
{
var oldPos = Coordinates;
SetPosition(newState.LocalPosition);
_localPosition = newState.LocalPosition;
var ev = new MoveEvent(Owner, oldPos, Coordinates);
EntitySystem.Get<SharedTransformSystem>().DeferMoveEvent(ev);
@@ -735,17 +761,6 @@ namespace Robust.Shared.GameObjects
}
}
protected virtual void SetPosition(Vector2 position)
{
// DebugTools.Assert(!float.IsNaN(position.X) && !float.IsNaN(position.Y));
_localPosition = position;
}
protected virtual void SetRotation(Angle rotation)
{
_localRotation = rotation;
}
public Matrix3 GetLocalMatrix()
{
return _localMatrix;

View File

@@ -35,25 +35,6 @@ namespace Robust.Shared.GameObjects
UiKey = _uiKeyRaw;
}
}
[NetSerializable, Serializable]
protected sealed class BoundInterfaceMessageWrapMessage : ComponentMessage
{
public readonly BoundUserInterfaceMessage Message;
public readonly object UiKey;
public BoundInterfaceMessageWrapMessage(BoundUserInterfaceMessage message, object uiKey)
{
Directed = true;
Message = message;
UiKey = uiKey;
}
public override string ToString()
{
return $"{nameof(BoundInterfaceMessageWrapMessage)}: {Message}";
}
}
}
[NetSerializable, Serializable]
@@ -87,4 +68,24 @@ namespace Robust.Shared.GameObjects
internal sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
{
}
[Serializable, NetSerializable]
internal sealed class BoundUIWrapMessage : EntityEventArgs
{
public readonly EntityUid Entity;
public readonly BoundUserInterfaceMessage Message;
public readonly object UiKey;
public BoundUIWrapMessage(EntityUid entity, BoundUserInterfaceMessage message, object uiKey)
{
Message = message;
UiKey = uiKey;
Entity = entity;
}
public override string ToString()
{
return $"{nameof(BoundUIWrapMessage)}: {Message}";
}
}
}

View File

@@ -286,6 +286,12 @@ namespace Robust.Shared.GameObjects
return TryGetComponent(type, out var component) ? component : null;
}
/// <inheritdoc />
public void QueueDelete()
{
EntityManager.QueueDeleteEntity(this);
}
/// <inheritdoc />
public void Delete()
{

View File

@@ -99,6 +99,7 @@ namespace Robust.Shared.GameObjects
private class EventTables : IDisposable
{
private IEntityManager _entMan;
private IComponentFactory _comFac;
// eUid -> EventType -> { CompType1, ... CompTypeN }
private Dictionary<EntityUid, Dictionary<Type, HashSet<Type>>> _eventTables;
@@ -112,6 +113,7 @@ namespace Robust.Shared.GameObjects
public EventTables(IEntityManager entMan)
{
_entMan = entMan;
_comFac = entMan.ComponentManager.ComponentFactory;
_entMan.EntityAdded += OnEntityAdded;
_entMan.EntityDeleted += OnEntityDeleted;
@@ -194,18 +196,21 @@ namespace Robust.Shared.GameObjects
{
var eventTable = _eventTables[euid];
if (!_subscriptions.TryGetValue(compType, out var compSubs))
return;
foreach (var kvSub in compSubs)
foreach (var type in GetReferences(compType))
{
if(!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
{
subscribedComps = new HashSet<Type>();
eventTable.Add(kvSub.Key, subscribedComps);
}
if (!_subscriptions.TryGetValue(type, out var compSubs))
continue;
subscribedComps.Add(compType);
foreach (var kvSub in compSubs)
{
if(!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
{
subscribedComps = new HashSet<Type>();
eventTable.Add(kvSub.Key, subscribedComps);
}
subscribedComps.Add(type);
}
}
}
@@ -213,15 +218,18 @@ namespace Robust.Shared.GameObjects
{
var eventTable = _eventTables[euid];
if (!_subscriptions.TryGetValue(compType, out var compSubs))
return;
foreach (var kvSub in compSubs)
foreach (var type in GetReferences(compType))
{
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
return;
if (!_subscriptions.TryGetValue(type, out var compSubs))
continue;
subscribedComps.Remove(compType);
foreach (var kvSub in compSubs)
{
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
return;
subscribedComps.Remove(type);
}
}
}
@@ -241,19 +249,23 @@ namespace Robust.Shared.GameObjects
return;
var component = _entMan.ComponentManager.GetComponent(euid, compType);
handler(euid, component, args);
}
}
public void DispatchComponent(EntityUid euid, IComponent component, Type eventType, EntityEventArgs args)
{
if (!_subscriptions.TryGetValue(component.GetType(), out var compSubs))
return;
foreach (var type in GetReferences(component.GetType()))
{
if (!_subscriptions.TryGetValue(type, out var compSubs))
continue;
if (!compSubs.TryGetValue(eventType, out var handler))
return;
if (!compSubs.TryGetValue(eventType, out var handler))
continue;
handler(euid, component, args);
handler(euid, component, args);
}
}
public void ClearEntities()
@@ -281,6 +293,11 @@ namespace Robust.Shared.GameObjects
_eventTables = null!;
_subscriptions = null!;
}
private IEnumerable<Type> GetReferences(Type type)
{
return _comFac.GetRegistration(type).References;
}
}
/// <inheritdoc />

View File

@@ -44,6 +44,8 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public virtual IEntityNetworkManager? EntityNetManager => null;
protected readonly HashSet<EntityUid> QueuedDeletions = new();
/// <summary>
/// All entities currently stored in the manager.
/// </summary>
@@ -112,6 +114,16 @@ namespace Robust.Shared.GameObjects
_eventBus.ProcessEventQueue();
}
using (histogram?.WithLabels("QueuedDeletion").NewTimer())
{
foreach (var uid in QueuedDeletions)
{
DeleteEntity(uid);
}
QueuedDeletions.Clear();
}
using (histogram?.WithLabels("ComponentCull").NewTimer())
{
_componentManager.CullRemovedComponents();
@@ -212,6 +224,7 @@ namespace Robust.Shared.GameObjects
return false;
}
[Obsolete("IEntityQuery is obsolete")]
public IEnumerable<IEntity> GetEntities(IEntityQuery query)
{
return query.Match(this);
@@ -287,6 +300,16 @@ namespace Robust.Shared.GameObjects
EventBus.RaiseEvent(EventSource.Local, new EntityDeletedMessage(entity));
}
public void QueueDeleteEntity(IEntity entity)
{
QueueDeleteEntity(entity.Uid);
}
public void QueueDeleteEntity(EntityUid uid)
{
QueuedDeletions.Add(uid);
}
public void DeleteEntity(EntityUid uid)
{
if (TryGetEntity(uid, out var entity))
@@ -359,7 +382,7 @@ namespace Robust.Shared.GameObjects
// we want this called before adding components
EntityAdded?.Invoke(this, entity.Uid);
// We do this after the event, so if the event throws we have not committed
// We do this after the event, so if the event throws we have not committed
Entities[entity.Uid] = entity;
AllEntities.Add(entity);
@@ -457,6 +480,7 @@ namespace Robust.Shared.GameObjects
var session = netMsg.Session;
compMsg.Remote = true;
#pragma warning disable 618
var uid = netMsg.EntityUid;
if (compMsg.Directed)
{
@@ -470,6 +494,7 @@ namespace Robust.Shared.GameObjects
component.HandleNetworkMessage(compMsg, compChannel, session);
}
}
#pragma warning restore 618
}
/// <summary>

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
namespace Robust.Shared.GameObjects
{
public abstract partial class EntitySystem
{
private List<SubBase>? _subscriptions;
protected void SubscribeNetworkEvent<T>(EntityEventHandler<T> handler)
where T : notnull
{
EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, handler);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<T>(EventSource.Network));
}
protected void SubscribeNetworkEvent<T>(EntitySessionEventHandler<T> handler)
where T : notnull
{
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Network, this, handler);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Network));
}
protected void SubscribeLocalEvent<T>(EntityEventHandler<T> handler)
where T : notnull
{
EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, handler);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<T>(EventSource.Local));
}
protected void SubscribeLocalEvent<T>(EntitySessionEventHandler<T> handler)
where T : notnull
{
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Local, this, handler);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Local));
}
[Obsolete("Unsubscribing of entity system events is now automatic")]
protected void UnsubscribeNetworkEvent<T>()
where T : notnull
{
EntityManager.EventBus.UnsubscribeEvent<T>(EventSource.Network, this);
}
[Obsolete("Unsubscribing of entity system events is now automatic")]
protected void UnsubscribeLocalEvent<T>()
where T : notnull
{
EntityManager.EventBus.UnsubscribeEvent<T>(EventSource.Local, this);
}
protected void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs
{
EntityManager.EventBus.SubscribeLocalEvent(handler);
_subscriptions ??= new();
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
[Obsolete("Unsubscribing of entity system events is now automatic")]
protected void UnsubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs
{
EntityManager.EventBus.UnsubscribeLocalEvent<TComp, TEvent>();
}
[Obsolete("Unsubscribing of entity system events is now automatic")]
protected void UnsubscribeLocalEvent<TComp, TEvent>()
where TComp : IComponent
where TEvent : EntityEventArgs
{
EntityManager.EventBus.UnsubscribeLocalEvent<TComp, TEvent>();
}
private void ShutdownSubscriptions()
{
if (_subscriptions == null)
return;
foreach (var sub in _subscriptions)
{
sub.Unsubscribe(this, EntityManager.EventBus);
}
_subscriptions = null;
}
private abstract class SubBase
{
public abstract void Unsubscribe(EntitySystem sys, IEventBus bus);
}
private sealed class SubBroadcast<T> : SubBase where T : notnull
{
private readonly EventSource _source;
public SubBroadcast(EventSource source)
{
_source = source;
}
public override void Unsubscribe(EntitySystem sys, IEventBus bus)
{
bus.UnsubscribeEvent<T>(_source, sys);
}
}
private sealed class SubLocal<TComp, TBase> : SubBase where TComp : IComponent where TBase : EntityEventArgs
{
public override void Unsubscribe(EntitySystem sys, IEventBus bus)
{
bus.UnsubscribeLocalEvent<TComp, TBase>();
}
}
}
}

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