Compare commits

..

1 Commits

Author SHA1 Message Date
PJB3005
ee330d0ae9 Make DOOM work
I think I lost this work originally
2025-01-15 23:08:44 +01:00
387 changed files with 6413 additions and 10401 deletions

View File

@@ -38,4 +38,4 @@ jobs:
- name: Content.Tests
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
- name: Content.IntegrationTests
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
run: dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n

3
.gitmodules vendored
View File

@@ -13,6 +13,3 @@
[submodule "ManagedHttpListener"]
path = ManagedHttpListener
url = https://github.com/space-wizards/ManagedHttpListener.git
[submodule "Linguini"]
path = Linguini
url = https://github.com/space-wizards/Linguini

Submodule Linguini deleted from 62b0e75b91

View File

@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
return IntPtr.Zero;
}
if (OperatingSystem.IsLinux())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return NativeLibrary.Load("libglfw.so.3", assembly, path);
}
if (OperatingSystem.IsMacOS())
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
}

View File

@@ -23,48 +23,6 @@
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- name: Box2D
license:
MIT License
Copyright (c) 2019 Erin Catto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- name: Bullet Physics SDK
license:
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
Bullet Continuous Collision Detection and Physics Library
http://bulletphysics.org
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
- name: Castle Core
license: |
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
@@ -359,43 +317,6 @@
See the License for the specific language governing permissions and
limitations under the License.
- name: Farseer Physics Engine
license:
Microsoft Permissive License (Ms-PL)
This license governs use of the accompanying software.
If you use the software, you accept this license.
If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
including a complete copy of this license with your distribution. If you distribute any portion of the software in
compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
To the extent permitted under your local laws, the contributors exclude the implied warranties of
merchantability, fitness for a particular purpose and non-infringement.
- name: Mono.Cecil
license: |
Copyright (c) 2008 - 2015 Jb Evain

View File

@@ -0,0 +1 @@
console-line-edit-placeholder = Command Here

View File

@@ -1,11 +0,0 @@
## EntitySpawnWindow
entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-erase-button-text = Erase Mode
entity-spawn-window-override-menu-tooltip = Override placement
## Console
console-line-edit-placeholder = Command Here

View File

@@ -1 +0,0 @@
tab-container-not-tab-title-provided = No title

View File

@@ -1,11 +0,0 @@
## ViewVariablesInstanceEntity
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
view-variable-instance-entity-client-variables-tab-title = Client Variables
view-variable-instance-entity-client-components-tab-title = Client Components
view-variable-instance-entity-server-variables-tab-title = Server Variables
view-variable-instance-entity-server-components-tab-title = Server Components
view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]

View File

@@ -29,27 +29,26 @@ namespace Robust.Build.Tasks
}
}
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
class ConsoleBuildEngine : IBuildEngine
{
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine(e.Message);
Console.WriteLine($"CUSTOM: {e.Message}");
}
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,

View File

@@ -280,8 +280,8 @@ namespace Robust.Build.Tasks
}
catch (Exception e)
{
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
e.ToString(), "", "CompileRobustXaml"));
}
}
return true;

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -148,10 +147,9 @@ namespace {nameSpace}
foreach (var typeSymbol in symbols)
{
var xamlFileName = $"{typeSymbol.Name}.xaml";
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
if (relevantXamlFiles.Length == 0)
if (relevantXamlFile == null)
{
context.ReportDiagnostic(
Diagnostic.Create(
@@ -167,28 +165,13 @@ namespace {nameSpace}
continue;
}
if (relevantXamlFiles.Length > 1)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0002",
$"Found multiple candidate XAML files for {typeSymbol}",
$"Multiple files exist with name {xamlFileName}",
"Usage",
DiagnosticSeverity.Error,
true),
typeSymbol.Locations[0]));
continue;
}
var txt = relevantXamlFiles[0].GetText()?.ToString();
var txt = relevantXamlFile.GetText()?.ToString();
if (txt == null)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0004",
"RXN0002",
$"Unexpected empty Xaml-File was found at {xamlFileName}",
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
"Usage",
@@ -209,7 +192,7 @@ namespace {nameSpace}
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0003",
"AXN0003",
"Unhandled exception occured while generating typed Name references.",
$"Unhandled exception occured while generating typed Name references: {e}",
"Usage",

View File

@@ -6,8 +6,6 @@ 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;
@@ -63,9 +61,8 @@ 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!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
@@ -97,7 +94,7 @@ namespace Robust.Client.Audio.Midi
if (MathHelper.CloseTo(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volume = value;
_volumeDirty = true;
}
}
@@ -134,12 +131,6 @@ namespace Robust.Client.Audio.Midi
{
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,7 +166,7 @@ namespace Robust.Client.Audio.Midi
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
FluidsynthInitialized = true;
}
@@ -225,7 +216,7 @@ namespace Robust.Client.Audio.Midi
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
foreach (var filepath in LinuxSoundfonts)
{
@@ -243,12 +234,12 @@ namespace Robust.Client.Audio.Midi
break;
}
}
else if (OperatingSystem.IsMacOS())
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (OperatingSystem.IsWindows())
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);

View File

@@ -7,7 +7,6 @@ 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;
@@ -204,9 +203,7 @@ 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;
@@ -316,7 +313,6 @@ 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);
@@ -326,27 +322,6 @@ 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)
@@ -453,6 +428,9 @@ namespace Robust.Client.Audio.Midi
{
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;
@@ -467,16 +445,36 @@ 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;
NumericsHelpers.Add(audio[..l], audio[l..]);
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]));
}
}
}
for (var i = 0; i < buffers.Length; i++)
@@ -534,16 +532,16 @@ namespace Robust.Client.Audio.Midi
lock(_playerStateLock)
switch (midiEvent.Type)
{
// Note Off - 0x80
case 128:
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// Note On 0x90
// Note On 0x80
case 144:
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
// Note Off - 0x90
case 128:
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// After Touch - 0xA
case 160:
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
@@ -578,12 +576,6 @@ namespace Robust.Client.Audio.Midi
case 81:
// System Messages - 0xF0
case 240:
switch (midiEvent.Control)
{
case 11:
_synth.AllNotesOff(midiEvent.Channel);
break;
}
return;
default:
@@ -605,7 +597,7 @@ namespace Robust.Client.Audio.Midi
if (Disposed) return;
var seqEv = (SequencerEvent) midiEvent;
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
seqEv.Dest = _synthRegister;
_sequencer.SendAt(seqEv, time, absolute);
}
@@ -628,15 +620,10 @@ namespace Robust.Client.Audio.Midi
void IMidiRenderer.InternalDispose()
{
Source?.Dispose();
_driver?.Dispose();
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
_sequencer?.UnregisterClient(_debugRegister);
_sequencer?.UnregisterClient(_synthRegister);
_sequencer?.Dispose();
_synth?.Dispose();
_player?.Dispose();
_driver?.Dispose();
_sequencer?.Dispose();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;

View File

@@ -46,15 +46,13 @@ namespace Robust.Client
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<GameController, GameController>();
IoCManager.Register<IGameController, GameController>();
IoCManager.Register<IGameControllerInternal, GameController>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IResourceManager, ResourceCache>();
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
IoCManager.Register<IResourceCache, ResourceCache>();
@@ -74,6 +72,8 @@ namespace Robust.Client
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
switch (mode)

View File

@@ -17,7 +17,6 @@ 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?
@@ -32,7 +31,6 @@ 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();
@@ -126,26 +124,6 @@ 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();
@@ -164,7 +142,6 @@ namespace Robust.Client
launcher,
username,
cvars,
logLevels,
connectAddress,
ss14Address,
mountOptions);
@@ -185,7 +162,6 @@ 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.
@@ -199,7 +175,6 @@ Options:
bool launcher,
string? username,
IReadOnlyCollection<(string key, string value)> cVars,
IReadOnlyCollection<(string key, string value)> logLevels,
string connectAddress, string? ss14Address,
MountOptions mountOptions)
{
@@ -209,7 +184,6 @@ 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,15 +45,11 @@ namespace Robust.Client.Console
/// <inheritdoc />
public void Initialize()
{
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
Reset();
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
}
@@ -65,6 +61,17 @@ namespace Robust.Client.Console
ExecuteCommand(null, text);
}
/// <inheritdoc />
public void Reset()
{
AvailableCommands.Clear();
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
}
/// <inheritdoc />
public event EventHandler<AddStringArgs>? AddString;
@@ -83,8 +90,6 @@ namespace Robust.Client.Console
OutputText(text, true, true);
}
public override event ConAnyCommandCallback? AnyCommandExecuted;
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
{
@@ -92,7 +97,7 @@ namespace Robust.Client.Console
return;
// echo the command locally
WriteLine(null, "> " + command);
WriteError(null, "> " + command);
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
@@ -105,11 +110,7 @@ namespace Robust.Client.Console
{
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
var shell = new ConsoleShell(this, null);
var cmdArgs = args.ToArray();
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
command1.Execute(shell, command, cmdArgs);
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
}
else
WriteError(null, "Unknown command: " + commandName);
@@ -141,9 +142,6 @@ namespace Robust.Client.Console
private void OutputText(string text, bool local, bool error)
{
AddString?.Invoke(this, new AddStringArgs(text, local, error));
var level = error ? LogLevel.Warning : LogLevel.Info;
Logger.LogS(level, "CON", text);
}
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)

View File

@@ -27,7 +27,6 @@ using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console.Commands
{
@@ -78,7 +77,7 @@ namespace Robust.Client.Console.Commands
message.Append($"net ID: {registration.NetID}");
}
message.Append($", References:");
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
shell.WriteLine(message.ToString());
@@ -537,10 +536,7 @@ namespace Robust.Client.Console.Commands
var scroll = new ScrollContainer();
tabContainer.AddChild(scroll);
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var vBox = new VBoxContainer();
scroll.AddChild(vBox);
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
@@ -598,10 +594,7 @@ namespace Robust.Client.Console.Commands
}
var group = new ButtonGroup();
var vBoxRadioButtons = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var vBoxRadioButtons = new VBoxContainer();
for (var i = 0; i < 10; i++)
{
vBoxRadioButtons.AddChild(new Button
@@ -617,9 +610,8 @@ namespace Robust.Client.Console.Commands
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
tabContainer.AddChild(new BoxContainer
tabContainer.AddChild(new VBoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = "Slider",
Children =
{

View File

@@ -1,19 +0,0 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : IConsoleCommand
{
public string Command => "lightbb";
public string Description => "Toggles whether to show light bounding boxes";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands
{
@@ -40,7 +41,7 @@ namespace Robust.Client.Console.Commands
var mgr = IoCManager.Resolve<IScriptClient>();
if (!mgr.CanScript)
{
shell.WriteError("You do not have server side scripting permission.");
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
return;
}

View File

@@ -11,6 +11,11 @@ namespace Robust.Client.Console
/// </summary>
void Initialize();
/// <summary>
/// Resets the console to a post-initialized state.
/// </summary>
void Reset();
event EventHandler<AddStringArgs> AddString;
event EventHandler<AddFormattedMessageArgs> AddFormatted;

View File

@@ -17,11 +17,11 @@ namespace Robust.Client.Console
public void Initialize()
{
_netManager.RegisterNetMessage<MsgScriptStop>();
_netManager.RegisterNetMessage<MsgScriptEval>();
_netManager.RegisterNetMessage<MsgScriptStart>();
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
_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);
}
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)

View File

@@ -1,4 +1,5 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -13,11 +14,11 @@ using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
#nullable enable
@@ -42,14 +43,14 @@ namespace Robust.Client.Console
public ScriptConsoleClient()
{
Title = "Robust C# Interactive (CLIENT)";
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
ScriptInstanceShared.InitDummy();
_globals = new ScriptGlobalsImpl(this);
IoCManager.InjectDependencies(this);
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
OutputPanel.AddText(">");
}
@@ -117,7 +118,7 @@ namespace Robust.Client.Console
}
else
{
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
}

View File

@@ -8,10 +8,11 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console
{
@@ -19,7 +20,7 @@ namespace Robust.Client.Console
{
private readonly IReflectionManager _reflectionManager;
private readonly BoxContainer _watchesVBox;
private readonly VBoxContainer _watchesVBox;
private readonly LineEdit _addWatchEdit;
private readonly Button _addWatchButton;
@@ -30,32 +31,29 @@ namespace Robust.Client.Console
ScriptInstanceShared.InitDummy();
Title = "Watch Window";
Title = Loc.GetString("Watch Window");
var mainVBox = new BoxContainer
var mainVBox = new VBoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (500, 300),
Children =
{
(_watchesVBox = new BoxContainer
(_watchesVBox = new VBoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true
}),
new BoxContainer
new HBoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_addWatchEdit = new HistoryLineEdit
{
HorizontalExpand = true,
PlaceHolder = "Add watch (C# interactive)"
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
}),
(_addWatchButton = new Button
{
Text = "Add"
Text = Loc.GetString("Add")
})
}
}
@@ -109,9 +107,8 @@ namespace Robust.Client.Console
Button delButton;
_runner = runner;
AddChild(new BoxContainer
AddChild(new HBoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_outputLabel = new Label
@@ -121,7 +118,7 @@ namespace Robust.Client.Console
}),
(delButton = new Button
{
Text = "Remove"
Text = Loc.GetString("Remove")
}),
}
});
@@ -171,9 +168,8 @@ namespace Robust.Client.Console
public CompilationErrorControl(string message)
{
Button delButton;
AddChild(new BoxContainer
AddChild(new HBoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label
@@ -182,7 +178,7 @@ namespace Robust.Client.Console
ClipText = true,
HorizontalExpand = true
},
(delButton = new Button {Text = "Remove"})
(delButton = new Button {Text = Loc.GetString("Remove")})
}
});

