diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 99dc929e41..d6dc02d690 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,7 +28,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.101 + dotnet-version: 8.0.100 - name: Install dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/compiler-test.yml b/.github/workflows/compiler-test.yml index 329007ebf9..95e70668c1 100644 --- a/.github/workflows/compiler-test.yml +++ b/.github/workflows/compiler-test.yml @@ -27,7 +27,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.101 + dotnet-version: 8.0.100 - name: Install dependencies run: dotnet restore main/OpenDream.sln - name: Build diff --git a/.github/workflows/test-tgs.yml b/.github/workflows/test-tgs.yml index a901f1dc02..a6a34ad7b4 100644 --- a/.github/workflows/test-tgs.yml +++ b/.github/workflows/test-tgs.yml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true env: - OD_DOTNET_VERSION: 7 + OD_DOTNET_VERSION: 8 TGS_DOTNET_VERSION: 8 TGS_REFERENCE: dev diff --git a/Content.IntegrationTests/Content.IntegrationTests.csproj b/Content.IntegrationTests/Content.IntegrationTests.csproj index 78418cb1b2..71b32f8827 100644 --- a/Content.IntegrationTests/Content.IntegrationTests.csproj +++ b/Content.IntegrationTests/Content.IntegrationTests.csproj @@ -5,7 +5,7 @@ ..\bin\Content.IntegrationTests\ false false - 11 + 12 diff --git a/Content.Tests/Content.Tests.csproj b/Content.Tests/Content.Tests.csproj index 9b757865e1..719446e02d 100644 --- a/Content.Tests/Content.Tests.csproj +++ b/Content.Tests/Content.Tests.csproj @@ -2,7 +2,7 @@ $(TargetFramework) - 11 + 12 false false ..\bin\Content.Tests\ diff --git a/DMCompiler/DMCompiler.csproj b/DMCompiler/DMCompiler.csproj index c533fdee4f..53b82d0b6c 100644 --- a/DMCompiler/DMCompiler.csproj +++ b/DMCompiler/DMCompiler.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable Debug;Release;Tools AnyCPU diff --git a/DMCompiler/copy_standard.bat b/DMCompiler/copy_standard.bat index d57f678a2a..d53a132c2d 100644 --- a/DMCompiler/copy_standard.bat +++ b/DMCompiler/copy_standard.bat @@ -1,5 +1,5 @@ @echo off -if not exist bin\Debug\net7.0\DMStandard mkdir bin\Debug\net7.0\DMStandard -xcopy DMStandard bin\Debug\net7.0\DMStandard /y /s /e -if not exist bin\Release\net7.0\DMStandard mkdir bin\Release\net7.0\DMStandard -xcopy DMStandard bin\Release\net7.0\DMStandard /y /s /e +if not exist bin\Debug\net8.0\DMStandard mkdir bin\Debug\net8.0\DMStandard +xcopy DMStandard bin\Debug\net8.0\DMStandard /y /s /e +if not exist bin\Release\net8.0\DMStandard mkdir bin\Release\net8.0\DMStandard +xcopy DMStandard bin\Release\net8.0\DMStandard /y /s /e diff --git a/DMCompiler/copy_standard.sh b/DMCompiler/copy_standard.sh index d1f8bee8ef..3532579229 100755 --- a/DMCompiler/copy_standard.sh +++ b/DMCompiler/copy_standard.sh @@ -4,16 +4,16 @@ set -xe SCRIPT=$(readlink -f "$0") SCRIPTPATH=$(dirname "$SCRIPT") -if [ -d "$SCRIPTPATH/bin/Debug/net7.0/DMStandard" ]; then - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Debug/net7.0/DMStandard +if [ -d "$SCRIPTPATH/bin/Debug/net8.0/DMStandard" ]; then + cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Debug/net8.0/DMStandard else - mkdir -p $SCRIPTPATH/bin/Debug/net7.0/DMStandard - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Debug/net7.0/DMStandard + mkdir -p $SCRIPTPATH/bin/Debug/net8.0/DMStandard + cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Debug/net8.0/DMStandard fi -if [ -d "$SCRIPTPATH/bin/Release/net7.0/DMStandard" ]; then - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Release/net7.0/DMStandard +if [ -d "$SCRIPTPATH/bin/Release/net8.0/DMStandard" ]; then + cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Release/net8.0/DMStandard else - mkdir -p $SCRIPTPATH/bin/Release/net7.0/DMStandard - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Release/net7.0/DMStandard + mkdir -p $SCRIPTPATH/bin/Release/net8.0/DMStandard + cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Release/net8.0/DMStandard fi diff --git a/DMDisassembler/DMDisassembler.csproj b/DMDisassembler/DMDisassembler.csproj index 231be680b8..e116fcf4e6 100644 --- a/DMDisassembler/DMDisassembler.csproj +++ b/DMDisassembler/DMDisassembler.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 Debug;Release;Tools AnyCPU diff --git a/OpenDreamClient/Audio/DreamSoundChannel.cs b/OpenDreamClient/Audio/DreamSoundChannel.cs index 45ab8a4ec9..525882e14c 100644 --- a/OpenDreamClient/Audio/DreamSoundChannel.cs +++ b/OpenDreamClient/Audio/DreamSoundChannel.cs @@ -1,20 +1,12 @@ -using Robust.Client.Graphics; +using Robust.Client.Audio; +using Robust.Shared.Audio.Components; namespace OpenDreamClient.Audio; -public sealed class DreamSoundChannel : IDisposable { - public IClydeAudioSource Source { get; } - - public DreamSoundChannel(IClydeAudioSource source) { - Source = source; - } +public sealed class DreamSoundChannel(AudioSystem audioSystem, (EntityUid Entity, AudioComponent Component) source) { + public readonly (EntityUid Entity, AudioComponent Component) Source = source; public void Stop() { - Source.StopPlaying(); - } - - public void Dispose() { - Stop(); - Source.Dispose(); + audioSystem.Stop(Source.Entity, Source.Component); } } diff --git a/OpenDreamClient/Audio/DreamSoundEngine.cs b/OpenDreamClient/Audio/DreamSoundEngine.cs index 703bd8ca35..8665d3f751 100644 --- a/OpenDreamClient/Audio/DreamSoundEngine.cs +++ b/OpenDreamClient/Audio/DreamSoundEngine.cs @@ -1,89 +1,103 @@ using OpenDreamClient.Resources; using OpenDreamClient.Resources.ResourceTypes; using OpenDreamShared.Network.Messages; -using Robust.Client.Graphics; +using Robust.Client.Audio; using Robust.Shared.Audio; using Robust.Shared.Network; -namespace OpenDreamClient.Audio { - public sealed class DreamSoundEngine : IDreamSoundEngine { - private const int SoundChannelLimit = 1024; +namespace OpenDreamClient.Audio; - [Dependency] private readonly IDreamResourceManager _resourceManager = default!; - [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly INetManager _netManager = default!; +public sealed class DreamSoundEngine : IDreamSoundEngine { + private const int SoundChannelLimit = 1024; - private ISawmill _sawmill = default!; + [Dependency] private readonly IDreamResourceManager _resourceManager = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IAudioManager _audioManager = default!; + private AudioSystem? _audioSystem; - private readonly DreamSoundChannel?[] _channels = new DreamSoundChannel[SoundChannelLimit]; + private ISawmill _sawmill = default!; - public void Initialize() { - _sawmill = _logManager.GetSawmill("opendream.audio"); + private readonly DreamSoundChannel?[] _channels = new DreamSoundChannel[SoundChannelLimit]; - _netManager.RegisterNetMessage(RxSound); + public void Initialize() { + _sawmill = _logManager.GetSawmill("opendream.audio"); - _netManager.Disconnect += DisconnectedFromServer; - } + _netManager.RegisterNetMessage(RxSound); - public void StopFinishedChannels() { - for (int i = 0; i < SoundChannelLimit; i++) { - if (_channels[i]?.Source.IsPlaying is false or null) - StopChannel(i + 1); - } + _netManager.Disconnect += DisconnectedFromServer; + } + + public void StopFinishedChannels() { + for (int i = 0; i < SoundChannelLimit; i++) { + if (_channels[i]?.Source.Component.Playing is false or null) + StopChannel(i + 1); } + } - public void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sound, float volume) { - if (channel == 0) { - //First available channel - for (int i = 0; i < _channels.Length; i++) { - if (_channels[i] == null) { - channel = i + 1; - break; - } - } + public void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sound, float volume) { + if (_audioSystem == null) + _entitySystemManager.Resolve(ref _audioSystem); - if (channel == 0) { - _sawmill.Error("Failed to find a free audio channel to play a sound on"); - return; + if (channel == 0) { + //First available channel + for (int i = 0; i < _channels.Length; i++) { + if (_channels[i] == null) { + channel = i + 1; + break; } } - StopChannel(channel); - - // convert from DM volume (0-100) to OpenAL volume (db) - IClydeAudioSource? source = sound.Play(format, AudioParams.Default.WithVolume(20 * MathF.Log10(volume))); - if (source == null) + if (channel == 0) { + _sawmill.Error("Failed to find a free audio channel to play a sound on"); return; - - _channels[channel - 1] = new DreamSoundChannel(source); + } } + StopChannel(channel); - public void StopChannel(int channel) { - ref DreamSoundChannel? ch = ref _channels[channel - 1]; - - ch?.Dispose(); - // This will null the corresponding index in the array. - ch = null; + var stream = sound.GetStream(format, _audioManager); + if (stream == null) { + _sawmill.Error($"Failed to load audio ${sound}"); + return; } - public void StopAllChannels() { - for (int i = 0; i < SoundChannelLimit; i++) { - StopChannel(i + 1); - } + var db = 20 * MathF.Log10(volume); // convert from DM volume (0-100) to OpenAL volume (db) + var source = _audioSystem.PlayGlobal(stream, AudioParams.Default.WithVolume(db)); // TODO: Positional audio. + if (source == null) { + _sawmill.Error($"Failed to play audio ${sound}"); + return; } - private void RxSound(MsgSound msg) { - if (msg.ResourceId.HasValue) { - _resourceManager.LoadResourceAsync(msg.ResourceId.Value, - sound => PlaySound(msg.Channel, msg.Format!.Value, sound, msg.Volume / 100.0f)); - } else { - StopChannel(msg.Channel); - } + _channels[channel - 1] = new DreamSoundChannel(_audioSystem, source.Value); + } + + + public void StopChannel(int channel) { + ref DreamSoundChannel? ch = ref _channels[channel - 1]; + + ch?.Stop(); + // This will null the corresponding index in the array. + ch = null; + } + + public void StopAllChannels() { + for (int i = 0; i < SoundChannelLimit; i++) { + StopChannel(i + 1); } + } - private void DisconnectedFromServer(object? sender, NetDisconnectedArgs e) { - StopAllChannels(); + private void RxSound(MsgSound msg) { + if (msg.ResourceId.HasValue) { + _resourceManager.LoadResourceAsync(msg.ResourceId.Value, + sound => PlaySound(msg.Channel, msg.Format!.Value, sound, msg.Volume / 100.0f)); + } else { + StopChannel(msg.Channel); } } + + private void DisconnectedFromServer(object? sender, NetDisconnectedArgs e) { + StopAllChannels(); + } } diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index c6de6edfd6..029c7ca44b 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -2,7 +2,7 @@ using OpenDreamClient.Rendering; using Robust.Client.GameObjects; using Robust.Client.Graphics; -using Robust.Client.Player; +using Robust.Shared.Player; namespace OpenDreamClient; diff --git a/OpenDreamClient/OpenDreamClient.csproj b/OpenDreamClient/OpenDreamClient.csproj index b741bea1c5..e06241556c 100644 --- a/OpenDreamClient/OpenDreamClient.csproj +++ b/OpenDreamClient/OpenDreamClient.csproj @@ -2,7 +2,7 @@ $(TargetFramework) - 11 + 12 false false ..\bin\Content.Client\ diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index ab2f4a514b..e24977df9a 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -68,6 +68,7 @@ internal sealed class DreamViewOverlay : Overlay { // Defined here so it isn't recreated every frame private ViewAlgorithm.Tile?[,]? _tileInfo; + private readonly HashSet _entities = new(); public DreamViewOverlay(TransformSystem transformSystem, EntityLookupSystem lookupSystem, ClientAppearanceSystem appearanceSystem, ClientScreenOverlaySystem screenOverlaySystem, ClientImagesSystem clientImagesSystem) { @@ -140,19 +141,18 @@ private void DrawAll(OverlayDrawArgs args, EntityUid eye, Vector2i viewportSize) var worldHandle = args.WorldHandle; - HashSet entities; using (_prof.Group("lookup")) { //TODO use a sprite tree. //the scaling is to attempt to prevent pop-in, by rendering sprites that are *just* offscreen - entities = _lookupSystem.GetEntitiesIntersecting(args.MapId, args.WorldAABB.Scale(1.2f), MapLookupFlags); + _lookupSystem.GetEntitiesIntersecting(args.MapId, args.WorldAABB.Scale(1.2f), _entities, MapLookupFlags); } var eyeTile = grid.GetTileRef(eyeTransform.MapPosition); - var tiles = CalculateTileVisibility(grid, entities, eyeTile, seeVis); + var tiles = CalculateTileVisibility(grid, _entities, eyeTile, seeVis); RefreshRenderTargets(args.WorldHandle, viewportSize); - CollectVisibleSprites(tiles, grid, eyeTile, entities, seeVis, sight, args.WorldAABB); + CollectVisibleSprites(tiles, grid, eyeTile, _entities, seeVis, sight, args.WorldAABB); ClearPlanes(); ProcessSprites(worldHandle, viewportSize, args.WorldAABB); diff --git a/OpenDreamClient/Resources/ResourceTypes/ResourceSound.cs b/OpenDreamClient/Resources/ResourceTypes/ResourceSound.cs index c8d36fe471..eb34c92da1 100644 --- a/OpenDreamClient/Resources/ResourceTypes/ResourceSound.cs +++ b/OpenDreamClient/Resources/ResourceTypes/ResourceSound.cs @@ -2,52 +2,28 @@ using JetBrains.Annotations; using OpenDreamShared.Network.Messages; using Robust.Client.Audio; -using Robust.Client.Graphics; -using Robust.Shared.Audio; -namespace OpenDreamClient.Resources.ResourceTypes { - [UsedImplicitly] - public sealed class ResourceSound : DreamResource { - private AudioStream? _stream; +namespace OpenDreamClient.Resources.ResourceTypes; - public ResourceSound(int id, byte[] data) : base(id, data) { } - - public IClydeAudioSource? Play(MsgSound.FormatType format, AudioParams audioParams) { - LoadStream(format); - if (_stream == null) - return null; - - // TODO: Positional audio. - var source = IoCManager.Resolve().CreateAudioSource(_stream); - - if (source != null) { - source.SetGlobal(); - source.SetPitch(audioParams.PitchScale); - source.SetVolume(audioParams.Volume); - source.SetPlaybackPosition(audioParams.PlayOffsetSeconds); - source.IsLooping = audioParams.Loop; - - source.StartPlaying(); - } - - return source; - } - - private void LoadStream(MsgSound.FormatType format) { - if (_stream != null) - return; +[UsedImplicitly] +public sealed class ResourceSound(int id, byte[] data) : DreamResource(id, data) { + private AudioStream? _stream; + public AudioStream? GetStream(MsgSound.FormatType format, IAudioManager audioManager) { + if (_stream == null) { switch (format) { case MsgSound.FormatType.Ogg: - _stream = IoCManager.Resolve().LoadAudioOggVorbis(new MemoryStream(Data)); + _stream = audioManager.LoadAudioOggVorbis(new MemoryStream(Data)); break; case MsgSound.FormatType.Wav: - _stream = IoCManager.Resolve().LoadAudioWav(new MemoryStream(Data)); + _stream = audioManager.LoadAudioWav(new MemoryStream(Data)); break; default: Logger.GetSawmill("opendream.audio").Fatal("Only *.ogg and *.wav audio files are supported."); break; } } + + return _stream; } } diff --git a/OpenDreamPackageTool/OpenDreamPackageTool.csproj b/OpenDreamPackageTool/OpenDreamPackageTool.csproj index 469182ada8..ac86916b7a 100644 --- a/OpenDreamPackageTool/OpenDreamPackageTool.csproj +++ b/OpenDreamPackageTool/OpenDreamPackageTool.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/OpenDreamPackaging/DreamPackaging.cs b/OpenDreamPackaging/DreamPackaging.cs index b26dddae96..13004e1e97 100644 --- a/OpenDreamPackaging/DreamPackaging.cs +++ b/OpenDreamPackaging/DreamPackaging.cs @@ -19,11 +19,9 @@ public static async Task WriteResources( var inputPass = graph.Input; - await RobustClientPackaging.WriteContentAssemblies( - inputPass, + await RobustClientPackaging.WriteClientResources( contentDir, - "Content.Client", - new[] { "OpenDreamClient", "OpenDreamShared" }, + inputPass, cancel); await RobustClientPackaging.WriteClientResources(contentDir, inputPass, cancel); diff --git a/OpenDreamPackaging/OpenDreamPackaging.csproj b/OpenDreamPackaging/OpenDreamPackaging.csproj index 5797b27310..34748e8ac1 100644 --- a/OpenDreamPackaging/OpenDreamPackaging.csproj +++ b/OpenDreamPackaging/OpenDreamPackaging.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index a750c9da83..8dcf8a6271 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -8,579 +8,572 @@ using OpenDreamShared.Dream; using OpenDreamShared.Dream.Procs; using OpenDreamShared.Network.Messages; -using Robust.Server.GameObjects; using Robust.Shared.Enums; using Robust.Shared.Player; -namespace OpenDreamRuntime { - public sealed class DreamConnection { - [Dependency] private readonly DreamManager _dreamManager = default!; - [Dependency] private readonly DreamObjectTree _objectTree = default!; - [Dependency] private readonly DreamResourceManager _resourceManager = default!; - [Dependency] private readonly WalkManager _walkManager = default!; - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - private readonly ServerScreenOverlaySystem? _screenOverlaySystem; - private readonly ServerClientImagesSystem? _clientImagesSystem; - private readonly ActorSystem? _actorSystem; - - [ViewVariables] private readonly Dictionary _availableVerbs = new(); - [ViewVariables] private readonly Dictionary> _statPanels = new(); - [ViewVariables] private bool _currentlyUpdatingStat; - - [ViewVariables] public ICommonSession? Session { get; private set; } - [ViewVariables] public DreamObjectClient? Client { get; private set; } - [ViewVariables] public DreamObjectMob? Mob { - get => _mob; - set { - if (_mob != value) { - var oldMob = _mob; - _mob = value; - - if (oldMob != null) { - oldMob.Key = null; - oldMob.SpawnProc("Logout"); - oldMob.Connection = null; - } +namespace OpenDreamRuntime; + +public sealed class DreamConnection { + [Dependency] private readonly DreamManager _dreamManager = default!; + [Dependency] private readonly DreamObjectTree _objectTree = default!; + [Dependency] private readonly DreamResourceManager _resourceManager = default!; + [Dependency] private readonly WalkManager _walkManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + + private readonly ServerScreenOverlaySystem? _screenOverlaySystem; + private readonly ServerClientImagesSystem? _clientImagesSystem; + + [ViewVariables] private readonly Dictionary _availableVerbs = new(); + [ViewVariables] private readonly Dictionary> _statPanels = new(); + [ViewVariables] private bool _currentlyUpdatingStat; + + [ViewVariables] public ICommonSession? Session { get; private set; } + [ViewVariables] public DreamObjectClient? Client { get; private set; } + [ViewVariables] public DreamObjectMob? Mob { + get => _mob; + set { + if (_mob != value) { + var oldMob = _mob; + _mob = value; + + if (oldMob != null) { + oldMob.Key = null; + oldMob.SpawnProc("Logout"); + oldMob.Connection = null; + } - StatObj = new(value); - if (Eye != null && Eye == oldMob) { - Eye = value; - } + StatObj = new(value); + if (Eye != null && Eye == oldMob) { + Eye = value; + } - if (_mob != null) { - // If the mob is already owned by another player, kick them out - if (_mob.Connection != null) - _mob.Connection.Mob = null; + if (_mob != null) { + // If the mob is already owned by another player, kick them out + if (_mob.Connection != null) + _mob.Connection.Mob = null; - _mob.Connection = this; - _mob.Key = Session!.Name; - _mob.SpawnProc("Login", usr: _mob); - } - - UpdateAvailableVerbs(); + _mob.Connection = this; + _mob.Key = Session!.Name; + _mob.SpawnProc("Login", usr: _mob); } + + UpdateAvailableVerbs(); } } + } - [ViewVariables] - public DreamObjectMovable? Eye { - get => _eye; - set { - _eye = value; - - if (_eye != null) { - _actorSystem?.Attach(_eye.Entity, Session!); - } else { - _actorSystem?.Detach(Session!); - } - } + [ViewVariables] + public DreamObjectMovable? Eye { + get => _eye; + set { + _eye = value; + _playerManager.SetAttachedEntity(Session!, _eye?.Entity); } + } - [ViewVariables] - public DreamValue StatObj { get; set; } // This can be just any DreamValue. Only atoms will function though. + [ViewVariables] + public DreamValue StatObj { get; set; } // This can be just any DreamValue. Only atoms will function though. - [ViewVariables] private string? _outputStatPanel; - [ViewVariables] private string _selectedStatPanel; - [ViewVariables] private readonly Dictionary> _promptEvents = new(); - [ViewVariables] private int _nextPromptEvent = 1; + [ViewVariables] private string? _outputStatPanel; + [ViewVariables] private string _selectedStatPanel; + [ViewVariables] private readonly Dictionary> _promptEvents = new(); + [ViewVariables] private int _nextPromptEvent = 1; - private DreamObjectMob? _mob; - private DreamObjectMovable? _eye; + private DreamObjectMob? _mob; + private DreamObjectMovable? _eye; - private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.connection"); + private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.connection"); - public string SelectedStatPanel { - get => _selectedStatPanel; - set { - _selectedStatPanel = value; + public string SelectedStatPanel { + get => _selectedStatPanel; + set { + _selectedStatPanel = value; - var msg = new MsgSelectStatPanel() { StatPanel = value }; - Session?.ConnectedClient.SendMessage(msg); - } + var msg = new MsgSelectStatPanel() { StatPanel = value }; + Session?.ConnectedClient.SendMessage(msg); } + } - public DreamConnection() { - IoCManager.InjectDependencies(this); + public DreamConnection() { + IoCManager.InjectDependencies(this); - _entitySystemManager.TryGetEntitySystem(out _screenOverlaySystem); - _entitySystemManager.TryGetEntitySystem(out _clientImagesSystem); - _entitySystemManager.TryGetEntitySystem(out _actorSystem); - } + _entitySystemManager.TryGetEntitySystem(out _screenOverlaySystem); + _entitySystemManager.TryGetEntitySystem(out _clientImagesSystem); + } - public void HandleConnection(ICommonSession session) { - var client = new DreamObjectClient(_objectTree.Client.ObjectDefinition, this, _screenOverlaySystem, _clientImagesSystem); + public void HandleConnection(ICommonSession session) { + var client = new DreamObjectClient(_objectTree.Client.ObjectDefinition, this, _screenOverlaySystem, _clientImagesSystem); - Session = session; + Session = session; - Client = client; - Client.InitSpawn(new()); + Client = client; + Client.InitSpawn(new()); - SendClientInfoUpdate(); - } + SendClientInfoUpdate(); + } - public void HandleDisconnection() { - if (Session == null || Client == null) // Already disconnected? - return; + public void HandleDisconnection() { + if (Session == null || Client == null) // Already disconnected? + return; - if (_mob != null) { - // Don't null out the ckey here - _mob.SpawnProc("Logout"); + if (_mob != null) { + // Don't null out the ckey here + _mob.SpawnProc("Logout"); - if (_mob != null) { // Logout() may have removed our mob - _mob.Connection = null; - _mob = null; - } + if (_mob != null) { // Logout() may have removed our mob + _mob.Connection = null; + _mob = null; } + } - Client.Delete(); - Client = null; + Client.Delete(); + Client = null; - Session = null; - } + Session = null; + } - public void UpdateAvailableVerbs() { - _availableVerbs.Clear(); - var verbs = new List<(string, string, string)>(); - - void AddVerbs(DreamObject src, IEnumerable adding) { - foreach (DreamValue mobVerb in adding) { - if (!mobVerb.TryGetValueAsProc(out var proc)) - continue; - - string verbName = proc.VerbName ?? proc.Name; - string verbId = verbName.ToLowerInvariant().Replace(" ", "-"); // Case-insensitive, dashes instead of spaces - if (_availableVerbs.ContainsKey(verbId)) { - // BYOND will actually show the user two verbs with different capitalization/dashes, but they will both execute the same verb. - // We make a warning and ignore the latter ones instead. - _sawmill.Warning($"User \"{Session.Name}\" has multiple verb commands named \"{verbId}\", ignoring all but the first"); - continue; - } + public void UpdateAvailableVerbs() { + _availableVerbs.Clear(); + var verbs = new List<(string, string, string)>(); - _availableVerbs.Add(verbId, (src, proc)); + void AddVerbs(DreamObject src, IEnumerable adding) { + foreach (DreamValue mobVerb in adding) { + if (!mobVerb.TryGetValueAsProc(out var proc)) + continue; - // Don't send invisible verbs. - if (_mob != null && proc.Invisibility > _mob.SeeInvisible) { - continue; - } + string verbName = proc.VerbName ?? proc.Name; + string verbId = verbName.ToLowerInvariant().Replace(" ", "-"); // Case-insensitive, dashes instead of spaces + if (_availableVerbs.ContainsKey(verbId)) { + // BYOND will actually show the user two verbs with different capitalization/dashes, but they will both execute the same verb. + // We make a warning and ignore the latter ones instead. + _sawmill.Warning($"User \"{Session.Name}\" has multiple verb commands named \"{verbId}\", ignoring all but the first"); + continue; + } - // Don't send hidden verbs. Names starting with "." count as hidden. - if ((proc.Attributes & ProcAttributes.Hidden) == ProcAttributes.Hidden || - verbName.StartsWith('.')) { - continue; - } + _availableVerbs.Add(verbId, (src, proc)); - string? category = proc.VerbCategory; - // Explicitly null category is hidden from verb panels, "" category becomes the default_verb_category - if (category == string.Empty) { - // But if default_verb_category is null, we hide it from the verb panel - Client.GetVariable("default_verb_category").TryGetValueAsString(out category); - } + // Don't send invisible verbs. + if (_mob != null && proc.Invisibility > _mob.SeeInvisible) { + continue; + } - // Null category is serialized as an empty string and treated as hidden - verbs.Add((verbName, verbId, category ?? string.Empty)); + // Don't send hidden verbs. Names starting with "." count as hidden. + if ((proc.Attributes & ProcAttributes.Hidden) == ProcAttributes.Hidden || + verbName.StartsWith('.')) { + continue; } - } - if (Client != null) { - AddVerbs(Client, Client.Verbs.GetValues()); - } + string? category = proc.VerbCategory; + // Explicitly null category is hidden from verb panels, "" category becomes the default_verb_category + if (category == string.Empty) { + // But if default_verb_category is null, we hide it from the verb panel + Client.GetVariable("default_verb_category").TryGetValueAsString(out category); + } - if (Mob != null) { - AddVerbs(Mob, Mob.Verbs.GetValues()); + // Null category is serialized as an empty string and treated as hidden + verbs.Add((verbName, verbId, category ?? string.Empty)); } + } - var msg = new MsgUpdateAvailableVerbs() { - AvailableVerbs = verbs.ToArray() - }; + if (Client != null) { + AddVerbs(Client, Client.Verbs.GetValues()); + } - Session?.ConnectedClient.SendMessage(msg); + if (Mob != null) { + AddVerbs(Mob, Mob.Verbs.GetValues()); } - public void UpdateStat() { - if (Session == null || Client == null || _currentlyUpdatingStat) - return; + var msg = new MsgUpdateAvailableVerbs() { + AvailableVerbs = verbs.ToArray() + }; - _currentlyUpdatingStat = true; - _statPanels.Clear(); + Session?.ConnectedClient.SendMessage(msg); + } - DreamThread.Run("Stat", async (state) => { - try { - var statProc = Client.GetProc("Stat"); + public void UpdateStat() { + if (Session == null || Client == null || _currentlyUpdatingStat) + return; - await state.Call(statProc, Client, Mob); - if (Session.Status == SessionStatus.InGame) { - var msg = new MsgUpdateStatPanels(_statPanels); - Session.ConnectedClient.SendMessage(msg); - } + _currentlyUpdatingStat = true; + _statPanels.Clear(); - return DreamValue.Null; - } finally { - _currentlyUpdatingStat = false; - } - }); - } + DreamThread.Run("Stat", async (state) => { + try { + var statProc = Client.GetProc("Stat"); - public void SendClientInfoUpdate() { - MsgUpdateClientInfo msg = new() { - View = Client!.View - }; + await state.Call(statProc, Client, Mob); + if (Session.Status == SessionStatus.InGame) { + var msg = new MsgUpdateStatPanels(_statPanels); + Session.ConnectedClient.SendMessage(msg); + } - Session?.ConnectedClient.SendMessage(msg); - } + return DreamValue.Null; + } finally { + _currentlyUpdatingStat = false; + } + }); + } - public void SetOutputStatPanel(string name) { - if (!_statPanels.ContainsKey(name)) - _statPanels.Add(name, new()); + public void SendClientInfoUpdate() { + MsgUpdateClientInfo msg = new() { + View = Client!.View + }; - _outputStatPanel = name; - } + Session?.ConnectedClient.SendMessage(msg); + } - public void AddStatPanelLine(string name, string value, string? atomRef) { - if (_outputStatPanel == null || !_statPanels.ContainsKey(_outputStatPanel)) - SetOutputStatPanel("Stats"); + public void SetOutputStatPanel(string name) { + if (!_statPanels.ContainsKey(name)) + _statPanels.Add(name, new()); - _statPanels[_outputStatPanel].Add( (name, value, atomRef) ); - } + _outputStatPanel = name; + } - public void HandleMsgSelectStatPanel(MsgSelectStatPanel message) { - _selectedStatPanel = message.StatPanel; - } + public void AddStatPanelLine(string name, string value, string? atomRef) { + if (_outputStatPanel == null || !_statPanels.ContainsKey(_outputStatPanel)) + SetOutputStatPanel("Stats"); - public void HandleMsgPromptResponse(MsgPromptResponse message) { - if (!_promptEvents.TryGetValue(message.PromptId, out var promptEvent)) { - _sawmill.Warning($"{message.MsgChannel}: Received MsgPromptResponse for prompt {message.PromptId} which does not exist."); - return; - } + _statPanels[_outputStatPanel].Add( (name, value, atomRef) ); + } - DreamValue value = message.Type switch { - DMValueType.Null => DreamValue.Null, - DMValueType.Text or DMValueType.Message => new DreamValue((string)message.Value), - DMValueType.Num => new DreamValue((float)message.Value), - _ => throw new Exception("Invalid prompt response '" + message.Type + "'") - }; + public void HandleMsgSelectStatPanel(MsgSelectStatPanel message) { + _selectedStatPanel = message.StatPanel; + } - promptEvent.Invoke(value); - _promptEvents.Remove(message.PromptId); + public void HandleMsgPromptResponse(MsgPromptResponse message) { + if (!_promptEvents.TryGetValue(message.PromptId, out var promptEvent)) { + _sawmill.Warning($"{message.MsgChannel}: Received MsgPromptResponse for prompt {message.PromptId} which does not exist."); + return; } - public void HandleMsgTopic(MsgTopic pTopic) { - DreamList hrefList = DreamProcNativeRoot.params2list(_objectTree, HttpUtility.UrlDecode(pTopic.Query)); - DreamValue srcRefValue = hrefList.GetValue(new DreamValue("src")); - DreamValue src = DreamValue.Null; + DreamValue value = message.Type switch { + DMValueType.Null => DreamValue.Null, + DMValueType.Text or DMValueType.Message => new DreamValue((string)message.Value), + DMValueType.Num => new DreamValue((float)message.Value), + _ => throw new Exception("Invalid prompt response '" + message.Type + "'") + }; - if (srcRefValue.TryGetValueAsString(out var srcRef)) { - src = _dreamManager.LocateRef(srcRef); - } + promptEvent.Invoke(value); + _promptEvents.Remove(message.PromptId); + } + + public void HandleMsgTopic(MsgTopic pTopic) { + DreamList hrefList = DreamProcNativeRoot.params2list(_objectTree, HttpUtility.UrlDecode(pTopic.Query)); + DreamValue srcRefValue = hrefList.GetValue(new DreamValue("src")); + DreamValue src = DreamValue.Null; - Client?.SpawnProc("Topic", usr: Mob, new(pTopic.Query), new(hrefList), src); + if (srcRefValue.TryGetValueAsString(out var srcRef)) { + src = _dreamManager.LocateRef(srcRef); } - public void OutputDreamValue(DreamValue value) { - if (value.TryGetValueAsDreamObject(out var outputObject)) { - ushort channel = (ushort)outputObject.GetVariable("channel").GetValueAsInteger(); - ushort volume = (ushort)outputObject.GetVariable("volume").GetValueAsInteger(); - DreamValue file = outputObject.GetVariable("file"); + Client?.SpawnProc("Topic", usr: Mob, new(pTopic.Query), new(hrefList), src); + } - var msg = new MsgSound() { - Channel = channel, - Volume = volume - }; + public void OutputDreamValue(DreamValue value) { + if (value.TryGetValueAsDreamObject(out var outputObject)) { + ushort channel = (ushort)outputObject.GetVariable("channel").GetValueAsInteger(); + ushort volume = (ushort)outputObject.GetVariable("volume").GetValueAsInteger(); + DreamValue file = outputObject.GetVariable("file"); - if (!file.TryGetValueAsDreamResource(out var soundResource)) { - if (file.TryGetValueAsString(out var soundPath)) { - soundResource = _resourceManager.LoadResource(soundPath); - } else if (!file.IsNull) { - throw new ArgumentException($"Cannot output {value}", nameof(value)); - } - } + var msg = new MsgSound() { + Channel = channel, + Volume = volume + }; - msg.ResourceId = soundResource?.Id; - if (soundResource?.ResourcePath is { } resourcePath) { - if (resourcePath.EndsWith(".ogg")) - msg.Format = MsgSound.FormatType.Ogg; - else if (resourcePath.EndsWith(".wav")) - msg.Format = MsgSound.FormatType.Wav; - else - throw new Exception($"Sound {value} is not a supported file type"); + if (!file.TryGetValueAsDreamResource(out var soundResource)) { + if (file.TryGetValueAsString(out var soundPath)) { + soundResource = _resourceManager.LoadResource(soundPath); + } else if (!file.IsNull) { + throw new ArgumentException($"Cannot output {value}", nameof(value)); } + } - Session?.ConnectedClient.SendMessage(msg); - return; + msg.ResourceId = soundResource?.Id; + if (soundResource?.ResourcePath is { } resourcePath) { + if (resourcePath.EndsWith(".ogg")) + msg.Format = MsgSound.FormatType.Ogg; + else if (resourcePath.EndsWith(".wav")) + msg.Format = MsgSound.FormatType.Wav; + else + throw new Exception($"Sound {value} is not a supported file type"); } - OutputControl(value.Stringify(), null); + Session?.ConnectedClient.SendMessage(msg); + return; } - public void OutputControl(string message, string? control) { - var msg = new MsgOutput() { - Value = message, - Control = control - }; + OutputControl(value.Stringify(), null); + } - Session?.ConnectedClient.SendMessage(msg); - } + public void OutputControl(string message, string? control) { + var msg = new MsgOutput() { + Value = message, + Control = control + }; - public void HandleCommand(string fullCommand) { - // TODO: Arguments are a little more complicated than "split by spaces" - // e.g. strings can be passed - string[] args = fullCommand.Split(' ', StringSplitOptions.TrimEntries); - string command = args[0].ToLowerInvariant(); // Case-insensitive - - switch (command) { - case ".north": - case ".east": - case ".south": - case ".west": - case ".northeast": - case ".southeast": - case ".southwest": - case ".northwest": - case ".center": - string movementProc = command switch { - ".north" => "North", - ".east" => "East", - ".south" => "South", - ".west" => "West", - ".northeast" => "Northeast", - ".southeast" => "Southeast", - ".southwest" => "Southwest", - ".northwest" => "Northwest", - ".center" => "Center", - _ => throw new ArgumentOutOfRangeException() - }; - - if (Mob != null) - _walkManager.StopWalks(Mob); - Client?.SpawnProc(movementProc, Mob); break; - - default: { - if (_availableVerbs.TryGetValue(command, out var value)) { - (DreamObject verbSrc, DreamProc verb) = value; - - DreamThread.Run(fullCommand, async (state) => { - DreamValue[] arguments; - if (verb.ArgumentNames != null) { - arguments = new DreamValue[verb.ArgumentNames.Count]; - - // TODO: this should probably be done on the client, shouldn't it? - if (args.Length == 1) { // No args given; prompt the client for them - for (int i = 0; i < verb.ArgumentNames.Count; i++) { - String argumentName = verb.ArgumentNames[i]; - DMValueType argumentType = verb.ArgumentTypes[i]; - DreamValue argumentValue = await Prompt(argumentType, title: String.Empty, // No settable title for verbs - argumentName, defaultValue: String.Empty); // No default value for verbs - - arguments[i] = argumentValue; - } - } else { // Attempt to parse the given arguments - for (int i = 0; i < verb.ArgumentNames.Count; i++) { - DMValueType argumentType = verb.ArgumentTypes[i]; - - if (argumentType == DMValueType.Text) { - arguments[i] = new(args[i+1]); - } else { - _sawmill.Error($"Parsing verb args of type {argumentType} is unimplemented; ignoring command ({fullCommand})"); - return DreamValue.Null; - } - } - } - } else { - arguments = Array.Empty(); - } + Session?.ConnectedClient.SendMessage(msg); + } - await state.Call(verb, verbSrc, Mob, arguments); - return DreamValue.Null; - }); - } + public void HandleCommand(string fullCommand) { + // TODO: Arguments are a little more complicated than "split by spaces" + // e.g. strings can be passed + string[] args = fullCommand.Split(' ', StringSplitOptions.TrimEntries); + string command = args[0].ToLowerInvariant(); // Case-insensitive + + switch (command) { + case ".north": + case ".east": + case ".south": + case ".west": + case ".northeast": + case ".southeast": + case ".southwest": + case ".northwest": + case ".center": + string movementProc = command switch { + ".north" => "North", + ".east" => "East", + ".south" => "South", + ".west" => "West", + ".northeast" => "Northeast", + ".southeast" => "Southeast", + ".southwest" => "Southwest", + ".northwest" => "Northwest", + ".center" => "Center", + _ => throw new ArgumentOutOfRangeException() + }; - break; - } - } - } + if (Mob != null) + _walkManager.StopWalks(Mob); + Client?.SpawnProc(movementProc, Mob); break; - public Task Prompt(DMValueType types, String title, String message, String defaultValue) { - var task = MakePromptTask(out var promptId); - var msg = new MsgPrompt() { - PromptId = promptId, - Title = title, - Message = message, - Types = types, - DefaultValue = defaultValue - }; + default: { + if (_availableVerbs.TryGetValue(command, out var value)) { + (DreamObject verbSrc, DreamProc verb) = value; - Session.ConnectedClient.SendMessage(msg); - return task; - } + DreamThread.Run(fullCommand, async (state) => { + DreamValue[] arguments; + if (verb.ArgumentNames != null) { + arguments = new DreamValue[verb.ArgumentNames.Count]; - public async Task PromptList(DMValueType types, DreamList list, string title, string message, DreamValue defaultValue) { - List listValues = list.GetValues(); + // TODO: this should probably be done on the client, shouldn't it? + if (args.Length == 1) { // No args given; prompt the client for them + for (int i = 0; i < verb.ArgumentNames.Count; i++) { + String argumentName = verb.ArgumentNames[i]; + DMValueType argumentType = verb.ArgumentTypes[i]; + DreamValue argumentValue = await Prompt(argumentType, title: String.Empty, // No settable title for verbs + argumentName, defaultValue: String.Empty); // No default value for verbs - List promptValues = new(listValues.Count); - for (int i = 0; i < listValues.Count; i++) { - DreamValue value = listValues[i]; + arguments[i] = argumentValue; + } + } else { // Attempt to parse the given arguments + for (int i = 0; i < verb.ArgumentNames.Count; i++) { + DMValueType argumentType = verb.ArgumentTypes[i]; + + if (argumentType == DMValueType.Text) { + arguments[i] = new(args[i+1]); + } else { + _sawmill.Error($"Parsing verb args of type {argumentType} is unimplemented; ignoring command ({fullCommand})"); + return DreamValue.Null; + } + } + } + } else { + arguments = Array.Empty(); + } - if (types.HasFlag(DMValueType.Obj) && !value.TryGetValueAsDreamObject(out _)) - continue; - if (types.HasFlag(DMValueType.Mob) && !value.TryGetValueAsDreamObject(out _)) - continue; - if (types.HasFlag(DMValueType.Turf) && !value.TryGetValueAsDreamObject(out _)) - continue; - if (types.HasFlag(DMValueType.Area) && !value.TryGetValueAsDreamObject(out _)) - continue; + await state.Call(verb, verbSrc, Mob, arguments); + return DreamValue.Null; + }); + } - promptValues.Add(value.Stringify()); + break; } + } + } - if (promptValues.Count == 0) - return DreamValue.Null; + public Task Prompt(DMValueType types, String title, String message, String defaultValue) { + var task = MakePromptTask(out var promptId); + var msg = new MsgPrompt() { + PromptId = promptId, + Title = title, + Message = message, + Types = types, + DefaultValue = defaultValue + }; + + Session.ConnectedClient.SendMessage(msg); + return task; + } - var task = MakePromptTask(out var promptId); - var msg = new MsgPromptList() { - PromptId = promptId, - Title = title, - Message = message, - CanCancel = (types & DMValueType.Null) == DMValueType.Null, - DefaultValue = defaultValue.Stringify(), - Values = promptValues.ToArray() - }; + public async Task PromptList(DMValueType types, DreamList list, string title, string message, DreamValue defaultValue) { + List listValues = list.GetValues(); - Session.ConnectedClient.SendMessage(msg); + List promptValues = new(listValues.Count); + for (int i = 0; i < listValues.Count; i++) { + DreamValue value = listValues[i]; - // The client returns the index of the selected item, this needs turned back into the DreamValue. - var selectedIndex = await task; - if (selectedIndex.TryGetValueAsInteger(out int index) && index < listValues.Count) { - return listValues[index]; - } + if (types.HasFlag(DMValueType.Obj) && !value.TryGetValueAsDreamObject(out _)) + continue; + if (types.HasFlag(DMValueType.Mob) && !value.TryGetValueAsDreamObject(out _)) + continue; + if (types.HasFlag(DMValueType.Turf) && !value.TryGetValueAsDreamObject(out _)) + continue; + if (types.HasFlag(DMValueType.Area) && !value.TryGetValueAsDreamObject(out _)) + continue; - // Client returned an invalid value. - // Return the first value in the list, or null if cancellable - return msg.CanCancel ? DreamValue.Null : listValues[0]; + promptValues.Add(value.Stringify()); } - public Task WinExists(string controlId) { - var task = MakePromptTask(out var promptId); - var msg = new MsgWinExists() { - PromptId = promptId, - ControlId = controlId - }; + if (promptValues.Count == 0) + return DreamValue.Null; + + var task = MakePromptTask(out var promptId); + var msg = new MsgPromptList() { + PromptId = promptId, + Title = title, + Message = message, + CanCancel = (types & DMValueType.Null) == DMValueType.Null, + DefaultValue = defaultValue.Stringify(), + Values = promptValues.ToArray() + }; + + Session.ConnectedClient.SendMessage(msg); + + // The client returns the index of the selected item, this needs turned back into the DreamValue. + var selectedIndex = await task; + if (selectedIndex.TryGetValueAsInteger(out int index) && index < listValues.Count) { + return listValues[index]; + } - Session.ConnectedClient.SendMessage(msg); + // Client returned an invalid value. + // Return the first value in the list, or null if cancellable + return msg.CanCancel ? DreamValue.Null : listValues[0]; + } - return task; - } + public Task WinExists(string controlId) { + var task = MakePromptTask(out var promptId); + var msg = new MsgWinExists() { + PromptId = promptId, + ControlId = controlId + }; - public Task WinGet(string controlId, string queryValue) { - var task = MakePromptTask(out var promptId); - var msg = new MsgWinGet() { - PromptId = promptId, - ControlId = controlId, - QueryValue = queryValue - }; + Session.ConnectedClient.SendMessage(msg); - Session.ConnectedClient.SendMessage(msg); + return task; + } - return task; - } + public Task WinGet(string controlId, string queryValue) { + var task = MakePromptTask(out var promptId); + var msg = new MsgWinGet() { + PromptId = promptId, + ControlId = controlId, + QueryValue = queryValue + }; - public Task Alert(String title, String message, String button1, String button2, String button3) { - var task = MakePromptTask(out var promptId); - var msg = new MsgAlert() { - PromptId = promptId, - Title = title, - Message = message, - Button1 = button1, - Button2 = button2, - Button3 = button3 - }; + Session.ConnectedClient.SendMessage(msg); - Session.ConnectedClient.SendMessage(msg); - return task; - } + return task; + } - private Task MakePromptTask(out int promptId) { - TaskCompletionSource tcs = new(); - promptId = _nextPromptEvent++; + public Task Alert(String title, String message, String button1, String button2, String button3) { + var task = MakePromptTask(out var promptId); + var msg = new MsgAlert() { + PromptId = promptId, + Title = title, + Message = message, + Button1 = button1, + Button2 = button2, + Button3 = button3 + }; + + Session.ConnectedClient.SendMessage(msg); + return task; + } - _promptEvents.Add(promptId, response => { - tcs.TrySetResult(response); - }); + private Task MakePromptTask(out int promptId) { + TaskCompletionSource tcs = new(); + promptId = _nextPromptEvent++; - return tcs.Task; - } + _promptEvents.Add(promptId, response => { + tcs.TrySetResult(response); + }); - public void BrowseResource(DreamResource resource, string filename) { - if (resource.ResourceData == null) - return; + return tcs.Task; + } - var msg = new MsgBrowseResource() { - Filename = filename, - Data = resource.ResourceData - }; + public void BrowseResource(DreamResource resource, string filename) { + if (resource.ResourceData == null) + return; - Session?.ConnectedClient.SendMessage(msg); - } + var msg = new MsgBrowseResource() { + Filename = filename, + Data = resource.ResourceData + }; - public void Browse(string? body, string? options) { - string? window = null; - Vector2i size = (480, 480); + Session?.ConnectedClient.SendMessage(msg); + } - if (options != null) { - foreach (string option in options.Split(',', ';', '&')) { - string optionTrimmed = option.Trim(); + public void Browse(string? body, string? options) { + string? window = null; + Vector2i size = (480, 480); - if (optionTrimmed != string.Empty) { - string[] optionSeparated = optionTrimmed.Split("=", 2); - string key = optionSeparated[0]; - string value = optionSeparated[1]; + if (options != null) { + foreach (string option in options.Split(',', ';', '&')) { + string optionTrimmed = option.Trim(); - if (key == "window") { - window = value; - } else if (key == "size") { - string[] sizeSeparated = value.Split("x", 2); + if (optionTrimmed != string.Empty) { + string[] optionSeparated = optionTrimmed.Split("=", 2); + string key = optionSeparated[0]; + string value = optionSeparated[1]; - size = (int.Parse(sizeSeparated[0]), int.Parse(sizeSeparated[1])); - } + if (key == "window") { + window = value; + } else if (key == "size") { + string[] sizeSeparated = value.Split("x", 2); + + size = (int.Parse(sizeSeparated[0]), int.Parse(sizeSeparated[1])); } } } - - var msg = new MsgBrowse() { - Size = size, - Window = window, - HtmlSource = body - }; - - Session?.ConnectedClient.SendMessage(msg); } - public void WinSet(string? controlId, string @params) { - var msg = new MsgWinSet() { - ControlId = controlId, - Params = @params - }; + var msg = new MsgBrowse() { + Size = size, + Window = window, + HtmlSource = body + }; - Session?.ConnectedClient.SendMessage(msg); - } + Session?.ConnectedClient.SendMessage(msg); + } - public void WinClone(string controlId, string cloneId) { - var msg = new MsgWinClone() { ControlId = controlId, CloneId = cloneId }; + public void WinSet(string? controlId, string @params) { + var msg = new MsgWinSet() { + ControlId = controlId, + Params = @params + }; - Session?.ConnectedClient.SendMessage(msg); - } + Session?.ConnectedClient.SendMessage(msg); + } - /// - /// Prompts the user to save a file to disk - /// - /// File to save - /// Suggested name to save the file as - public void SendFile(DreamResource file, string suggestedName) { - var msg = new MsgFtp { - ResourceId = file.Id, - SuggestedName = suggestedName - }; + public void WinClone(string controlId, string cloneId) { + var msg = new MsgWinClone() { ControlId = controlId, CloneId = cloneId }; - Session?.ConnectedClient.SendMessage(msg); - } + Session?.ConnectedClient.SendMessage(msg); + } + + /// + /// Prompts the user to save a file to disk + /// + /// File to save + /// Suggested name to save the file as + public void SendFile(DreamResource file, string suggestedName) { + var msg = new MsgFtp { + ResourceId = file.Id, + SuggestedName = suggestedName + }; + + Session?.ConnectedClient.SendMessage(msg); } } diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index eb17f69505..958a001d5d 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -753,7 +753,7 @@ public override void AddValue(DreamValue value) { // TODO: Only override the entity's visibility if its parent atom is visible if (entity != EntityUid.Invalid) - _pvsOverrideSystem?.AddGlobalOverride(entity); + _pvsOverrideSystem?.AddGlobalOverride(_entityManager.GetNetEntity(entity)); _atomManager.UpdateAppearance(_atom, appearance => { // Add even an invalid UID to keep this and _visContents in sync diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs index a24a19d891..0cdd2528fd 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs @@ -84,8 +84,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) { DreamList contents = ObjectTree.CreateList(); using (var childEnumerator = _transformComponent.ChildEnumerator) { - while (childEnumerator.MoveNext(out EntityUid? child)) { - if (!AtomManager.TryGetMovableFromEntity(child.Value, out var childAtom)) + while (childEnumerator.MoveNext(out EntityUid child)) { + if (!AtomManager.TryGetMovableFromEntity(child, out var childAtom)) continue; contents.AddValue(new DreamValue(childAtom)); diff --git a/OpenDreamRuntime/OpenDreamRuntime.csproj b/OpenDreamRuntime/OpenDreamRuntime.csproj index 153b919dee..6c861cf1b5 100644 --- a/OpenDreamRuntime/OpenDreamRuntime.csproj +++ b/OpenDreamRuntime/OpenDreamRuntime.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 enable true Debug;Release;Tools diff --git a/OpenDreamServer/OpenDreamServer.csproj b/OpenDreamServer/OpenDreamServer.csproj index 5f14ea39d0..039555f069 100644 --- a/OpenDreamServer/OpenDreamServer.csproj +++ b/OpenDreamServer/OpenDreamServer.csproj @@ -2,7 +2,7 @@ $(TargetFramework) - 11 + 12 false false ..\bin\Content.Server\ diff --git a/OpenDreamShared/OpenDreamShared.csproj b/OpenDreamShared/OpenDreamShared.csproj index ccb9c13fc0..59d0a5476e 100644 --- a/OpenDreamShared/OpenDreamShared.csproj +++ b/OpenDreamShared/OpenDreamShared.csproj @@ -2,7 +2,7 @@ $(TargetFramework) - 11 + 12 false false ../bin/Content.Shared diff --git a/README.md b/README.md index 85d2fd43ff..03a40d1626 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The first step to building OpenDream is initializing the submodule for the game To do this, simply run `git submodule update --init --recursive` in git bash and let it finish. -**OpenDream requires .NET 7.** You can check your version by running `dotnet --version`. It should be at least `7.0.0`. +**OpenDream requires .NET 8.** You can check your version by running `dotnet --version`. It should be at least `8.0.0`. To build, one can use a C# compiler (such as MSBuild) to compile the various projects described in the solution. diff --git a/RobustToolbox b/RobustToolbox index f5874ea402..eb092e90ef 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit f5874ea402430bb902a5d5d1f47953679d79d781 +Subproject commit eb092e90efc7ac4ae562bc46f9b760745a29e289 diff --git a/global.json b/global.json index 22dfd864b4..3fea262b1b 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.0", + "version": "8.0.0", "rollForward": "latestFeature" } }