View File

@@ -7,13 +7,13 @@ namespace Robust.Client
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
GameController.Start(args, new GameControllerOptions(), true);
GameController.Start(args, true);
#endif
}
public static void StartLibrary(string[] args, GameControllerOptions options)
{
GameController.Start(args, options, true, null);
GameController.Start(args, true, null, options);
}
}
}

View File

@@ -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 IEntityLookup _lookup = default!;
[Dependency] private readonly IEntityManager _entityManager = 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(_lookup, _eyeManager));
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
@@ -130,17 +130,17 @@ namespace Robust.Client.Debugging
{
if (body != _hoverBodies[0])
{
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
@@ -162,25 +162,23 @@ namespace Robust.Client.Debugging
if (viewport.IsEmpty()) return;
var mapId = _eyeManager.CurrentMap;
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
var colorEdge = Color.Red.WithAlpha(0.33f);
var drawnJoints = new HashSet<Joint>();
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewport))
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;
var worldBox = physBody.GetWorldAABB();
var worldBox = physBody.GetWorldAABB(_mapManager);
if (worldBox.IsEmpty()) continue;
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
drawing.SetTransform(in Matrix3.Identity);
}
foreach (var joint in physBody.Joints)
@@ -189,7 +187,6 @@ namespace Robust.Client.Debugging
drawnJoints.Add(joint);
joint.DebugDraw(drawing, in viewport);
drawing.SetTransform(in Matrix3.Identity);
}
if (worldBox.Contains(mouseWorldPos))
@@ -202,6 +199,17 @@ namespace Robust.Client.Debugging
}
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
private class PhysDrawingAdapter : DebugDrawingHandle
{
private readonly DrawingHandleWorld _handle;
@@ -262,14 +270,14 @@ namespace Robust.Client.Debugging
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityLookup _lookup;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_lookup = lookup;
_entityManager = entityManager;
_eyeManager = eyeManager;
}
@@ -278,17 +286,18 @@ namespace Robust.Client.Debugging
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var viewport = _eyeManager.GetWorldViewport();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
foreach (var entity in _entityManager.GetEntities())
{
var transform = entity.Transform;
if (transform.MapID != _eyeManager.CurrentMap ||
!_eyeManager.GetWorldViewport().Contains(transform.WorldPosition))
{
continue;
}
var center = transform.WorldPosition;
var worldRotation = transform.WorldRotation;
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
var xLine = transform.WorldRotation.RotateVec(Vector2.UnitX);
var yLine = transform.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.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
{
@@ -54,7 +54,7 @@ namespace Robust.Client.Debugging
public void Initialize()
{
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME, HandleDrawRay);
}
private void HandleDrawRay(MsgRay msg)

View File

@@ -11,7 +11,7 @@ namespace Robust.Client
{
public void Main(IMainArgs args)
{
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
Start(args.Args, contentStart: false, args);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace Robust.Client
public static void Main(string[] args)
{
Start(args, new GameControllerOptions());
Start(args);
}
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
{
if (_hasStarted)
{
@@ -40,7 +40,7 @@ namespace Robust.Client
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
{
IoCManager.InitThread();
@@ -51,13 +51,15 @@ namespace Robust.Client
var gc = IoCManager.Resolve<GameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
if(options != null)
gc.Options = options;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
gc.ContentStart = contentStart;
gc.Run(mode, options);
gc.Run(mode);
}
public void OverrideMainLoop(IGameLoop gameLoop)
@@ -65,9 +67,9 @@ namespace Robust.Client
_mainLoop = gameLoop;
}
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(options, logHandlerFactory))
if (!StartupSystemSplash(logHandlerFactory))
{
Logger.Fatal("Failed to start game controller!");
return;

View File

@@ -75,12 +75,14 @@ namespace Robust.Client
public GameControllerOptions Options { get; private set; } = new();
public InitialLaunchState LaunchState { get; private set; } = default!;
public bool LoadConfigAndUserData { get; set; } = true;
public void SetCommandLineArgs(CommandLineArgs args)
{
_commandLineArgs = args;
}
internal bool StartupContinue(DisplayMode displayMode)
private bool StartupContinue(DisplayMode displayMode)
{
_clyde.InitializePostWindowing();
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
@@ -91,9 +93,9 @@ namespace Robust.Client
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
_modLoader.SetEnableSandboxing(false && Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
@@ -194,35 +196,12 @@ namespace Robust.Client
return true;
}
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
{
Options = options;
ReadInitialLaunchState();
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;
}
}
ProgramShared.PrintRuntimeInfo(_logManager.RootSawmill);
// Figure out user data directory.
var userDataDir = GetUserDataDir();
@@ -233,7 +212,7 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
if (Options.LoadConfigAndUserData)
if (LoadConfigAndUserData)
{
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
if (File.Exists(configFile))
@@ -257,13 +236,13 @@ namespace Robust.Client
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
{
@@ -470,7 +449,7 @@ namespace Robust.Client
Clyde,
}
internal void Cleanup()
private void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();

View File

@@ -42,11 +42,6 @@ namespace Robust.Client
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Client";
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
@@ -57,16 +52,6 @@ namespace Robust.Client
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to mount content resources when not on FULL_RELEASE.
/// </summary>
public bool LoadContentResources { get; init; } = true;
/// <summary>
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>

View File

@@ -1,15 +1,12 @@
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
{
internal class ClientComponentFactory : ComponentFactory
public class ClientComponentFactory : ComponentFactory
{
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
: base(typeFactory, reflectionManager, conHost)
public ClientComponentFactory()
{
// Required for the engine to work
RegisterIgnore("KeyBindingInput");
@@ -25,7 +22,6 @@ namespace Robust.Client.GameObjects
RegisterClass<InputComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<ClientOccluderComponent>();
RegisterClass<OccluderTreeComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();

View File

@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
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;
@@ -17,7 +21,6 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -64,7 +67,7 @@ namespace Robust.Client.GameObjects
/// <inheritdoc />
public void SetupNetworking()
{
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
}
public override void TickUpdate(float frameTime, Histogram? histogram)
@@ -109,15 +112,13 @@ namespace Robust.Client.GameObjects
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
if (!netId.HasValue)
if (!component.NetID.HasValue)
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity.Uid;
msg.NetId = netId.Value;
msg.NetId = component.NetID.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
@@ -147,11 +148,7 @@ namespace Robust.Client.GameObjects
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
return;
}
}

View File

@@ -104,7 +104,7 @@ namespace Robust.Client.GameObjects
_appearanceDirty = false;
}
protected override void Initialize()
public override void Initialize()
{
base.Initialize();

View File

@@ -116,7 +116,7 @@ namespace Robust.Client.GameObjects
public MapCoordinates? Position => _eye?.Position;
/// <inheritdoc />
protected override void Initialize()
public override void Initialize()
{
base.Initialize();
@@ -156,7 +156,7 @@ namespace Robust.Client.GameObjects
VisibilityMask = state.VisibilityMask;
}
protected override void OnRemove()
public override void OnRemove()
{
base.OnRemove();

View File

@@ -34,11 +34,11 @@ namespace Robust.Client.GameObjects
if (Owner.Transform.Anchored)
{
AnchorStateChanged();
SnapGridOnPositionChanged();
}
}
public void AnchorStateChanged()
public void SnapGridOnPositionChanged()
{
SendDirty();

View File

@@ -1,13 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -18,12 +15,12 @@ namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
[NetworkedComponent()]
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
internal bool TreeUpdateQueued { get; set; }
@@ -47,29 +44,11 @@ namespace Robust.Client.GameObjects
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
}
set => _enabled = value;
}
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded
{
get => _containerOccluded;
set
{
if (_containerOccluded == value) return;
_containerOccluded = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
}
}
private bool _containerOccluded;
public bool ContainerOccluded { get; set; }
/// <summary>
/// Determines if the light mask should automatically rotate with the entity. (like a flashlight)
@@ -138,13 +117,30 @@ namespace Robust.Client.GameObjects
public bool VisibleNested
{
get => _visibleNested;
set => _visibleNested = value;
set
{
if (_visibleNested == value) return;
_visibleNested = value;
if (value)
{
if (Owner.Transform.Parent == null) return;
_lightOnParent = true;
}
else
{
if (!_lightOnParent) return;
_lightOnParent = false;
}
}
}
[DataField("radius")]
private float _radius = 5f;
[DataField("nestedvisible")]
private bool _visibleNested = true;
private bool _lightOnParent;
[DataField("color")]
private Color _color = Color.White;
[DataField("offset")]
@@ -171,8 +167,6 @@ namespace Robust.Client.GameObjects
get => _radius;
set
{
if (MathHelper.CloseTo(value, _radius)) return;
_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 PointLightRadiusChangedEvent(this));
}
@@ -186,8 +180,17 @@ namespace Robust.Client.GameObjects
Mask = null;
}
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal RenderingTreeComponent? RenderTree { get; set; }
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()
{
@@ -197,13 +200,42 @@ namespace Robust.Client.GameObjects
}
}
protected override void Initialize()
public override void Initialize()
{
base.Initialize();
UpdateMask();
}
protected override void OnRemove()
/// <inheritdoc />
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
if (message is ParentChangedMessage msg)
{
HandleTransformParentChanged(msg);
}
}
private void HandleTransformParentChanged(ParentChangedMessage obj)
{
// TODO: this does not work for things nested multiply layers deep.
if (!VisibleNested)
{
return;
}
if (obj.NewParent != null && obj.NewParent.IsValid())
{
_lightOnParent = true;
}
else
{
_lightOnParent = false;
}
}
public override void OnRemove()
{
base.OnRemove();
@@ -238,9 +270,4 @@ namespace Robust.Client.GameObjects
PointLightComponent = pointLightComponent;
}
}
internal sealed class PointLightUpdateEvent : EntityEventArgs
{
}
}

View File

@@ -42,13 +42,7 @@ namespace Robust.Client.GameObjects
public override bool Visible
{
get => _visible;
set
{
if (_visible == value) return;
_visible = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
}
set => _visible = value;
}
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
@@ -131,8 +125,17 @@ namespace Robust.Client.GameObjects
[DataField("directional")]
private bool _directional = true;
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal RenderingTreeComponent? RenderTree { get; set; } = null;
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
@@ -149,7 +152,7 @@ namespace Robust.Client.GameObjects
}
set
{
if (value == null) return;
if(value == null) return;
Layers.Clear();
foreach (var layerDatum in value)
@@ -159,12 +162,11 @@ namespace Robust.Client.GameObjects
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
try
{
layer.RSI = resource.RSI;
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
}
else
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
@@ -263,7 +265,7 @@ namespace Robust.Client.GameObjects
}
_layerMapShared = true;
QueueUpdateIsInert();
UpdateIsInert();
}
}
@@ -306,30 +308,17 @@ namespace Robust.Client.GameObjects
}
[DataField("sprite", readOnly: true)] private string? rsi;
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new();
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
[DataField("state", readOnly: true)] private string? state;
[DataField("texture", readOnly: true)] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded
{
get => _containerOccluded;
set
{
if (_containerOccluded == value) return;
_containerOccluded = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
}
}
private bool _containerOccluded;
public bool ContainerOccluded { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public bool TreeUpdateQueued { get; set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _inertUpdateQueued;
[ViewVariables(VVAccess.ReadWrite)]
public ShaderInstance? PostShader { get; set; }
@@ -359,13 +348,13 @@ namespace Robust.Client.GameObjects
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
try
{
BaseRSI = resource.RSI;
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
}
else
catch (Exception e)
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
@@ -493,7 +482,7 @@ namespace Robust.Client.GameObjects
public int AddBlankLayer(int? newIndex = null)
{
var layer = new Layer(this) { Visible = false };
var layer = new Layer(this) {Visible = false};
return AddLayer(layer, newIndex);
}
@@ -522,13 +511,13 @@ namespace Robust.Client.GameObjects
public int AddLayer(Texture? texture, int? newIndex = null)
{
var layer = new Layer(this) { Texture = texture };
var layer = new Layer(this) {Texture = texture};
return AddLayer(layer, newIndex);
}
public int AddLayer(RSI.StateId stateId, int? newIndex = null)
{
var layer = new Layer(this) { State = stateId };
var layer = new Layer(this) {State = stateId};
if (BaseRSI != null && BaseRSI.TryGetState(stateId, out var state))
{
layer.AnimationTimeLeft = state.GetDelay(0);
@@ -574,7 +563,7 @@ namespace Robust.Client.GameObjects
public int AddLayer(RSI.StateId stateId, RSI? rsi, int? newIndex = null)
{
var layer = new Layer(this) { State = stateId, RSI = rsi };
var layer = new Layer(this) {State = stateId, RSI = rsi};
if (rsi != null && rsi.TryGetState(stateId, out var state))
{
layer.AnimationTimeLeft = state.GetDelay(0);
@@ -630,7 +619,7 @@ namespace Robust.Client.GameObjects
index = Layers.Count - 1;
}
QueueUpdateIsInert();
UpdateIsInert();
return index;
}
@@ -657,7 +646,7 @@ namespace Robust.Client.GameObjects
}
}
QueueUpdateIsInert();
UpdateIsInert();
}
public void RemoveLayer(object layerKey)
@@ -1299,8 +1288,8 @@ namespace Robust.Client.GameObjects
var layerColor = color * layer.Color;
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
// TODO: Implement layer-specific rotation and scale.
@@ -1320,7 +1309,7 @@ namespace Robust.Client.GameObjects
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
{
var theta = worldAngle.Theta;
var segSize = (MathF.PI * 2) / (numDirections * 2);
var segSize = (MathF.PI*2) / (numDirections * 2);
var segments = (int)(theta / segSize);
var odd = segments % 2;
var result = theta - (segments * segSize) - (odd * segSize);
@@ -1370,6 +1359,18 @@ 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 RenderTreeRemoveSpriteEvent(this, map));
}
}
public void FrameUpdate(float delta)
{
foreach (var t in Layers)
@@ -1420,7 +1421,7 @@ namespace Robust.Client.GameObjects
if (curState == null)
return;
var thestate = (SpriteComponentState)curState;
var thestate = (SpriteComponentState) curState;
Visible = thestate.Visible;
DrawDepth = thestate.DrawDepth;
@@ -1508,20 +1509,8 @@ namespace Robust.Client.GameObjects
};
}
private void QueueUpdateIsInert()
private void UpdateIsInert()
{
if (_inertUpdateQueued)
return;
_inertUpdateQueued = true;
// Yes that null check is valid because of that stupid fucking dummy IEntity.
// Who thought that was a good idea.
Owner?.EntityManager?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
}
internal void DoUpdateIsInert()
{
_inertUpdateQueued = false;
IsInert = true;
foreach (var layer in Layers)
@@ -1871,14 +1860,14 @@ namespace Robust.Client.GameObjects
{
AutoAnimated = value;
_parent.QueueUpdateIsInert();
_parent.UpdateIsInert();
}
public void SetVisible(bool value)
{
Visible = value;
_parent.QueueUpdateIsInert();
_parent.UpdateIsInert();
}
public void SetRsi(RSI? rsi)
@@ -1910,7 +1899,7 @@ namespace Robust.Client.GameObjects
}
}
_parent.QueueUpdateIsInert();
_parent.UpdateIsInert();
}
public void SetState(RSI.StateId stateId)
@@ -1942,7 +1931,7 @@ namespace Robust.Client.GameObjects
AnimationTime = 0;
AnimationTimeLeft = state.GetDelay(0);
_parent.QueueUpdateIsInert();
_parent.UpdateIsInert();
}
public void SetTexture(Texture? texture)
@@ -1950,7 +1939,7 @@ namespace Robust.Client.GameObjects
State = default;
Texture = texture;
_parent.QueueUpdateIsInert();
_parent.UpdateIsInert();
}
public void SetOffset(Vector2 offset)
@@ -2002,13 +1991,13 @@ namespace Robust.Client.GameObjects
switch (layerProp)
{
case "texture":
LayerSetTexture(index, (string)value);
LayerSetTexture(index, (string) value);
return;
case "state":
LayerSetState(index, (string)value);
LayerSetState(index, (string) value);
return;
case "color":
LayerSetColor(index, (Color)value);
LayerSetColor(index, (Color) value);
return;
default:
throw new ArgumentException($"Unknown layer property '{layerProp}'");
@@ -2053,7 +2042,7 @@ namespace Robust.Client.GameObjects
yield break;
}
var dummy = new DummyIconEntity { Prototype = prototype };
var dummy = new DummyIconEntity {Prototype = prototype};
var spriteComponent = dummy.AddComponent<SpriteComponent>();
if (prototype.Components.TryGetValue("Appearance", out _))
@@ -2097,7 +2086,7 @@ namespace Robust.Client.GameObjects
return GetFallbackState(resourceCache);
}
var dummy = new DummyIconEntity { Prototype = prototype };
var dummy = new DummyIconEntity {Prototype = prototype};
var spriteComponent = dummy.AddComponent<SpriteComponent>();
dummy.Delete();
@@ -2134,7 +2123,7 @@ namespace Robust.Client.GameObjects
{
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
_components[typeof(T)] = comp;
comp.Owner = this;
@@ -2168,7 +2157,7 @@ namespace Robust.Client.GameObjects
public T GetComponent<T>()
{
return (T)_components[typeof(T)];
return (T) _components[typeof(T)];
}
public IComponent GetComponent(Type type)
@@ -2180,7 +2169,7 @@ namespace Robust.Client.GameObjects
{
component = null;
if (!_components.TryGetValue(typeof(T), out var value)) return false;
component = (T)value;
component = (T) value;
return true;
}
@@ -2236,14 +2225,4 @@ namespace Robust.Client.GameObjects
}
#endregion
}
internal sealed class SpriteUpdateEvent : EntityEventArgs
{
}
internal struct SpriteUpdateInertEvent
{
public SpriteComponent Sprite;
}
}

View File

@@ -1,57 +0,0 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public sealed class RenderingTreeComponent : Component
{
public override string Name => "RenderingTree";
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SharedBroadphaseSystem _broadPhaseSystem = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
@@ -38,7 +38,7 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadphaseSystem>();
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -254,7 +254,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(entity.Transform.WorldPosition))
{
source.Dispose();
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
Logger.Warning("Can't play positional audio, can't set position.");
return null;
}
@@ -301,7 +301,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
{
source.Dispose();
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
Logger.Warning("Can't play positional audio, can't set position.");
return null;
}

View File

@@ -35,7 +35,7 @@ namespace Robust.Client.GameObjects
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
}
public override void FrameUpdate(float frameTime)
@@ -62,9 +62,9 @@ namespace Robust.Client.GameObjects
}
}
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, AnchorStateChangedEvent args)
private static void HandleSnapGridMove(EntityUid uid, ClientOccluderComponent component, SnapGridPositionChangedEvent args)
{
component.AnchorStateChanged();
component.SnapGridOnPositionChanged();
}
private void HandleDirtyEvent(OccluderDirtyEvent ev)

View File

@@ -1,134 +0,0 @@
#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,84 +0,0 @@
#if DEBUG
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
internal sealed class DebugLightTreeSystem : EntitySystem
{
private DebugLightOverlay? _lightOverlay;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
overlayManager.AddOverlay(_lightOverlay);
}
else
{
overlayManager.RemoveOverlay(_lightOverlay!);
_lightOverlay = null;
}
}
}
private bool _enabled;
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
private RenderingTreeSystem _tree;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
_mapManager = mapManager;
_tree = tree;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
var viewport = _eyeManager.GetWorldViewport();
foreach (var tree in _tree.GetLightTrees(map, viewport))
{
foreach (var light in tree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
if (!aabb.Intersects(viewport)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
}
}
}
}
}
}
#endif

View File

@@ -173,24 +173,4 @@ namespace Robust.Client.GameObjects
AttachedEntity = attachedEntity;
}
}
public class PlayerAttachedEvent : EntityEventArgs
{
public PlayerAttachedEvent(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
public class PlayerDetachedEvent : EntityEventArgs
{
public PlayerDetachedEvent(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -3,11 +3,8 @@ using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Physics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -21,48 +18,23 @@ namespace Robust.Client.GameObjects
[UsedImplicitly]
public sealed class RenderingTreeSystem : EntitySystem
{
internal const string LoggerSawmill = "rendertree";
// Nullspace is not indexed. Keep that in mind.
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
private readonly List<SpriteComponent> _spriteQueue = new();
private readonly List<PointLightComponent> _lightQueue = new();
private HashSet<EntityUid> _checkedChildren = new();
/// <summary>
/// <see cref="CVars.MaxLightRadius"/>
/// </summary>
public float MaxLightRadius { get; private set; }
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
{
if (mapId == MapId.Nullspace) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
}
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
return _gridTrees[map][grid].SpriteTree;
}
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
{
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.SpriteTree;
}
}
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
{
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.LightTree;
}
return _gridTrees[map][grid].LightTree;
}
public override void Initialize()
@@ -74,65 +46,20 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(PhysicsSystem));
_mapManager.MapCreated += MapManagerOnMapCreated;
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
_mapManager.OnGridCreated += MapManagerOnGridCreated;
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
SubscribeLocalEvent<SpriteComponent, MoveEvent>(SpriteMoved);
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
SubscribeLocalEvent<SpriteComponent, RenderTreeRemoveSpriteEvent>(RemoveSprite);
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
SubscribeLocalEvent<PointLightComponent, MoveEvent>(LightMoved);
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
}
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
{
if (component.TreeUpdateQueued) return;
QueueLightUpdate(component);
}
private void HandleSpriteUpdate(EntityUid uid, SpriteComponent component, SpriteUpdateEvent args)
{
if (component.TreeUpdateQueued) return;
QueueSpriteUpdate(component);
}
private void AnythingMoved(MoveEvent args)
{
AnythingMovedSubHandler(args.Sender.Transform);
}
private void AnythingMovedSubHandler(ITransformComponent sender)
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(sender.Owner.Uid) ||
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
// 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)
// Ironically this was lagging the GC lolz
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
@@ -146,22 +73,33 @@ namespace Robust.Client.GameObjects
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)
private void RemoveSprite(EntityUid uid, SpriteComponent component, RenderTreeRemoveSpriteEvent args)
{
ClearSprite(component);
}
private void ClearSprite(SpriteComponent component)
{
if (component.RenderTree == null) return;
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.RenderTree.SpriteTree.Remove(component);
component.RenderTree = null;
component.IntersectingGrids.Clear();
}
private void QueueSpriteUpdate(SpriteComponent component)
@@ -170,6 +108,22 @@ namespace Robust.Client.GameObjects
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
@@ -179,6 +133,11 @@ namespace Robust.Client.GameObjects
QueueLightUpdate(component);
}
private void LightMoved(EntityUid uid, PointLightComponent component, MoveEvent args)
{
QueueLightUpdate(component);
}
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
{
QueueLightUpdate(component);
@@ -196,10 +155,16 @@ namespace Robust.Client.GameObjects
private void ClearLight(PointLightComponent component)
{
if (component.RenderTree == null) return;
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.RenderTree.LightTree.Remove(component);
component.RenderTree = null;
component.IntersectingGrids.Clear();
}
private void QueueLightUpdate(PointLightComponent component)
@@ -208,6 +173,22 @@ namespace Robust.Client.GameObjects
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
@@ -215,23 +196,31 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
_mapManager.MapCreated -= MapManagerOnMapCreated;
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
}
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
{
foreach (var sprite in component.SpriteTree)
foreach (var (_, gridTree) in _gridTrees[e.Map])
{
sprite.RenderTree = null;
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();
}
foreach (var light in component.LightTree)
{
light.RenderTree = null;
}
component.SpriteTree.Clear();
component.LightTree.Clear();
_gridTrees.Remove(e.Map);
}
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
@@ -241,120 +230,166 @@ namespace Robust.Client.GameObjects
return;
}
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
{
{GridId.Invalid, new MapTrees()}
});
}
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
{
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
_gridTrees[mapId].Add(gridId, new MapTrees());
}
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
{
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
var gridTree = _gridTrees[mapId][gridId];
var parent = entity.Transform.Parent?.Owner;
while (true)
foreach (var sprite in gridTree.SpriteTree)
{
if (parent == null) break;
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
parent = parent.Transform.Parent?.Owner;
sprite.IntersectingGrids.Remove(gridId);
}
return null;
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)
{
_checkedChildren.Clear();
foreach (var sprite in _spriteQueue)
{
sprite.TreeUpdateQueued = false;
if (!sprite.Visible || sprite.ContainerOccluded)
{
ClearSprite(sprite);
continue;
}
var oldMapTree = sprite.RenderTree;
var newMapTree = GetRenderTree(sprite.Owner);
// TODO: Temp PVS guard
var worldPos = sprite.Owner.Transform.WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
ClearSprite(sprite);
continue;
}
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
var mapId = sprite.Owner.Transform.MapID;
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
if (sprite.IntersectingMapId != mapId)
{
ClearSprite(sprite);
newMapTree?.SpriteTree.Add(sprite, aabb);
}
else
{
newMapTree?.SpriteTree.Update(sprite, aabb);
}
sprite.RenderTree = newMapTree;
sprite.IntersectingMapId = mapId;
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)
{
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].SpriteTree.Remove(sprite);
}
// 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 light in _lightQueue)
{
light.TreeUpdateQueued = false;
if (!light.Enabled || light.ContainerOccluded)
{
ClearLight(light);
continue;
}
var oldMapTree = light.RenderTree;
var newMapTree = GetRenderTree(light.Owner);
// TODO: Temp PVS guard
var worldPos = light.Owner.Transform.WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
ClearLight(light);
continue;
}
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
// this is fine for now IMO as it's 1 float check for every light that moves
if (light.Radius > MaxLightRadius)
{
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
var mapId = light.Owner.Transform.MapID;
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
if (light.IntersectingMapId != mapId)
{
ClearLight(light);
newMapTree?.LightTree.Add(light, aabb);
}
else
{
newMapTree?.LightTree.Update(light, aabb);
}
light.RenderTree = newMapTree;
light.IntersectingMapId = mapId;
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)
{
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].LightTree.Remove(light);
}
// 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();
_lightQueue.Clear();
}
private sealed class MapTrees
{
public readonly DynamicTree<SpriteComponent> SpriteTree;
public readonly DynamicTree<PointLightComponent> LightTree;
public MapTrees()
{
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
}
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
internal static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
}
}
}
internal class RenderTreeRemoveSpriteEvent : EntityEventArgs
{
public RenderTreeRemoveSpriteEvent(SpriteComponent sprite, MapId map)
{
Sprite = sprite;
Map = map;
}
public SpriteComponent Sprite { get; }
public MapId Map { get; }
}
internal class RenderTreeRemoveLightEvent : EntityEventArgs

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
@@ -17,29 +17,16 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IMapManager _mapManager = default!;
private RenderingTreeSystem _treeSystem = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
public override void Initialize()
{
base.Initialize();
_treeSystem = Get<RenderingTreeSystem>();
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
{
_inertUpdateQueue.Enqueue(ev.Sprite);
}
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
while (_inertUpdateQueue.TryDequeue(out var sprite))
{
sprite.DoUpdateIsInert();
}
// 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);
@@ -50,11 +37,13 @@ namespace Robust.Client.GameObjects
return;
}
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
{
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{
if (value.IsInert)
{
@@ -63,7 +52,7 @@ namespace Robust.Client.GameObjects
value.FrameUpdate(state);
return true;
}, bounds, true);
}, gridBounds, approx: true);
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects

View File

@@ -0,0 +1,17 @@
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,32 +1,29 @@
// ReSharper disable once RedundantUsingDirective
// Used in EXCEPTION_TOLERANCE preprocessor
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
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;
namespace Robust.Client.GameStates
{
/// <inheritdoc />
[UsedImplicitly]
public class ClientGameStateManager : IClientGameStateManager
{
private GameStateProcessor _processor = default!;
@@ -38,8 +35,6 @@ namespace Robust.Client.GameStates
_pendingSystemMessages
= new();
private uint _metaCompNetId;
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
@@ -85,8 +80,8 @@ namespace Robust.Client.GameStates
{
_processor = new GameStateProcessor(_timing);
_network.RegisterNetMessage<MsgState>(HandleStateMessage);
_network.RegisterNetMessage<MsgStateAck>();
_network.RegisterNetMessage<MsgState>(MsgState.NAME, HandleStateMessage);
_network.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME);
_client.RunLevelChanged += RunLevelChanged;
_config.OnValueChanged(CVars.NetInterp, b => _processor.Interpolation = b, true);
@@ -103,12 +98,6 @@ namespace Robust.Client.GameStates
Predicting = _config.GetCVar(CVars.NetPredict);
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
if (!metaId.HasValue)
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
_metaCompNetId = metaId.Value;
}
/// <inheritdoc />
@@ -220,11 +209,6 @@ namespace Robust.Client.GameStates
ResetPredictedEntities(_timing.CurTick);
}
if (!curState.Extrapolated)
{
_processor.UpdateFullRep(curState);
}
// Store last tick we got from the GameStateProcessor.
_lastProcessedTick = _timing.CurTick;
@@ -334,8 +318,6 @@ namespace Robust.Client.GameStates
private void ResetPredictedEntities(GameTick curTick)
{
var bus = (EntityEventBus) _entities.EventBus;
foreach (var entity in _entities.GetEntities())
{
// TODO: 99% there's an off-by-one here.
@@ -353,18 +335,17 @@ namespace Robust.Client.GameStates
}
// TODO: handle component deletions/creations.
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
{
DebugTools.AssertNotNull(netId);
DebugTools.AssertNotNull(comp.NetID);
if (comp.LastModifiedTick < curTick || !last.TryGetValue(netId, out var compState))
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID!.Value, out var compState))
{
continue;
}
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.Name}");
// TODO: Handle interpolation.
bus.RaiseComponentEvent(comp, new ComponentHandleState(compState, null));
comp.HandleComponentState(compState, null);
}
}
@@ -378,22 +359,24 @@ namespace Robust.Client.GameStates
// so that we can later roll back to it (if necessary).
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
foreach (var createdEntity in createdEntities)
{
var compData = new Dictionary<uint, ComponentState>();
outputData.Add(createdEntity, compData);
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
foreach (var component in _componentManager.GetNetComponents(createdEntity))
{
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
var state = component.GetComponentState(player);
if(state.GetType() == typeof(ComponentState))
if (state.GetType() == typeof(ComponentState))
{
continue;
}
compData.Add(netId, state);
compData.Add(state.NetID, state);
}
}
@@ -410,7 +393,7 @@ namespace Robust.Client.GameStates
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
{
_config.TickProcessMessages();
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates);
_mapManager.ApplyGameStatePre(curState.MapData);
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
nextState?.EntityStates);
_players.ApplyPlayerStates(curState.PlayerStates);
@@ -435,17 +418,16 @@ namespace Robust.Client.GameStates
//Known entities
if (_entities.TryGetEntity(es.Uid, out var entity))
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
toApply.Add(entity, (es, null));
}
else //Unknown entities
{
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
var metaState = (MetaDataComponentState?) es.ComponentStates
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
if (metaState == null)
{
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
}
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
@@ -472,20 +454,17 @@ namespace Robust.Client.GameStates
}
}
var bus = (EntityEventBus) _entities.EventBus;
// Make sure this is done after all entities have been instantiated.
foreach (var kvStates in toApply)
{
var ent = kvStates.Key;
var entity = (Entity) ent;
HandleEntityState(entity.EntityManager.ComponentManager, entity, bus, kvStates.Value.Item1,
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
kvStates.Value.Item2);
}
foreach (var id in deletions)
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
_entities.DeleteEntity(id);
}
@@ -547,10 +526,10 @@ namespace Robust.Client.GameStates
return created;
}
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
EntityState? nextState)
{
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
var entityUid = entity.Uid;
if (curState?.ComponentChanges != null)
@@ -566,50 +545,45 @@ namespace Robust.Client.GameStates
}
else
{
//Right now we just assume every state from an unseen entity is added
if (compMan.HasComponent(entityUid, compChange.NetID))
continue;
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
newComp.Owner = entity;
compMan.AddComponent(entity, newComp, true);
compStateWork[compChange.NetID] = (compChange.State, null);
}
}
}
if (curState?.ComponentChanges != null)
if (curState?.ComponentStates != null)
{
foreach (var compChange in curState.ComponentChanges)
foreach (var compState in curState.ComponentStates)
{
compStateWork[compChange.NetID] = (compChange.State, null);
compStateWork[compState.NetID] = (compState, null);
}
}
if (nextState?.ComponentChanges != null)
if (nextState?.ComponentStates != null)
{
foreach (var compState in nextState.ComponentChanges)
foreach (var compState in nextState.ComponentStates)
{
if (compStateWork.TryGetValue(compState.NetID, out var state))
{
compStateWork[compState.NetID] = (state.curState, compState.State);
compStateWork[compState.NetID] = (state.curState, compState);
}
else
{
compStateWork[compState.NetID] = (null, compState.State);
compStateWork[compState.NetID] = (null, compState);
}
}
}
foreach (var (netId, (cur, next)) in compStateWork)
{
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
if (compMan.TryGetComponent(entityUid, netId, out var component))
{
try
{
bus.RaiseComponentEvent(component, new ComponentHandleState(cur, next));
component.HandleComponentState(cur, next);
}
catch (Exception e)

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
@@ -149,6 +149,11 @@ namespace Robust.Client.GameStates
{
Logger.DebugS("net.state", $"Applying State: ext={curState!.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
}
if (!curState!.Extrapolated)
{
UpdateFullRep(curState);
}
}
var cState = curState!;
@@ -157,10 +162,8 @@ namespace Robust.Client.GameStates
return applyNextState;
}
public void UpdateFullRep(GameState state)
private void UpdateFullRep(GameState state)
{
// Logger.Debug($"UPDATE FULL REP: {string.Join(", ", state.EntityStates?.Select(e => e.Uid) ?? Enumerable.Empty<EntityUid>())}");
if (state.FromSequence == GameTick.Zero)
{
// Full state.
@@ -195,10 +198,14 @@ namespace Robust.Client.GameStates
{
compData.Remove(change.NetID);
}
else if (change.State is not null)
{
compData[change.NetID] = change.State;
}
}
}
if (entityState.ComponentStates != null)
{
foreach (var compState in entityState.ComponentStates)
{
compData[compState.NetID] = compState;
}
}
}

View File

@@ -178,7 +178,7 @@ namespace Robust.Client.GameStates
var yPos = 10 + _lineHeight * i;
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
var color = CalcTextColor(ref netEnt);
screenHandle.DrawString(_font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
}
}
@@ -223,6 +223,17 @@ namespace Robust.Client.GameStates
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
baseLine += new Vector2(advance, 0);
}
}
private struct NetEntity
{
public GameTick LastUpdate;

View File

@@ -21,7 +21,6 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
@@ -91,14 +90,17 @@ namespace Robust.Client.GameStates
sb.Append($"\n Changes:");
foreach (var compChange in entState.ComponentChanges)
{
var registration = _componentFactory.GetRegistration(compChange.NetID);
var create = compChange.Created ? 'C' : '\0';
var mod = !(compChange.Created || compChange.Created) ? 'M' : '\0';
var del = compChange.Deleted ? 'D' : '\0';
sb.Append($"\n [{create}{mod}{del}]{compChange.NetID}:{registration.Name}");
var del = compChange.Deleted ? 'D' : 'C';
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
}
}
if(compChange.State is not null)
sb.Append($"\n STATE:{compChange.State.GetType().Name}");
if (entState.ComponentStates is not null)
{
sb.Append($"\n States:");
foreach (var compState in entState.ComponentStates)
{
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
}
}
}
@@ -182,7 +184,7 @@ namespace Robust.Client.GameStates
// Draw size if above average
if (drawSizeThreshold * 1.5 < state.Payload)
{
handle.DrawString(_font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
DrawString(handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
}
// second tick marks
@@ -222,14 +224,14 @@ namespace Robust.Client.GameStates
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
// payload text
handle.DrawString(_font, new Vector2(leftMargin + width, warnYoff), "56K");
handle.DrawString(_font, new Vector2(leftMargin + width, midYoff), "33.6K");
DrawString(handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
DrawString(handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
// interp text info
if(lastLagY != -1)
handle.DrawString(_font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
DrawString(handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
handle.DrawString(_font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
DrawString(handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void DisposeBehavior()
@@ -239,6 +241,17 @@ namespace Robust.Client.GameStates
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
private class NetShowGraphCommand : IConsoleCommand
{
public string Command => "net_graph";

View File

@@ -44,12 +44,8 @@ namespace Robust.Client.Graphics.Clyde
internal bool IsEfxSupported;
private ISawmill _openALSawmill = default!;
private void _initializeAudio()
{
_openALSawmill = Logger.GetSawmill("clyde.oal");
_audioOpenDevice();
// Create OpenAL context.
@@ -78,9 +74,9 @@ namespace Robust.Client.Graphics.Clyde
_alContextExtensions.Add(extension);
}
_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));
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));
}
private void _audioOpenDevice()
@@ -93,7 +89,7 @@ namespace Robust.Client.Graphics.Clyde
_openALDevice = ALC.OpenDevice(preferredDevice);
if (_openALDevice == IntPtr.Zero)
{
_openALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
Logger.WarningS("clyde.oal", "Unable to open preferred audio device '{0}': {1}. Falling back default.",
preferredDevice, ALC.GetError(ALDevice.Null));
_openALDevice = ALC.OpenDevice(null);
@@ -157,7 +153,7 @@ namespace Robust.Client.Graphics.Clyde
// Clear out finalized audio sources.
while (_sourceDisposeQueue.TryDequeue(out var handles))
{
_openALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
Logger.DebugS("clyde.oal", "Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
@@ -167,7 +163,7 @@ namespace Robust.Client.Graphics.Clyde
// Clear out finalized buffered audio sources.
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
{
_openALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
Logger.DebugS("clyde.oal", "Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
@@ -187,6 +183,32 @@ namespace Robust.Client.Graphics.Clyde
if (handles.filterHandle != 0) EFX.DeleteFilter(handles.filterHandle);
}
public AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate)
{
var buffer = AL.GenBuffer();
unsafe
{
fixed (short* ptr = samples)
{
AL.BufferData(
buffer,
channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16,
(IntPtr) ptr,
samples.Length * 2,
sampleRate);
}
}
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
// ReSharper disable once PossibleLossOfFraction
var length = TimeSpan.FromSeconds(samples.Length / channels / (double) sampleRate);
return new AudioStream(handle, length, channels);
}
public void SetMasterVolume(float newVolume)
{
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
@@ -215,24 +237,24 @@ namespace Robust.Client.Graphics.Clyde
return audioSource;
}
private void _checkAlcError(ALDevice device,
private static void _checkAlcError(ALDevice device,
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = ALC.GetError(device);
if (error != AlcError.NoError)
{
_openALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
Logger.ErrorS("clyde.oal", "[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
}
}
private void _checkAlError([CallerMemberName] string callerMember = "",
private static void _checkAlError([CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
{
_openALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
Logger.ErrorS("clyde.oal", "[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
}
}
@@ -334,35 +356,6 @@ 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;
@@ -414,14 +407,14 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.SourcePlay(SourceHandle);
_master._checkAlError();
_checkAlError();
}
public void StopPlaying()
{
_checkDisposed();
AL.SourceStop(SourceHandle);
_master._checkAlError();
_checkAlError();
}
public bool IsPlaying
@@ -440,14 +433,14 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
_master._checkAlError();
_checkAlError();
return ret;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.Looping, value);
_master._checkAlError();
_checkAlError();
}
}
@@ -455,7 +448,7 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
_master._checkAlError();
_checkAlError();
}
public void SetVolume(float decibels)
@@ -469,10 +462,10 @@ namespace Robust.Client.Graphics.Clyde
}
_gain = MathF.Pow(10, decibels / 10);
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_master._checkAlError();
_checkAlError();
}
public void SetVolumeDirect(float scale)
public void SetVolumeDirect(float decibels)
{
_checkDisposed();
var priorOcclusion = 1f;
@@ -481,9 +474,10 @@ namespace Robust.Client.Graphics.Clyde
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = scale;
_gain = decibels;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_master._checkAlError();
_checkAlError();
}
public void SetOcclusion(float blocks)
@@ -500,7 +494,7 @@ namespace Robust.Client.Graphics.Clyde
gain *= gain * gain;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
}
_master._checkAlError();
_checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
@@ -520,7 +514,7 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
_master._checkAlError();
_checkAlError();
}
public bool SetPosition(Vector2 position)
@@ -546,7 +540,7 @@ namespace Robust.Client.Graphics.Clyde
#endif
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
_master._checkAlError();
_checkAlError();
return true;
}
@@ -573,14 +567,14 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
_master._checkAlError();
_checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
_master._checkAlError();
_checkAlError();
}
~AudioSource()
@@ -606,7 +600,7 @@ namespace Robust.Client.Graphics.Clyde
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
_master._audioSources.Remove(SourceHandle);
_master._checkAlError();
_checkAlError();
}
SourceHandle = -1;
@@ -656,7 +650,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
_master._checkAlError();
_checkAlError();
}
public void StopPlaying()
@@ -664,7 +658,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.SourceStop(SourceHandle!.Value);
_master._checkAlError();
_checkAlError();
}
public bool IsPlaying
@@ -690,7 +684,7 @@ namespace Robust.Client.Graphics.Clyde
_mono = false;
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
_master._checkAlError();
_checkAlError();
}
public void SetLooping()
@@ -709,21 +703,7 @@ namespace Robust.Client.Graphics.Clyde
}
_gain = MathF.Pow(10, decibels / 10);
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
_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();
_checkAlError();
}
public void SetOcclusion(float blocks)
@@ -741,7 +721,7 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
}
_master._checkAlError();
_checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
@@ -761,7 +741,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
_master._checkAlError();
_checkAlError();
}
public bool SetPosition(Vector2 position)
@@ -778,7 +758,7 @@ namespace Robust.Client.Graphics.Clyde
_mono = true;
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
_master._checkAlError();
_checkAlError();
return true;
}
@@ -805,7 +785,22 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
_master._checkAlError();
_checkAlError();
}
public void SetVolumeDirect(float masterVolumeDecay)
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = masterVolumeDecay;
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
_checkAlError();
}
public void SetPitch(float pitch)
@@ -813,7 +808,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
_master._checkAlError();
_checkAlError();
}
~BufferedAudioSource()
@@ -844,7 +839,7 @@ namespace Robust.Client.Graphics.Clyde
AL.DeleteSource(SourceHandle.Value);
AL.DeleteBuffers(BufferHandles);
_master._bufferedAudioSources.Remove(SourceHandle.Value);
_master._checkAlError();
_checkAlError();
}
SourceHandle = null;

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Graphics.Clyde
{
static Clyde()
{
if (OperatingSystem.IsWindows() &&
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
{
@@ -28,20 +28,20 @@ namespace Robust.Client.Graphics.Clyde
}
}
if (OperatingSystem.IsWindows())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
NativeLibrary.SetDllImportResolver(typeof(GL).Assembly, (name, assembly, path) =>
{
if (OperatingSystem.IsLinux()
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
&& _dllMapLinux.TryGetValue(name, out var mappedName))
{
return NativeLibrary.Load(mappedName);
}
if (OperatingSystem.IsMacOS()
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
&& _dllMapMacOS.TryGetValue(name, out mappedName))
{
return NativeLibrary.Load(mappedName);

View File

@@ -366,14 +366,33 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
{
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
comp.SpriteTree.QueryAabb(ref list, ((
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
{
Box2 gridBounds;
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
tree.QueryAabb(ref list, ((
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
// TODO: Probably value in storing this as its own DynamicTree
if (value.ContainerOccluded || !value.Visible)
{
return true;
}
var entity = value.Owner;
var transform = entity.Transform;
@@ -385,7 +404,7 @@ namespace Robust.Client.Graphics.Clyde
entry.yWorldPos = worldPos.Y;
return true;
}), bounds, true);
}), gridBounds, approx: true);
}
}

View File

@@ -5,7 +5,6 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -150,16 +149,16 @@ namespace Robust.Client.Graphics.Clyde
// FOV FBO.
_fovRenderTarget = CreateRenderTarget((FovMapSize, 2),
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat },
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat},
nameof(_fovRenderTarget));
if (_hasGLSamplerObjects)
{
_fovFilterSampler = new GLHandle(GL.GenSampler());
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int)All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int)All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int)All.Repeat);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int)All.Repeat);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int) All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int) All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int) All.Repeat);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int) All.Repeat);
CheckGlError();
}
@@ -181,17 +180,20 @@ namespace Robust.Client.Graphics.Clyde
_fovCalculationProgram = _compileProgram(depthVert, depthFrag, attribLocations, "Shadow Depth Program");
var debugShader = _resourceCache.GetResource<ShaderSourceResource>("/Shaders/Internal/depth-debug.swsl");
_fovDebugShaderInstance = (ClydeShaderInstance)InstanceShader(debugShader.ClydeHandle);
_fovDebugShaderInstance = (ClydeShaderInstance) InstanceShader(debugShader.ClydeHandle);
ClydeHandle LoadShaderHandle(string path)
{
if (_resourceCache.TryGetResource(path, out ShaderSourceResource? resource))
try
{
return resource.ClydeHandle;
var shaderSource = _resourceCache.GetResource<ShaderSourceResource>(path);
return shaderSource.ClydeHandle;
}
catch (Exception ex)
{
Logger.Warning($"Can't load shader {path}\n{ex.GetType().Name}: {ex.Message}");
return default;
}
Logger.Warning($"Can't load shader {path}\n");
return default;
}
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
@@ -212,7 +214,7 @@ namespace Robust.Client.Graphics.Clyde
{
// Calculate maximum distance for the projection based on screen size.
var screenSizeCut = viewport.Size / EyeManager.PixelsPerMeter;
var maxDist = (float)Math.Max(screenSizeCut.X, screenSizeCut.Y);
var maxDist = (float) Math.Max(screenSizeCut.X, screenSizeCut.Y);
// FOV is rendered twice.
// Once with back face culling like regular lighting.
@@ -495,16 +497,24 @@ namespace Robust.Client.Graphics.Clyde
GetLightsToRender(MapId map, in Box2 worldBounds)
{
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var state = (this, worldBounds, count: 0);
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
{
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
Box2 gridBounds;
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
{
var transform = light.Owner.Transform;
@@ -514,6 +524,12 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
// TODO: Don't insert into trees for these, same as sprites.
if (!light.Enabled || light.ContainerOccluded)
{
return true;
}
var lightPos = transform.WorldMatrix.Transform(light.Offset);
var circle = new Circle(lightPos, light.Radius);
@@ -528,7 +544,7 @@ namespace Robust.Client.Graphics.Clyde
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}, bounds);
}, gridBounds);
}
if (state.count > _maxLightsPerScene)
@@ -574,7 +590,7 @@ namespace Robust.Client.Graphics.Clyde
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
shader.SetUniformMaybe("size", (Vector2)viewport.WallBleedIntermediateRenderTarget1.Size);
shader.SetUniformMaybe("size", (Vector2) viewport.WallBleedIntermediateRenderTarget1.Size);
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
var size = viewport.WallBleedIntermediateRenderTarget1.Size;
@@ -774,15 +790,26 @@ namespace Robust.Client.Graphics.Clyde
var ii = 0;
var imi = 0;
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, expandedBounds, true))
{
// TODO: I know this doesn't work with rotated grids but when I come back to these I'm adding tests
// because rotation bugs are common.
var treeBounds = expandedBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (!occluderSystem.TryGetOccluderTreeForGrid(map, gridId, out var occluderTree)) continue;
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
Box2 gridBounds;
if (gridId == GridId.Invalid)
{
var occluder = (ClientOccluderComponent)sOccluder;
gridBounds = expandedBounds;
}
else
{
// TODO: Ideally this would clamp to the outer border of what we can see
var grid = _mapManager.GetGrid(gridId);
gridBounds = expandedBounds.Translated(-grid.WorldPosition);
}
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
{
var occluder = (ClientOccluderComponent) sOccluder;
var transform = occluder.Owner.Transform;
if (!occluder.Enabled)
{
@@ -866,11 +893,11 @@ namespace Robust.Client.Graphics.Clyde
// DddD
// HHhh
// deflection
arrayVIBuffer[avi++] = (byte)((((vi + 1) & 2) != 0) ? 0 : 255);
arrayVIBuffer[avi++] = (byte) ((((vi + 1) & 2) != 0) ? 0 : 255);
// height
arrayVIBuffer[avi++] = (byte)(((vi & 2) != 0) ? 0 : 255);
arrayVIBuffer[avi++] = (byte) (((vi & 2) != 0) ? 0 : 255);
}
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort)aiBase);
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort) aiBase);
}
// North face (TL/TR)
@@ -904,12 +931,12 @@ namespace Robust.Client.Graphics.Clyde
arrayMaskBuffer[ami + 3] = new Vector2(blX, blY);
// Generate mask indices.
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort)ami);
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort) ami);
ami += 4;
return true;
}, treeBounds);
}, gridBounds);
}
_occlusionDataLength = ii;
@@ -946,7 +973,7 @@ namespace Robust.Client.Graphics.Clyde
var lightMapSize = GetLightMapSize(viewport.Size);
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
var lightMapColorFormat = _hasGLFloatFramebuffers ? RenderTargetColorFormat.R11FG11FB10F : RenderTargetColorFormat.Rgba8;
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
var lightMapSampleParameters = new TextureSampleParameters {Filter = true};
viewport.LightRenderTarget?.Dispose();
viewport.WallMaskRenderTarget?.Dispose();
@@ -983,14 +1010,14 @@ namespace Robust.Client.Graphics.Clyde
private Vector2i GetLightMapSize(Vector2i screenSize, bool furtherDivide = false)
{
var divider = (float)_lightmapDivider;
var divider = (float) _lightmapDivider;
if (furtherDivide)
{
divider *= 2;
}
var w = (int)Math.Ceiling(screenSize.X / divider);
var h = (int)Math.Ceiling(screenSize.Y / divider);
var w = (int) Math.Ceiling(screenSize.X / divider);
var h = (int) Math.Ceiling(screenSize.Y / divider);
return (w, h);
}
@@ -1016,7 +1043,7 @@ namespace Robust.Client.Graphics.Clyde
// Shadow FBO.
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, _maxLightsPerScene),
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat, Filter = true },
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
nameof(_shadowRenderTarget));
}

View File

@@ -131,7 +131,7 @@ namespace Robust.Client.Graphics.Clyde
if (!succeeded)
{
if (OperatingSystem.IsWindows())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var msgBoxContent = "Failed to create the game window. " +
"This probably means your GPU is too old to play the game. " +
@@ -164,7 +164,7 @@ namespace Robust.Client.Graphics.Clyde
private IEnumerable<Image<Rgba32>> LoadWindowIcons()
{
if (OperatingSystem.IsMacOS())
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Does nothing on macOS so don't bother.
yield break;
@@ -210,11 +210,11 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
}
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
public async Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_windowing);
return _windowing!.WindowCreate(parameters);
return await _windowing!.WindowCreate(parameters);
}
private void DoDestroyWindow(WindowReg reg)

View File

@@ -24,7 +24,6 @@ namespace Robust.Client.Graphics.Clyde
internal sealed partial class Clyde : IClydeInternal, IClydeAudio, IPostInjectInit
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;

View File

@@ -210,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
yield break;
}
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
{
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
{
@@ -218,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
};
_windows.Add(window);
return window;
return Task.FromResult<IClydeWindow>(window);
}
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
@@ -248,10 +248,9 @@ namespace Robust.Client.Graphics.Clyde
return new(default, default, 1, name);
}
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
public AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate)
{
// TODO: Might wanna actually load this so the length gets reported correctly.
return new(default, default, channels, name);
throw new NotImplementedException();
}
public IClydeAudioSource CreateAudioSource(AudioStream stream)
@@ -329,11 +328,6 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void SetVolumeDirect(float scale)
{
// Nada.
}
public void SetOcclusion(float blocks)
{
// Nada.
@@ -348,6 +342,11 @@ namespace Robust.Client.Graphics.Clyde
{
// Nada.
}
public void SetVolumeDirect(float masterVolumeDecay)
{
// Nada.
}
}
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource

View File

@@ -1,4 +1,4 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using System.Threading.Tasks;
using OpenToolkit.GraphicsLibraryFramework;
@@ -13,6 +13,11 @@ namespace Robust.Client.Graphics.Clyde
{
private sealed partial class GlfwWindowingImpl
{
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
// (despite what the docs claim, and yes this makes it useless).
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private bool _windowingRunning;
private ChannelWriter<CmdBase> _cmdWriter = default!;
private ChannelReader<CmdBase> _cmdReader = default!;
@@ -48,10 +53,7 @@ namespace Robust.Client.Graphics.Clyde
while (_windowingRunning)
{
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
// (despite what the docs claim, and yes this makes it useless).
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
if (OperatingSystem.IsMacOS())
if (IsMacOS)
GLFW.WaitEventsTimeout(0.008);
else
GLFW.WaitEvents();
@@ -155,7 +157,7 @@ namespace Robust.Client.Graphics.Clyde
_cmdWriter.TryWrite(cmd);
// Post empty event to unstuck WaitEvents if necessary.
if (!OperatingSystem.IsMacOS())
if (!IsMacOS)
GLFW.PostEmptyEvent();
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading.Tasks;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
@@ -22,7 +21,8 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
private unsafe sealed partial class GlfwWindowingImpl
// Wait for it.
private sealed partial class GlfwWindowingImpl
{
private readonly List<GlfwWindowReg> _windows = new();
@@ -33,25 +33,46 @@ namespace Robust.Client.Graphics.Clyde
private GlfwWindowReg? _mainWindow;
private GlfwBindingsContext _mainGraphicsContext = default!;
private int _nextWindowId = 1;
private static bool _eglLoaded;
public WindowHandle WindowCreate(WindowCreateParameters parameters)
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
{
// tfw await not allowed in unsafe contexts
// GL APIs don't take kindly to making a new window without unbinding the main context. Great.
// Leaving code for async path in, in case it works on like GLX.
var unbindContextAndBlock = true;
DebugTools.AssertNotNull(_mainWindow);
GLFW.MakeContextCurrent(null);
Task<GlfwWindowCreateResult> task;
unsafe
{
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(null);
var task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
}
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
if (unbindContextAndBlock)
{
unsafe
{
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
}
else
{
await task;
}
var (reg, error) = task.Result;
var (reg, error) = await task;
if (reg == null)
{
@@ -62,11 +83,18 @@ namespace Robust.Client.Graphics.Clyde
_clyde.CreateWindowRenderTexture(reg);
_clyde.InitWindowBlitThread(reg);
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
unsafe
{
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
return reg.Handle;
}
}
// Yes, you read that right.
private sealed unsafe partial class GlfwWindowingImpl
{
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
{
var width = _cfg.GetCVar(CVars.DisplayWidth);
@@ -137,21 +165,6 @@ namespace Robust.Client.Graphics.Clyde
WindowCreateParameters parameters,
Window* share)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
SendCmd(new CmdWinCreate(
@@ -461,20 +474,6 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
if (!_eglLoaded && OperatingSystem.IsWindows())
{
// On non-published builds (so, development), GLFW can't find libEGL.dll
// because it'll be in runtimes/<rid>/native/ instead of next to the actual executable.
// We manually preload the library here so that GLFW will find it when it does its thing.
NativeLibrary.TryLoad(
"libEGL.dll",
typeof(Clyde).Assembly,
DllImportSearchPath.SafeDirectories,
out _);
_eglLoaded = true;
}
}
@@ -510,6 +509,7 @@ namespace Robust.Client.Graphics.Clyde
}
return window;
}

View File

@@ -38,7 +38,7 @@ namespace Robust.Client.Graphics.Clyde
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);
uint? WindowGetX11Id(WindowReg window);
WindowHandle WindowCreate(WindowCreateParameters parameters);
Task<WindowHandle> WindowCreate(WindowCreateParameters parameters);
void WindowDestroy(WindowReg reg);
string KeyGetName(Keyboard.Key key);

View File

@@ -1,5 +1,4 @@
using System;
using System.Text;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -40,57 +39,5 @@ namespace Robust.Client.Graphics
DrawPrimitiveTopology type = filled ? DrawPrimitiveTopology.TriangleFan : DrawPrimitiveTopology.LineStrip;
DrawPrimitives(type, buffer, color);
}
/// <summary>
/// Draw a simple string to the screen at the specified position.
/// </summary>
/// <remarks>
/// This method is primarily intended for debug purposes and does not handle things like UI scaling.
/// </remarks>
/// <returns>
/// The space taken up (horizontal and vertical) by the text.
/// </returns>
/// <param name="font">The font to render with.</param>
/// <param name="pos">The top-left corner to start drawing text at.</param>
/// <param name="str">The text to draw.</param>
/// <param name="color">The color of text to draw.</param>
public Vector2 DrawString(Font font, Vector2 pos, string str, Color color)
{
var advanceTotal = Vector2.Zero;
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
var lineHeight = font.GetLineHeight(1);
foreach (var rune in str.EnumerateRunes())
{
if (rune == new Rune('\n'))
{
baseLine.X = pos.X;
baseLine.Y += lineHeight;
advanceTotal.Y += lineHeight;
continue;
}
var advance = font.DrawChar(this, rune, baseLine, 1, color);
advanceTotal.X += advance;
baseLine += new Vector2(advance, 0);
}
return advanceTotal;
}
/// <summary>
/// Draw a simple string to the screen at the specified position.
/// </summary>
/// <remarks>
/// This method is primarily intended for debug purposes and does not handle things like UI scaling.
/// </remarks>
/// <returns>
/// The space taken up (horizontal and vertical) by the text.
/// </returns>
/// <param name="font">The font to render with.</param>
/// <param name="pos">The top-left corner to start drawing text at.</param>
/// <param name="str">The text to draw.</param>
public Vector2 DrawString(Font font, Vector2 pos, string str)
=> DrawString(font, pos, str, Color.White);
}
}

View File

@@ -130,6 +130,6 @@ namespace Robust.Client.Graphics
IEnumerable<IClydeMonitor> EnumerateMonitors();
IClydeWindow CreateWindow(WindowCreateParameters parameters);
Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters);
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.IO;
using Robust.Client.Audio;
namespace Robust.Client.Graphics
@@ -9,7 +8,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);
AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate);
void SetMasterVolume(float newVolume);

View File

@@ -18,9 +18,9 @@ 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);
void SetVolumeDirect(float masterVolumeDecay);
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
internal interface IRenderHandle
public interface IRenderHandle
{
DrawingHandleScreen DrawingHandleScreen { get; }
DrawingHandleWorld DrawingHandleWorld { get; }

View File

@@ -10,7 +10,8 @@ namespace Robust.Client
GameControllerOptions Options { get; }
bool ContentStart { get; set; }
void SetCommandLineArgs(CommandLineArgs args);
void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null);
bool LoadConfigAndUserData { get; set; }
void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null);
void KeyDown(KeyEventArgs keyEvent);
void KeyUp(KeyEventArgs keyEvent);
void TextEntered(TextEventArgs textEvent);

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Shared.Localization;
using Robust.Shared.Map;
namespace Robust.Client.Input
{
@@ -178,9 +179,9 @@ namespace Robust.Client.Input
var locId = $"input-key-{key}";
if (key == Key.LSystem || key == Key.RSystem)
{
if (OperatingSystem.IsWindows())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
locId += "-win";
else if (OperatingSystem.IsMacOS())
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
locId += "-mac";
else
locId += "-linux";

View File

@@ -1,111 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Robust.Client.Map
{
internal class ClientMapManager : MapManager, IClientMapManager
{
public void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates)
[Dependency] private readonly INetManager _netManager = default!;
public void ApplyGameStatePre(GameStateMapData? data)
{
// There was no map data this tick, so nothing to do.
if(data == null)
return;
var createdGrids = data.CreatedGrids != null
? new Dictionary<GridId, GameStateMapData.GridCreationDatum>(data.CreatedGrids)
: null;
// First we need to figure out all the NEW MAPS.
if(data.CreatedMaps != null)
{
DebugTools.Assert(entityStates is not null, "Received new maps, but no entity state.");
foreach (var mapId in data.CreatedMaps)
{
// map already exists from a previous state.
if (_maps.Contains(mapId))
continue;
EntityUid mapEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
if(entityState.ComponentChanges is null)
continue;
foreach (var compChange in entityState.ComponentChanges)
{
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
continue;
mapEuid = entityState.Uid;
goto BreakMapEntSearch;
}
continue;
}
BreakMapEntSearch:
DebugTools.Assert(mapEuid != default, $"Could not find corresponding entity state for new map {mapId}.");
CreateMap(mapId, mapEuid);
CreateMap(mapId);
}
}
// Then make all the grids.
if(data.CreatedGrids != null)
{
DebugTools.Assert(data.GridData is not null, "Received new grids, but GridData was null.");
var gridData = data.GridData != null
? new Dictionary<GridId, GameStateMapData.GridDatum>(data.GridData)
: null;
DebugTools.AssertNotNull(createdGrids);
foreach (var (gridId, creationDatum) in data.CreatedGrids)
{
if (_grids.ContainsKey(gridId))
{
continue;
EntityUid gridEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
if (entityState.ComponentChanges is null)
continue;
foreach (var compState in entityState.ComponentChanges)
{
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
continue;
gridEuid = entityState.Uid;
goto BreakGridEntSearch;
}
}
BreakGridEntSearch:
DebugTools.Assert(gridEuid != default, $"Could not find corresponding entity state for new grid {gridId}.");
MapId gridMapId = default;
foreach (var kvData in data.GridData!)
{
if (kvData.Key != gridId)
continue;
gridMapId = kvData.Value.Coordinates.MapId;
break;
}
DebugTools.Assert(gridMapId != default, $"Could not find corresponding gridData for new grid {gridId}.");
CreateGrid(gridMapId, gridId, creationDatum.ChunkSize, gridEuid);
CreateGrid(gridData![gridId].Coordinates.MapId, gridId, creationDatum.ChunkSize);
}
}
// Process all grid updates.
if(data.GridData != null)
{
SuppressOnTileChanged = true;
// Ok good all the grids and maps exist now.
foreach (var (gridId, gridDatum) in data.GridData)
{
var grid = _grids[gridId];
if (grid.ParentMapId != gridDatum.Coordinates.MapId)
{
@@ -151,9 +112,99 @@ namespace Robust.Client.Map
public void ApplyGameStatePost(GameStateMapData? data)
{
DebugTools.Assert(_netManager.IsClient, "Only the client should call this.");
if(data == null) // if there is no data, there is nothing to do!
return;
// maps created on the client in pre-state are linked to client entities
// resolve new maps with their shared component that the server just gave us
// and delete the client entities
if (data.CreatedMaps != null)
{
foreach (var mapId in data.CreatedMaps)
{
// CreateMap should have set this
DebugTools.Assert(_mapEntities.ContainsKey(mapId));
// this was already linked in a previous state.
if(!_mapEntities[mapId].IsClientSide())
continue;
// get the existing client entity for the map.
var cEntity = EntityManager.GetEntity(_mapEntities[mapId]);
// locate the entity that represents this map that was just sent to us
IEntity? sharedMapEntity = null;
var mapComps = EntityManager.ComponentManager.EntityQuery<IMapComponent>(true);
foreach (var mapComp in mapComps)
{
if (!mapComp.Owner.Uid.IsClientSide() && mapComp.WorldMap == mapId)
{
sharedMapEntity = mapComp.Owner;
_mapEntities[mapId] = mapComp.Owner.Uid;
Logger.DebugS("map", $"Map {mapId} pivoted bound entity from {cEntity.Uid} to {mapComp.Owner.Uid}.");
break;
}
}
// verify shared entity was found (the server sent us one)
DebugTools.AssertNotNull(sharedMapEntity);
DebugTools.Assert(!_mapEntities[mapId].IsClientSide());
// Transfer client child grids made in GameStatePre to the shared component
// so they are not deleted
foreach (var childGridTrans in cEntity.Transform.Children.ToList())
{
childGridTrans.AttachParent(sharedMapEntity!);
}
// remove client entity
var cGridComp = cEntity.GetComponent<IMapComponent>();
cGridComp.ClearMapId();
cEntity.Delete();
}
}
// grids created on the client in pre-state are linked to client entities
// resolve new grids with their shared component that the server just gave us
// and delete the client entities
if (data.CreatedGrids != null)
{
foreach (var kvNewGrid in data.CreatedGrids)
{
var grid = _grids[kvNewGrid.Key];
// this was already linked in a previous state.
if(!grid.GridEntityId.IsClientSide())
continue;
// remove the existing client entity.
var cEntity = EntityManager.GetEntity(grid.GridEntityId);
var cGridComp = cEntity.GetComponent<IMapGridComponent>();
// prevents us from deleting the grid when deleting the grid entity
if(cEntity.Uid.IsClientSide())
cGridComp.ClearGridId();
cEntity.Delete(); // normal entities are already parented to the shared comp, client comp has no children
var gridComps = EntityManager.ComponentManager.EntityQuery<IMapGridComponent>(true);
foreach (var gridComp in gridComps)
{
if (gridComp.GridIndex == kvNewGrid.Key)
{
grid.GridEntityId = gridComp.Owner.Uid;
Logger.DebugS("map", $"Grid {grid.Index} pivoted bound entity from {cEntity.Uid} to {grid.GridEntityId}.");
break;
}
}
DebugTools.Assert(!grid.GridEntityId.IsClientSide());
}
}
if(data.DeletedGrids != null)
{
foreach (var grid in data.DeletedGrids)

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -8,7 +7,6 @@ using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -19,7 +17,7 @@ namespace Robust.Client.Map
[Dependency] private readonly IResourceCache _resourceCache = default!;
private Texture? _tileTextureAtlas;
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2> _tileRegions = new();
@@ -63,7 +61,7 @@ namespace Robust.Client.Map
var row = i / dimensionX;
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
using (var stream = _resourceCache.ContentFileRead($"/Textures/Constructible/Tiles/{def.SpriteName}.png"))
{
image = Image.Load<Rgba32>(stream);
}

View File

@@ -1,4 +1,3 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
@@ -8,7 +7,7 @@ namespace Robust.Client.Map
{
// Two methods here, so that new grids etc can be made BEFORE entities get states applied,
// but old ones can be deleted after.
void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates);
void ApplyGameStatePre(GameStateMapData? data);
void ApplyGameStatePost(GameStateMapData? data);
}
}

View File

@@ -1,8 +1,8 @@
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
namespace Robust.Client.Physics
{
internal sealed class BroadPhaseSystem : SharedBroadphaseSystem
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
{
public override void Initialize()
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
@@ -25,6 +25,7 @@ namespace Robust.Client.Placement
{
public partial class PlacementManager : IPlacementManager, IDisposable
{
[Dependency] public readonly IPhysicsManager PhysicsManager = default!;
[Dependency] private readonly IClientNetManager NetworkManager = default!;
[Dependency] public readonly IPlayerManager PlayerManager = default!;
[Dependency] public readonly IResourceCache ResourceCache = default!;
@@ -155,7 +156,7 @@ namespace Robust.Client.Placement
{
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
NetworkManager.RegisterNetMessage<MsgPlacement>(HandlePlacementMessage);
NetworkManager.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, HandlePlacementMessage);
_modeDictionary.Clear();
foreach (var type in ReflectionManager.GetAllChildren<PlacementMode>())
@@ -504,8 +505,8 @@ namespace Robust.Client.Placement
coordinates = new EntityCoordinates();
return false;
}
coordinates = EntityCoordinates.FromMap(MapManager,
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
coordinates = EntityCoordinates.FromMap(ent.EntityManager, MapManager,
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
return true;
}
}

View File

@@ -7,7 +7,6 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
@@ -233,7 +232,7 @@ namespace Robust.Client.Placement
bounds.Width,
bounds.Height);
return EntitySystem.Get<SharedBroadphaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
return EntitySystem.Get<SharedBroadPhaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
}
protected Vector2 ScreenToWorld(Vector2 point)
@@ -251,7 +250,7 @@ namespace Robust.Client.Placement
var mapCoords = pManager.eyeManager.ScreenToMap(coords.Position);
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var grid))
{
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
return EntityCoordinates.FromMap(pManager.EntityManager, pManager.MapManager, mapCoords);
}
return EntityCoordinates.FromMap(pManager.EntityManager, grid.GridEntityId, mapCoords);

View File

@@ -1,5 +1,5 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Client.Graphics;
namespace Robust.Client.Placement

View File

@@ -1,5 +1,6 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
@@ -70,10 +71,10 @@ namespace Robust.Client.Player
eye.Current = true;
EntityAttached?.Invoke(new EntityAttachedEventArgs(entity));
entity.SendMessage(null, new PlayerAttachedMsg());
// notify ECS Systems
ControlledEntity.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(ControlledEntity));
ControlledEntity.EntityManager.EventBus.RaiseLocalEvent(ControlledEntity.Uid, new PlayerAttachedEvent(ControlledEntity));
}
/// <summary>
@@ -82,13 +83,13 @@ namespace Robust.Client.Player
public void DetachEntity()
{
var previous = ControlledEntity;
if (previous is {Initialized: true, Deleted: false})
if (previous != null && previous.Initialized && !previous.Deleted)
{
previous.GetComponent<EyeComponent>().Current = false;
previous.SendMessage(null, new PlayerDetachedMsg());
// notify ECS Systems
previous.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(null));
previous.EntityManager.EventBus.RaiseLocalEvent(previous.Uid, new PlayerDetachedEvent(previous));
}
ControlledEntity = null;

View File

@@ -1,6 +1,7 @@
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;
@@ -81,8 +82,8 @@ namespace Robust.Client.Player
{
_client.RunLevelChanged += OnRunLevelChanged;
_network.RegisterNetMessage<MsgPlayerListReq>();
_network.RegisterNetMessage<MsgPlayerList>(HandlePlayerList);
_network.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME);
_network.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME, HandlePlayerList);
}
/// <inheritdoc />

View File

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

View File

@@ -463,7 +463,7 @@ namespace Robust.Client.UserInterface
{
}
internal virtual void DrawInternal(IRenderHandle renderHandle)
public virtual void DrawInternal(IRenderHandle renderHandle)
{
Draw(renderHandle.DrawingHandleScreen);
}

View File

@@ -6,31 +6,20 @@ namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// A container that lays out its children sequentially.
/// Use <see cref="VBoxContainer"/> or <see cref="HBoxContainer"/> for an implementation.
/// </summary>
public class BoxContainer : Container
public abstract class BoxContainer : Container
{
private LayoutOrientation _orientation;
public const string StylePropertySeparation = "separation";
private const int DefaultSeparation = 0;
private protected abstract bool Vertical { get; }
/// <summary>
/// Specifies "where" the controls should be laid out.
/// </summary>
public AlignMode Align { get; set; }
private bool Vertical => Orientation == LayoutOrientation.Vertical;
public LayoutOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
private int ActualSeparation
{
get
@@ -248,11 +237,5 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
End = 2
}
public enum LayoutOrientation : byte
{
Horizontal,
Vertical
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.UserInterface.Controls
{
public Label Label { get; }
public Button()
public Button() : base()
{
AddStyleClass(StyleClassButton);
Label = new Label

View File

@@ -14,13 +14,12 @@ namespace Robust.Client.UserInterface.Controls
public Label Label { get; }
public TextureRect TextureRect { get; }
public CheckBox()
public CheckBox() : base()
{
ToggleMode = true;
var hBox = new BoxContainer
var hBox = new HBoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
StyleClasses = { StyleClassCheckBox },
};
AddChild(hBox);

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePseudoClassHover = "hover";
public const string StylePseudoClassDisabled = "disabled";
public ContainerButton()
public ContainerButton() : base()
{
DrawModeChanged();
}

View File

@@ -1,16 +1,10 @@
using System;
namespace Robust.Client.UserInterface.Controls
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Container that lays its children out horizontally: from left to right.
/// </summary>
[Obsolete("Use BoxContainer and set Orientation instead")]
public class HBoxContainer : BoxContainer
{
public HBoxContainer()
{
Orientation = LayoutOrientation.Horizontal;
}
private protected override bool Vertical => false;
}
}

View File

@@ -1,13 +1,7 @@
using System;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Use SplitContainer directly and set Orientation")]
public class HSplitContainer : SplitContainer
{
public HSplitContainer()
{
Orientation = SplitOrientation.Horizontal;
}
private protected sealed override bool Vertical => false;
}
}

View File

@@ -318,8 +318,6 @@ namespace Robust.Client.UserInterface.Controls
{
base.Draw(handle);
var sizeBox = PixelSizeBox;
var font = ActualFont;
var listBg = ActualBackground;
var iconBg = ActualItemBackground;
@@ -348,40 +346,33 @@ namespace Robust.Client.UserInterface.Controls
itemHeight = item.IconSize.Y;
}
itemHeight = Math.Max(itemHeight, font.GetHeight(UIScale));
itemHeight = Math.Max(itemHeight, ActualFont.GetHeight(UIScale));
itemHeight += ActualItemBackground.MinimumSize.Y;
var region = UIBox2.FromDimensions(0, offset, PixelWidth, itemHeight);
item.Region = region;
item.Region = UIBox2.FromDimensions(0, offset, PixelWidth, itemHeight);
if (region.Intersects(sizeBox))
bg.Draw(handle, item.Region.Value);
var contentBox = bg.GetContentBox(item.Region.Value);
var drawOffset = contentBox.TopLeft;
if (item.Icon != null)
{
bg.Draw(handle, item.Region.Value);
var contentBox = bg.GetContentBox(item.Region.Value);
var drawOffset = contentBox.TopLeft;
if (item.Icon != null)
if (item.IconRegion.Size == Vector2.Zero)
{
if (item.IconRegion.Size == Vector2.Zero)
{
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
item.IconModulate);
}
else
{
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
item.IconRegion, item.IconModulate);
}
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size), item.IconModulate);
}
if (item.Text != null)
else
{
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right,
contentBox.Bottom);
DrawTextInternal(handle, item.Text, textBox);
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size), item.IconRegion, item.IconModulate);
}
}
if (item.Text != null)
{
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right, contentBox.Bottom);
DrawTextInternal(handle, item.Text, textBox);
}
offset += itemHeight;
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -14,9 +13,9 @@ namespace Robust.Client.UserInterface.Controls
{
private readonly List<Menu> _menus = new();
private readonly List<MenuBarTopButton> _buttons = new();
private readonly BoxContainer _hBox;
private readonly HBoxContainer _hBox;
private readonly Popup _popup;
private readonly BoxContainer _popupVBox;
private readonly VBoxContainer _popupVBox;
private bool _popupOpen;
public IList<Menu> Menus { get; }
@@ -27,21 +26,13 @@ namespace Robust.Client.UserInterface.Controls
{
Children =
{
(_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (300, 0)
})
(_popupVBox = new VBoxContainer {MinSize = (300, 0)})
}
};
_popup.OnPopupHide += PopupHidden;
UserInterfaceManager.ModalRoot.AddChild(_popup);
Menus = new MenuCollection(this);
AddChild(_hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SeparationOverride = 8
});
AddChild(_hBox = new HBoxContainer {SeparationOverride = 8});
}
private void AddMenu(Menu menu)

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -21,7 +20,7 @@ namespace Robust.Client.UserInterface.Controls
// map from key to buttondata index
private Dictionary<TKey, int> _keyMap = new();
private readonly Popup _popup;
private readonly BoxContainer _popupVBox;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
public event Action<ItemPressedEventArgs>? OnItemSelected;
@@ -61,17 +60,11 @@ namespace Robust.Client.UserInterface.Controls
AddStyleClass(StyleClassButton);
OnPressed += OnPressedInternal;
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
var hBox = new HBoxContainer();
AddChild(hBox);
_popup = new Popup();
_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_popupVBox = new VBoxContainer();
_popup.AddChild(_popupVBox);
_popup.OnPopupHide += OnPopupHide;

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -14,7 +13,7 @@ namespace Robust.Client.UserInterface.Controls
private readonly List<ButtonData> _buttonData = new();
private readonly Dictionary<int, int> _idMap = new();
private readonly Popup _popup;
private readonly BoxContainer _popupVBox;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
private readonly TextureRect _triangle;
@@ -50,17 +49,11 @@ namespace Robust.Client.UserInterface.Controls
Prefix = "";
OnPressed += OnPressedInternal;
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
var hBox = new HBoxContainer();
AddChild(hBox);
_popup = new Popup();
_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_popupVBox = new VBoxContainer();
_popup.AddChild(_popupVBox);
_popup.OnPopupHide += OnPopupHide;

View File

@@ -2,7 +2,6 @@ using System;
using System.Linq;
using System.Collections.Generic;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -35,17 +34,11 @@ namespace Robust.Client.UserInterface.Controls
switch (layout)
{
case RadioOptionsLayout.Vertical:
_container = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_container = new VBoxContainer();
break;
case RadioOptionsLayout.Horizontal:
default:
_container = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
_container = new HBoxContainer();
break;
}

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public SpinBox()
public SpinBox() : base()
{
MouseFilter = MouseFilterMode.Pass;

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
public class SplitContainer : Container
public abstract class SplitContainer : Container
{
/// <summary>
/// Defines how user-initiated moving of the split should work. See documentation
@@ -22,10 +22,11 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public float SplitEdgeSeparation { get; set; }
private protected abstract bool Vertical { get; }
private float _splitCenter;
private SplitState _splitState;
private bool _dragging;
private SplitOrientation _orientation;
// min / max x and y extents in relative virtual pixels of where the split can go regardless
// of anything else.
@@ -34,18 +35,6 @@ namespace Robust.Client.UserInterface.Controls
private float SplitMax =>
Vertical ? Height - (SplitWidth + SplitEdgeSeparation) : Width - (SplitWidth + SplitEdgeSeparation);
private bool Vertical => Orientation == SplitOrientation.Vertical;
public SplitOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
public SplitContainer()
{
MouseFilter = MouseFilterMode.Stop;
@@ -275,11 +264,5 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
Manual = 1
}
public enum SplitOrientation : byte
{
Horizontal,
Vertical
}
}
}

View File

@@ -1,4 +1,4 @@
using Robust.Client.GameObjects;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
@@ -41,14 +41,14 @@ namespace Robust.Client.UserInterface.Controls
return (32, 32) * Scale;
}
internal override void DrawInternal(IRenderHandle renderHandle)
public override void DrawInternal(IRenderHandle renderHandle)
{
if (Sprite == null || Sprite.Deleted)
{
return;
}
renderHandle.DrawEntity(Sprite.Owner, GlobalPixelPosition + PixelSize / 2, Scale * UIScale, OverrideDirection);
renderHandle.DrawEntity(Sprite.Owner, GlobalPixelPosition + PixelSize / 2, Scale, OverrideDirection);
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Localization;
@@ -75,7 +75,7 @@ namespace Robust.Client.UserInterface.Controls
var control = GetChild(tab);
var title = control.GetValue(TabTitleProperty);
return title ?? control.Name ?? Loc.GetString("tab-container-not-tab-title-provided");
return title ?? control.Name ?? Loc.GetString("No title");
}
public static string? GetTabTitle(Control control)

View File

@@ -1,16 +1,10 @@
using System;
namespace Robust.Client.UserInterface.Controls
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Container that lays its children out vertically: from top to bottom.
/// </summary>
[Obsolete("Use BoxContainer and set Orientation instead")]
public class VBoxContainer : BoxContainer
{
public VBoxContainer()
{
Orientation = LayoutOrientation.Vertical;
}
private protected override bool Vertical => true;
}
}

View File

@@ -1,13 +1,7 @@
using System;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Use SplitContainer directly and set Orientation")]
public class VSplitContainer : SplitContainer
{
public VSplitContainer()
{
Orientation = SplitOrientation.Vertical;
}
private protected sealed override bool Vertical => true;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.IoC;
@@ -165,8 +165,6 @@ namespace Robust.Client.UserInterface.CustomControls
protected internal override void MouseExited()
{
base.MouseExited();
if (Resizable && CurrentDrag == DragMode.None)
{
DefaultCursorShape = CursorShape.Arrow;

View File

@@ -1,6 +1,6 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
<BoxContainer Orientation="Vertical">
<VBoxContainer>
<OutputPanel Name="Output" VerticalExpand="True">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252add"
@@ -9,5 +9,5 @@
</OutputPanel.StyleBoxOverride>
</OutputPanel>
<HistoryLineEdit Name="CommandBar" PlaceHolder="{Loc 'console-line-edit-placeholder'}" />
</BoxContainer>
</VBoxContainer>
</Control>

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