Compare commits

...

56 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
metalgearsloth
6c44dd9665 RenderingTreeSystem cleanup (#1759)
* RenderingTreeSystem cleanup

10% less bad but 100% still boilerplate

* Slight changies

* Even better

* New shit just got made

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

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

That's what we got tests for.

* = null instead of .Clear()
2021-05-17 11:01:22 +02:00
Vera Aguilera Puerto
c26ebcbc78 QueueDelete method for entities (#1757) 2021-05-17 09:20:55 +02:00
Vera Aguilera Puerto
8334050411 MIDI improvements (#1756) 2021-05-16 19:28:14 +02:00
Vera Aguilera Puerto
cc67edfc2c Unsubscribe from directed event in UI System. 2021-05-15 12:40:35 +02:00
Vera Aguilera Puerto
943ea9e6c8 Shutdown and dispose of BoundUserInterfaces correctly when the UI component gets shutdown. 2021-05-14 14:25:53 +02:00
Pieter-Jan Briers
3aa5cefe03 Stop using component messages in bound UI code. 2021-05-13 03:29:38 +02:00
Pieter-Jan Briers
c5b34bab69 More obsoletions component messages. 2021-05-13 02:24:35 +02:00
Pieter-Jan Briers
e4f24ec125 Remove IPlayerSession on client. 2021-05-13 02:16:55 +02:00
Pieter-Jan Briers
250971ade7 Remove anchored APIs from physics. 2021-05-13 02:13:18 +02:00
Pieter-Jan Briers
718adf9740 Deprecate component messages harder. 2021-05-13 01:24:20 +02:00
20kdc
5d63aa8c95 Deferred Input Context Switches (fixes spawn entities menu while moving) (#1755) 2021-05-12 18:40:26 +02:00
Vera Aguilera Puerto
17af3612a5 Remove IActorComponent, rename BasicActorComponent to ActorComponent. (#1752)
Rename playerSession to PlayerSession.
2021-05-12 13:40:16 +02:00
Pieter-Jan Briers
d2ecf6b9b1 Remove CollidesOnMask
Only usage was a unit test.
2021-05-12 01:58:12 +02:00
Pieter-Jan Briers
2a1eda0d38 Fix tests 2021-05-12 01:57:03 +02:00
Pieter-Jan Briers
f0180abeb0 Missed a spot while fixing warnings. 2021-05-12 00:26:47 +02:00
Pieter-Jan Briers
720f1b1d05 Fix a bunch of compiler warnings. 2021-05-12 00:16:12 +02:00
ShadowCommander
ae45a96753 Fix error when someone else is shooting outside client PVS 2021-05-11 13:21:30 -07:00
ShadowCommander
74257c72ee Add yaml linting for entity prototype parent (#1749)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-05-11 21:54:51 +02:00
ShadowCommander
cea088f4b4 Change maxlinvelocity and maxangvelocity to take tickrate (#1746) 2021-05-11 21:53:36 +02:00
Pieter-Jan Briers
88678e7d58 Entity localization refactoring (#1753) 2021-05-11 21:52:54 +02:00
Pieter-Jan Briers
d222e25d22 Remove outdated comment. 2021-05-11 20:58:21 +02:00
Pieter-Jan Briers
f0d7fbb6f2 Allow enumeration of monitor video modes.
Basically I kept dumping debugging code for this into Clyde startup to test multi-monitor stuff with GLFW so I guess I'm making it official now.
2021-05-11 20:57:27 +02:00
Pieter-Jan Briers
adec0e71ec I can't believe Zumorica would clean up this class in the last 24 hours. 2021-05-11 13:30:38 +02:00
Pieter-Jan Briers
86d9067d62 Report error if prototype has invalid parent ID. 2021-05-11 13:24:42 +02:00
Vera Aguilera Puerto
b989c9dbee Remove unused IEntityQuery methods from EntitySystem. 2021-05-10 20:35:52 +02:00
Vera Aguilera Puerto
3101ec1985 Mark IEntityQuery and implementing classes as obsolete. 2021-05-10 20:25:54 +02:00
Vera Aguilera Puerto
9ec2da1777 PlayerSession comment cleanup. 2021-05-10 20:20:44 +02:00
Vera Aguilera Puerto
0acd28e8f4 Slight PrototypeManager cleanup. 2021-05-10 20:18:08 +02:00
Vera Aguilera Puerto
c340f50ee5 Remove unused dependencies. 2021-05-10 20:14:15 +02:00
Vera Aguilera Puerto
2b589215aa Improves the Filter API. (#1747)
- Recipients is now of type `IEnumerable<ICommonSession>`.
    - This way, people need to use the Filter API to modify the recipients, instead of being allowed to modify the underlying collection directly.
- Underlying collection is now a HashSet.
    - We want no duplicates at all. This will ensure that while being quite performant.
- Removes `IFilter`.
    - Let's face it, everyone is gonna end up using `Filter` instead as it has a much more convenient API, and nothing else will EVER implement `IFilter`. For this reason, I've just gone ahead and removed it. Every method used `Filter` already, anyway.
- Adds EntitySystem `RaiseNetworkEvent` overload that takes in a `Filter`.
    - This deduplicates a lot of code that enumerated the recipients and raised a network event per each one.
- Made AddPlayersByPvs aware of `net.pvs` and `net.maxupdaterange`, has a range multiplier parameter.
    - Now it will simply add all players to the filter if PVS is disabled, and add all players in range if it's enabled.
    - The range multiplier parameter allows us to make the PVS filter a bit more permissive, and generally better. The range is doubled by default, as it was already before.
- Adds `AddInRange` method to filters.
    - This is useful for the AudioSystem, and the PVS methods use it, too!
- Adds `RemoveByVisibility` method to filters.
    - This will remove all recipients that don't have a visibility flag in their entity's EyeComponent from the filter.
    - (This will be VERY useful for Ghost Pointing, for example)
- Adds `Clone` method to filters.
    - This is useful in cases where methods take in a filter and want to modify it without modifying the original instance. (See AudioSystem)
2021-05-08 22:27:47 +02:00
Vera Aguilera Puerto
490a567ad4 Makes some PlayerManager methods thread-safe. (#1744) 2021-05-08 22:27:26 +02:00
DrSmugleaf
32f0c49484 Fix missing Attribute suffix from some serialization attributes (#1741)
* Fix missing Attribute suffix from some serialization attributes

* Rename DataDefinition namespace to Definition
2021-05-07 14:23:21 +02:00
DrSmugleaf
61113d2434 Fix comments on serialization PropertyAndFieldDefinitionTest 2021-05-04 14:33:29 +02:00
DrSmugleaf
6ba1baa88c Add summary to seed data definition in Robust.Benchmarks 2021-05-04 14:26:07 +02:00
DrSmugleaf
07867acb9a Add serialization writing benchmark, optimize writing (#1739)
* Add serialization write benchmark

* Add baseline test and rename AddNode to Add in mapping extensions

* Optimize serialization writing

* Make reader delegate private

* Unhardcode baseline test
2021-05-04 14:25:13 +02:00
DrSmugleaf
3e28b083b9 Cleanup serialization markdown, add extensions for easier mapping node manipulation (#1738) 2021-05-04 13:01:07 +02:00
DrSmugleaf
68d9e13edf Fix nullability in ViewVariablesInstance onValueChanged tuple action 2021-05-04 12:08:53 +02:00
Pieter-Jan Briers
a0c0a722c9 Don't crash if window creation fails due to selected renderer. 2021-05-03 23:37:41 +02:00
Pieter-Jan Briers
bf4aead1e8 Fix margins on debug console background 2021-05-03 12:43:21 +02:00
Pieter-Jan Briers
39f0d2e974 Fix positioning and size handling when the game started in fullscreen and exited it later.
It now actually makes the non-fullscreen windows sized and placed sanely so it isn't placed offscreen and too big.
2021-05-02 23:30:50 +02:00
Pieter-Jan Briers
b20ae98f21 quit command now does soft exit, hardquit command added. 2021-05-02 23:30:50 +02:00
Acruid
7899780543 Unsubscribing from a directed entity event does not require a delegate argument anymore. 2021-05-02 14:23:48 -07:00
Pieter-Jan Briers
0c9e322b3e Fix macOS builds. 2021-05-02 21:48:58 +02:00
Pieter-Jan Briers
6005208285 Use Fluent localization for key names. 2021-05-02 21:48:50 +02:00
Pieter-Jan Briers
2b15831349 Fix fullscreen startup crashing. 2021-05-02 21:36:08 +02:00
Pieter-Jan Briers
1b2450d1cb Make debug.target_fps cvar for frame graph.
So 60 FPS isn't hardcoded anymore.
2021-05-02 20:55:46 +02:00
230 changed files with 2790 additions and 1813 deletions

View File

@@ -0,0 +1,54 @@
input-key-Escape = Escape
input-key-Control = Control
input-key-Shift = Shift
input-key-Alt = Alt
input-key-Menu = Menu
input-key-F1 = F1
input-key-F2 = F2
input-key-F3 = F3
input-key-F4 = F4
input-key-F5 = F5
input-key-F6 = F6
input-key-F7 = F7
input-key-F8 = F8
input-key-F9 = F9
input-key-F10 = F10
input-key-F11 = F11
input-key-F12 = F12
input-key-F13 = F13
input-key-F14 = F14
input-key-F15 = F15
input-key-Pause = Pause
input-key-Left = Left
input-key-Up = Up
input-key-Down = Down
input-key-Right = Right
input-key-Space = Space
input-key-Return = Return
input-key-NumpadEnter = Num Enter
input-key-BackSpace = Backspace
input-key-Tab = Tab
input-key-PageUp = Page Up
input-key-PageDown = Page Down
input-key-End = End
input-key-Home = Home
input-key-Insert = Insert
input-key-Delete = Delete
input-key-MouseLeft = Mouse Left
input-key-MouseRight = Mouse Right
input-key-MouseMiddle = Mouse Middle
input-key-MouseButton4 = Mouse 4
input-key-MouseButton5 = Mouse 5
input-key-MouseButton6 = Mouse 6
input-key-MouseButton7 = Mouse 7
input-key-MouseButton8 = Mouse 8
input-key-MouseButton9 = Mouse 9
input-key-LSystem-win = Left Win
input-key-RSystem-win = Right Win
input-key-LSystem-mac = Left Cmd
input-key-RSystem-mac = Right Cmd
input-key-LSystem-linux = Left Meta
input-key-RSystem-linux = Right Meta
input-key-unknown = <unknown key>

View File

@@ -1,44 +1,21 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Server;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Copy
{
public class SerializationCopyBenchmark
public class SerializationCopyBenchmark : SerializationBenchmark
{
public SerializationCopyBenchmark()
{
IoCManager.InitThread();
ServerIoC.RegisterIoC();
IoCManager.BuildGraph();
var assemblies = new[]
{
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
};
foreach (var assembly in assemblies)
{
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
}
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
SerializationManager = IoCManager.Resolve<ISerializationManager>();
SerializationManager.Initialize();
InitializeSerialization();
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
@@ -50,8 +27,6 @@ namespace Robust.Benchmarks.Serialization.Copy
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
}
private ISerializationManager SerializationManager { get; }
private const string String = "ABC";
private const int Integer = 1;

View File

@@ -6,6 +6,10 @@ using Robust.Shared.Utility;
namespace Robust.Benchmarks.Serialization.Definitions
{
/// <summary>
/// Arbitrarily large data definition for benchmarks.
/// Taken from content.
/// </summary>
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
{
@@ -96,13 +100,15 @@ namespace Robust.Benchmarks.Serialization.Definitions
Repeat
}
public enum Gas {}
public enum Gas
{
}
[DataDefinition]
public struct SeedChemQuantity
{
[DataField("Min")]
public int Min { get; }
public int Min;
[DataField("Max")]
public int Max;

View File

@@ -2,6 +2,9 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Read
@@ -13,7 +16,7 @@ namespace Robust.Benchmarks.Serialization.Read
InitializeSerialization();
StringDataDefNode = new MappingDataNode();
StringDataDefNode.AddNode(new ValueDataNode("string"), new ValueDataNode("ABC"));
StringDataDefNode.Add(new ValueDataNode("string"), new ValueDataNode("ABC"));
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));

View File

@@ -0,0 +1,98 @@
using System.Globalization;
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Write
{
public class SerializationWriteBenchmark : SerializationBenchmark
{
public SerializationWriteBenchmark()
{
InitializeSerialization();
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
}
private const string String = "ABC";
private const int Integer = 1;
private DataDefinitionWithString DataDefinitionWithString { get; }
private SeedDataDefinition Seed { get; }
[Benchmark]
public DataNode WriteString()
{
return SerializationManager.WriteValue(String);
}
[Benchmark]
public DataNode WriteInteger()
{
return SerializationManager.WriteValue(Integer);
}
[Benchmark]
public DataNode WriteDataDefinitionWithString()
{
return SerializationManager.WriteValue(DataDefinitionWithString);
}
[Benchmark]
public DataNode WriteSeedDataDefinition()
{
return SerializationManager.WriteValue(Seed);
}
[Benchmark]
public DataNode BaselineWriteSeedDataDefinition()
{
var mapping = new MappingDataNode();
mapping.Add("id", Seed.ID);
mapping.Add("name", Seed.Name);
mapping.Add("seedName", Seed.SeedName);
mapping.Add("displayName", Seed.DisplayName);
mapping.Add("productPrototypes", Seed.ProductPrototypes);
mapping.Add("harvestRepeat", Seed.HarvestRepeat.ToString());
mapping.Add("lifespan", Seed.Lifespan.ToString(CultureInfo.InvariantCulture));
mapping.Add("maturation", Seed.Maturation.ToString(CultureInfo.InvariantCulture));
mapping.Add("production", Seed.Production.ToString(CultureInfo.InvariantCulture));
mapping.Add("yield", Seed.Yield.ToString(CultureInfo.InvariantCulture));
mapping.Add("potency", Seed.Potency.ToString(CultureInfo.InvariantCulture));
mapping.Add("growthStages", Seed.GrowthStages.ToString(CultureInfo.InvariantCulture));
mapping.Add("idealLight", Seed.IdealLight.ToString(CultureInfo.InvariantCulture));
mapping.Add("idealHeat", Seed.IdealHeat.ToString(CultureInfo.InvariantCulture));
var chemicals = new MappingDataNode();
foreach (var (name, quantity) in Seed.Chemicals)
{
chemicals.Add(name, new MappingDataNode
{
["Min"] = new ValueDataNode(quantity.Min.ToString(CultureInfo.InvariantCulture)),
["Max"] = new ValueDataNode(quantity.Max.ToString(CultureInfo.InvariantCulture)),
["PotencyDivisor"] = new ValueDataNode(quantity.PotencyDivisor.ToString(CultureInfo.InvariantCulture))
});
}
mapping.Add("chemicals", chemicals);
return mapping;
}
}
}

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using MidiEvent = NFluidsynth.MidiEvent;
namespace Robust.Client.Audio.Midi
@@ -21,6 +22,17 @@ namespace Robust.Client.Audio.Midi
public interface IMidiRenderer : IDisposable
{
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
internal IClydeBufferedAudioSource Source { get; }
/// <summary>
/// Whether this renderer has been disposed or not.
/// </summary>
bool Disposed { get; }
/// <summary>
/// This controls whether the midi file being played will loop or not.
/// </summary>
@@ -110,6 +122,11 @@ namespace Robust.Client.Audio.Midi
/// </summary>
void StopAllNotes();
/// <summary>
/// Render and play MIDI to the audio source.
/// </summary>
internal void Render();
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
@@ -159,7 +176,7 @@ namespace Robust.Client.Audio.Midi
internal void InternalDispose();
}
public class MidiRenderer : IMidiRenderer
internal class MidiRenderer : IMidiRenderer
{
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
@@ -188,8 +205,12 @@ namespace Robust.Client.Audio.Midi
private readonly object _playerStateLock = new();
private SequencerClientId _synthRegister;
public IClydeBufferedAudioSource Source { get; set; }
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
[ViewVariables]
public bool Disposed { get; private set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public byte MidiProgram
{
get => _midiProgram;
@@ -203,6 +224,7 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public byte MidiBank
{
get => _midiBank;
@@ -216,6 +238,7 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public uint MidiSoundfont
{
get => _midiSoundfont;
@@ -229,10 +252,16 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool DisablePercussionChannel { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool DisableProgramChangeEvent { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTick
{
get => _player?.CurrentTick ?? 0;
@@ -243,12 +272,19 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public uint SequencerTick => _sequencer?.Tick ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public bool Mono { get; set; }
[ViewVariables]
public MidiRendererStatus Status { get; private set; } = MidiRendererStatus.None;
[ViewVariables(VVAccess.ReadWrite)]
public bool LoopMidi
{
get => _loopMidi;
@@ -260,10 +296,11 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public IEntity? TrackingEntity { get; set; } = null;
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
internal bool Free { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono = true)
{
@@ -294,7 +331,11 @@ namespace Robust.Client.Audio.Midi
Status = MidiRendererStatus.Input;
StopAllNotes();
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
lock (_playerStateLock)
{
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
}
return true;
}
@@ -332,8 +373,13 @@ namespace Robust.Client.Audio.Midi
{
if (Status != MidiRendererStatus.Input) return false;
Status = MidiRendererStatus.None;
_driver?.Dispose();
_driver = null;
lock (_playerStateLock)
{
_driver?.Dispose();
_driver = null;
}
StopAllNotes();
return true;
}
@@ -357,7 +403,8 @@ namespace Robust.Client.Audio.Midi
public void StopAllNotes()
{
_synth.AllNotesOff(-1);
lock(_playerStateLock)
_synth.AllNotesOff(-1);
}
public void LoadSoundfont(string filename, bool resetPresets = false)
@@ -372,7 +419,12 @@ namespace Robust.Client.Audio.Midi
public event Action<Shared.Audio.Midi.MidiEvent>? OnMidiEvent;
public event Action? OnMidiPlayerFinished;
internal void Render(int length = SampleRate / 250)
void IMidiRenderer.Render()
{
Render();
}
private void Render(int length = SampleRate / 250)
{
if (Disposed) return;
@@ -452,6 +504,7 @@ namespace Robust.Client.Audio.Midi
var timestamp = SequencerTick;
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
midiEv.Tick = timestamp;
midiEvent.Dispose();
SendMidiEvent(midiEv);
return 0;
}
@@ -462,6 +515,7 @@ namespace Robust.Client.Audio.Midi
var timestamp = SequencerTick;
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
midiEv.Tick = timestamp;
midiEvent.Dispose();
SendMidiEvent(midiEv);
return 0;
}

View File

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

View File

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

View File

@@ -25,6 +25,34 @@ namespace Robust.Client.Console.Commands
}
}
[UsedImplicitly]
public sealed class MonitorInfoCommand : IConsoleCommand
{
public string Command => "monitorinfo";
public string Description => "";
public string Help => "Usage: monitorinfo <id>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
{
shell.WriteError("Expected one argument.");
return;
}
var clyde = IoCManager.Resolve<IClyde>();
var monitor = clyde.EnumerateMonitors().Single(c => c.Id == int.Parse(args[0]));
shell.WriteLine($"{monitor.Id}: {monitor.Name}");
shell.WriteLine($"Video modes:");
foreach (var mode in monitor.VideoModes)
{
shell.WriteLine($" {mode.Width}x{mode.Height} {mode.RefreshRate} Hz {mode.RedBits}/{mode.GreenBits}/{mode.BlueBits}");
}
}
}
[UsedImplicitly]
public sealed class SetMonitorCommand : IConsoleCommand
{

View File

@@ -1,11 +1,12 @@
using System;
using Robust.Shared.Console;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
class QuitCommand : IConsoleCommand
class HardQuitCommand : IConsoleCommand
{
public string Command => "quit";
public string Command => "hardquit";
public string Description => "Kills the game client instantly.";
public string Help => "Kills the game client instantly, leaving no traces. No telling the server goodbye";
@@ -14,4 +15,16 @@ namespace Robust.Client.Console.Commands
Environment.Exit(0);
}
}
class QuitCommand : IConsoleCommand
{
public string Command => "quit";
public string Description => "Shuts down the game client gracefully.";
public string Help => "Properly shuts down the game client, notifying the connected server and such.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
IoCManager.Resolve<IGameController>().Shutdown("quit command used");
}
}
}

View File

@@ -30,7 +30,8 @@ namespace Robust.Client.Console
var console = new ScriptConsoleServer(this, session);
_activeConsoles.Add(session, console);
console.Open();
// FIXME: When this is Open(), resizing the window will cause its position to get NaN'd.
console.OpenCentered();
}
private void ReceiveScriptResponse(MsgScriptResponse message)

View File

@@ -93,7 +93,7 @@ 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(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
{
@@ -125,7 +125,6 @@ namespace Robust.Client
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
IoCManager.Resolve<IEntityLookup>().Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();

View File

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

View File

@@ -109,6 +109,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
if (!component.NetID.HasValue)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,17 +41,6 @@ namespace Robust.Client.GameObjects
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<PlayAudioEntityMessage>();
UnsubscribeNetworkEvent<PlayAudioGlobalMessage>();
UnsubscribeNetworkEvent<PlayAudioPositionalMessage>();
UnsubscribeNetworkEvent<StopAudioMessageClient>();
UnsubscribeLocalEvent<SoundSystem.QueryAudioSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
{
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);

View File

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

View File

@@ -67,9 +67,10 @@ namespace Robust.Client.GameObjects
//Create effect from creation message
var effect = new Effect(message, resourceCache, _mapManager, _entityManager);
effect.Deathtime = gameTiming.CurTime + message.LifeTime;
if (effect.AttachedEntityUid != null)
if (effect.AttachedEntityUid != null
&& _entityManager.TryGetEntity(effect.AttachedEntityUid.Value, out var attachedEntity))
{
effect.AttachedEntity = _entityManager.GetEntity(effect.AttachedEntityUid.Value);
effect.AttachedEntity = attachedEntity;
}
_Effects.Add(effect);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -183,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);
@@ -439,6 +465,21 @@ namespace Robust.Client.Graphics.Clyde
_checkAlError();
}
public void SetVolumeDirect(float decibels)
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = decibels;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_checkAlError();
}
public void SetOcclusion(float blocks)
{
_checkDisposed();
@@ -747,6 +788,21 @@ namespace Robust.Client.Graphics.Clyde
_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)
{
_checkDisposed();

View File

@@ -470,18 +470,20 @@ namespace Robust.Client.Graphics.Clyde
private sealed class MonitorHandle : IClydeMonitor
{
public MonitorHandle(int id, string name, Vector2i size, int refreshRate)
public MonitorHandle(int id, string name, Vector2i size, int refreshRate, VideoMode[] videoModes)
{
Id = id;
Name = name;
Size = size;
RefreshRate = refreshRate;
VideoModes = videoModes;
}
public int Id { get; }
public string Name { get; }
public Vector2i Size { get; }
public int RefreshRate { get; }
public IEnumerable<VideoMode> VideoModes { get; }
}
private abstract class MonitorReg

View File

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

View File

@@ -48,14 +48,14 @@ namespace Robust.Client.Graphics.Clyde
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<WindowClosedEventArgs>? CloseWindow;
public event Action<WindowDestroyedEventArgs>? DestroyWindow;
public event Action<TextEventArgs>? TextEntered { add { } remove { } }
public event Action<MouseMoveEventArgs>? MouseMove { add { } remove { } }
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave { add { } remove { } }
public event Action<KeyEventArgs>? KeyUp { add { } remove { } }
public event Action<KeyEventArgs>? KeyDown { add { } remove { } }
public event Action<MouseWheelEventArgs>? MouseWheel { add { } remove { } }
public event Action<WindowClosedEventArgs>? CloseWindow { add { } remove { } }
public event Action<WindowDestroyedEventArgs>? DestroyWindow { add { } remove { } }
public Texture GetStockTexture(ClydeStockTexture stockTexture)
{
@@ -248,6 +248,11 @@ namespace Robust.Client.Graphics.Clyde
return new(default, default, 1, name);
}
public AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate)
{
throw new NotImplementedException();
}
public IClydeAudioSource CreateAudioSource(AudioStream stream)
{
return DummyAudioSource.Instance;
@@ -337,6 +342,11 @@ namespace Robust.Client.Graphics.Clyde
{
// Nada.
}
public void SetVolumeDirect(float masterVolumeDecay)
{
// Nada.
}
}
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource
@@ -594,7 +604,7 @@ namespace Robust.Client.Graphics.Clyde
public bool IsVisible { get; set; } = true;
public Vector2 ContentScale => Vector2.One;
public bool DisposeOnClose { get; set; }
public event Action<WindowClosedEventArgs>? Closed;
public event Action<WindowClosedEventArgs>? Closed { add { } remove { } }
public void MaximizeOnMonitor(IClydeMonitor monitor)
{

View File

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

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Client.Input;
using Robust.Shared.Localization;
@@ -47,17 +48,18 @@ namespace Robust.Client.Graphics.Clyde
public string KeyGetName(Keyboard.Key key)
{
var name = Keyboard.GetSpecialKeyName(key);
if (_printableKeyNameMap.TryGetValue(key, out var name))
{
var textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;
return textInfo.ToTitleCase(name);
}
name = Keyboard.GetSpecialKeyName(key, _loc);
if (name != null)
{
return Loc.GetString(name);
}
if (_printableKeyNameMap.TryGetValue(key, out name))
{
return name;
}
return Loc.GetString("<unknown key>");
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Shared.Utility;
using GlfwVideoMode = OpenToolkit.GraphicsLibraryFramework.VideoMode;
namespace Robust.Client.Graphics.Clyde
{
@@ -18,7 +20,6 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<int, WinThreadMonitorReg> _winThreadMonitors = new();
// Can't use ClydeHandle because it's 64 bit.
// TODO: this should be MONITOR ID.
private int _nextMonitorId = 1;
private int _primaryMonitorId;
private readonly Dictionary<int, GlfwMonitorReg> _monitors = new();
@@ -35,8 +36,11 @@ namespace Robust.Client.Graphics.Clyde
var primaryMonitor = GLFW.GetPrimaryMonitor();
var up = GLFW.GetMonitorUserPointer(primaryMonitor);
_primaryMonitorId = (int) up;
ProcessEvents();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void WinThreadSetupMonitor(Monitor* monitor)
{
var id = _nextMonitorId++;
@@ -46,12 +50,31 @@ namespace Robust.Client.Graphics.Clyde
var name = GLFW.GetMonitorName(monitor);
var videoMode = GLFW.GetVideoMode(monitor);
var modesPtr = GLFW.GetVideoModesRaw(monitor, out var modeCount);
var modes = new VideoMode[modeCount];
for (var i = 0; i < modes.Length; i++)
{
modes[i] = ConvertVideoMode(modesPtr[i]);
}
GLFW.SetMonitorUserPointer(monitor, (void*) id);
_winThreadMonitors.Add(id, new WinThreadMonitorReg {Ptr = monitor});
SendEvent(new EventMonitorSetup(id, name, *videoMode));
SendEvent(new EventMonitorSetup(id, name, ConvertVideoMode(*videoMode), modes));
}
private static VideoMode ConvertVideoMode(in GlfwVideoMode mode)
{
return new()
{
Width = (ushort) mode.Width,
Height = (ushort) mode.Height,
RedBits = (byte) mode.RedBits,
RefreshRate = (ushort) mode.RefreshRate,
GreenBits = (byte) mode.GreenBits,
BlueBits = (byte) mode.BlueBits,
};
}
private void ProcessSetupMonitor(EventMonitorSetup ev)
@@ -59,8 +82,9 @@ namespace Robust.Client.Graphics.Clyde
var impl = new MonitorHandle(
ev.Id,
ev.Name,
(ev.Mode.Width, ev.Mode.Height),
ev.Mode.RefreshRate);
(ev.CurrentMode.Width, ev.CurrentMode.Height),
ev.CurrentMode.RefreshRate,
ev.AllModes);
_clyde._monitorHandles.Add(impl);
_monitors[ev.Id] = new GlfwMonitorReg

View File

@@ -210,7 +210,8 @@ namespace Robust.Client.Graphics.Clyde
(
int Id,
string Name,
VideoMode Mode
VideoMode CurrentMode,
VideoMode[] AllModes
) : EventBase;
private record EventMonitorDestroy

View File

@@ -1,4 +1,5 @@
using System.Threading.Channels;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using System.Threading.Tasks;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Shared;
@@ -12,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!;
@@ -47,7 +53,10 @@ namespace Robust.Client.Graphics.Clyde
while (_windowingRunning)
{
GLFW.WaitEvents();
if (IsMacOS)
GLFW.WaitEventsTimeout(0.008);
else
GLFW.WaitEvents();
while (_cmdReader.TryRead(out var cmd))
{
@@ -148,7 +157,8 @@ namespace Robust.Client.Graphics.Clyde
_cmdWriter.TryWrite(cmd);
// Post empty event to unstuck WaitEvents if necessary.
GLFW.PostEmptyEvent();
if (!IsMacOS)
GLFW.PostEmptyEvent();
}
private void SendEvent(EventBase ev)

View File

@@ -99,21 +99,26 @@ namespace Robust.Client.Graphics.Clyde
{
var width = _cfg.GetCVar(CVars.DisplayWidth);
var height = _cfg.GetCVar(CVars.DisplayHeight);
var prevWidth = width;
var prevHeight = height;
IClydeMonitor? monitor = null;
var fullscreen = false;
if (_clyde._windowMode == WindowMode.Fullscreen)
{
monitor = _monitors[_primaryMonitorId].Handle;
width = monitor.Size.X;
height = monitor.Size.Y;
fullscreen = true;
}
var parameters = new WindowCreateParameters
{
Width = width,
Height = height,
Monitor = monitor
Monitor = monitor,
Fullscreen = fullscreen
};
var windowTask = SharedWindowCreate(renderer, parameters, null);
@@ -133,6 +138,12 @@ namespace Robust.Client.Graphics.Clyde
_mainWindow = reg;
reg.IsMainWindow = true;
if (fullscreen)
{
reg.PrevWindowSize = (prevWidth, prevHeight);
reg.PrevWindowPos = (50, 50);
}
UpdateVSync();
error = null;
@@ -264,7 +275,7 @@ namespace Robust.Client.Graphics.Clyde
(Window*) cmd.Window,
monitorPtr,
cmd.X, cmd.Y,
cmd.W, cmd.W,
cmd.W, cmd.H,
cmd.RefreshRate
);
}
@@ -481,6 +492,10 @@ namespace Robust.Client.Graphics.Clyde
parameters.Fullscreen ? monitor : null,
contextShare);
// Check if window failed to create.
if (window == null)
return null;
if (parameters.Maximized)
{
GLFW.GetMonitorPos(monitor, out var x, out var y);

View File

@@ -4,6 +4,7 @@ using OpenToolkit.GraphicsLibraryFramework;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
namespace Robust.Client.Graphics.Clyde
@@ -14,6 +15,7 @@ namespace Robust.Client.Graphics.Clyde
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
private readonly Clyde _clyde;

View File

@@ -8,6 +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(short[] samples, int channels, int sampleRate);
void SetMasterVolume(float newVolume);

View File

@@ -21,5 +21,6 @@ namespace Robust.Client.Graphics
void SetOcclusion(float blocks);
void SetPlaybackPosition(float seconds);
void SetVelocity(Vector2 velocity);
void SetVolumeDirect(float masterVolumeDecay);
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Maths;
using System.Collections.Generic;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
@@ -14,5 +15,7 @@ namespace Robust.Client.Graphics
string Name { get; }
Vector2i Size { get; }
int RefreshRate { get; }
IEnumerable<VideoMode> VideoModes { get; }
}
}

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

@@ -0,0 +1,12 @@
namespace Robust.Client.Graphics
{
public struct VideoMode
{
public ushort Width;
public ushort Height;
public ushort RefreshRate;
public byte RedBits;
public byte BlueBits;
public byte GreenBits;
}
}

View File

@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Shared.Localization;
using Robust.Shared.Map;
namespace Robust.Client.Input
{
@@ -172,86 +174,23 @@ namespace Robust.Client.Input
/// Gets a "nice" version of special unprintable keys such as <see cref="Key.Escape"/>.
/// </summary>
/// <returns><see langword="null"/> if there is no nice version of this special key.</returns>
internal static string? GetSpecialKeyName(Key key)
internal static string? GetSpecialKeyName(Key key, ILocalizationManager loc)
{
if (_keyNiceNameMap.TryGetValue(key, out var val))
var locId = $"input-key-{key}";
if (key == Key.LSystem || key == Key.RSystem)
{
return val;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
locId += "-win";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
locId += "-mac";
else
locId += "-linux";
}
return null;
}
if (loc.TryGetString(locId, out var name))
return name;
private static readonly Dictionary<Key, string> _keyNiceNameMap;
static Keyboard()
{
_keyNiceNameMap = new Dictionary<Key, string>
{
{Key.Escape, "Escape"},
{Key.Control, "Control"},
{Key.Shift, "Shift"},
{Key.Alt, "Alt"},
{Key.Menu, "Menu"},
{Key.F1, "F1"},
{Key.F2, "F2"},
{Key.F3, "F3"},
{Key.F4, "F4"},
{Key.F5, "F5"},
{Key.F6, "F6"},
{Key.F7, "F7"},
{Key.F8, "F8"},
{Key.F9, "F9"},
{Key.F10, "F10"},
{Key.F11, "F11"},
{Key.F12, "F12"},
{Key.F13, "F13"},
{Key.F14, "F14"},
{Key.F15, "F15"},
{Key.Pause, "Pause"},
{Key.Left, "Left"},
{Key.Up, "Up"},
{Key.Down, "Down"},
{Key.Right, "Right"},
{Key.Space, "Space"},
{Key.Return, "Return"},
{Key.NumpadEnter, "Num Enter"},
{Key.BackSpace, "Backspace"},
{Key.Tab, "Tab"},
{Key.PageUp, "Page Up"},
{Key.PageDown, "Page Down"},
{Key.End, "End"},
{Key.Home, "Home"},
{Key.Insert, "Insert"},
{Key.Delete, "Delete"},
{Key.MouseLeft, "Mouse Left"},
{Key.MouseRight, "Mouse Right"},
{Key.MouseMiddle, "Mouse Middle"},
{Key.MouseButton4, "Mouse 4"},
{Key.MouseButton5, "Mouse 5"},
{Key.MouseButton6, "Mouse 6"},
{Key.MouseButton7, "Mouse 7"},
{Key.MouseButton8, "Mouse 8"},
{Key.MouseButton9, "Mouse 9"},
};
// Have to adjust system key name depending on platform.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_keyNiceNameMap.Add(Key.LSystem, "Left Cmd");
_keyNiceNameMap.Add(Key.RSystem, "Right Cmd");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_keyNiceNameMap.Add(Key.LSystem, "Left Win");
_keyNiceNameMap.Add(Key.RSystem, "Right Win");
}
else
{
_keyNiceNameMap.Add(Key.LSystem, "Left Meta");
_keyNiceNameMap.Add(Key.RSystem, "Right Meta");
}
return loc.GetString("input-key-unknown");
}
}
}

View File

@@ -16,11 +16,12 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.Core;
@@ -145,13 +146,13 @@ namespace Robust.Client.Input
.Where(p => _bindingsByFunction[p].Count == 0)
.ToArray();
mapping.AddNode("version", new ValueDataNode("1"));
mapping.AddNode("binds", serializationManager.WriteValue(modifiedBindings));
mapping.AddNode("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
mapping.Add("version", new ValueDataNode("1"));
mapping.Add("binds", serializationManager.WriteValue(modifiedBindings));
mapping.Add("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
var path = new ResourcePath(KeybindsPath);
using var writer = new StreamWriter(_resourceMan.UserData.Create(path));
var stream = new YamlStream {new(mapping.ToMappingNode())};
var stream = new YamlStream {new(mapping.ToYaml())};
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
}
@@ -333,6 +334,7 @@ namespace Robust.Client.Input
{
// christ this crap *is* re-entrant thanks to PlacementManager and
// I honestly have no idea what the best solution here is.
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
try
@@ -340,6 +342,8 @@ namespace Robust.Client.Input
// This is terrible but anyways.
// This flag keeps track of "did a viewport fire the key up for us" so we know we don't do it again.
_currentlyFindingViewport = true;
// And this stops context switches from causing crashes
Contexts.DeferringEnabled = true;
binding.State = state;
@@ -360,12 +364,14 @@ namespace Robust.Client.Input
finally
{
_currentlyFindingViewport = false;
Contexts.DeferringEnabled = false;
}
}
public void ViewportKeyEvent(Control? viewport, BoundKeyEventArgs eventArgs)
{
_currentlyFindingViewport = false;
Contexts.DeferringEnabled = false;
var cmd = GetInputCommand(eventArgs.Function);
// TODO: Allow input commands to still get forwarded to server if necessary.
@@ -453,7 +459,7 @@ namespace Robust.Client.Input
var robustMapping = mapping.ToDataNode() as MappingDataNode;
if (robustMapping == null) throw new InvalidOperationException();
if (robustMapping.TryGetNode("binds", out var BaseKeyRegsNode))
if (robustMapping.TryGet("binds", out var BaseKeyRegsNode))
{
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
@@ -482,7 +488,7 @@ namespace Robust.Client.Input
}
}
if (userData && robustMapping.TryGetNode("leaveEmpty", out var node))
if (userData && robustMapping.TryGet("leaveEmpty", out var node))
{
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ namespace Robust.Client.Player
/// <summary>
/// Active sessions of connected clients to the server.
/// </summary>
private readonly Dictionary<NetUserId, IPlayerSession> _sessions = new();
private readonly Dictionary<NetUserId, ICommonSession> _sessions = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions
@@ -69,10 +69,10 @@ namespace Robust.Client.Player
/// <inheritdoc />
[ViewVariables]
IEnumerable<IPlayerSession> IPlayerManager.Sessions => _sessions.Values;
IEnumerable<ICommonSession> IPlayerManager.Sessions => _sessions.Values;
/// <inheritdoc />
public IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict => _sessions;
public IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict => _sessions;
/// <inheritdoc />
public event EventHandler? PlayerListUpdated;

View File

@@ -5,9 +5,8 @@ using Robust.Shared.Players;
namespace Robust.Client.Player
{
internal sealed class PlayerSession : IPlayerSession
internal sealed class PlayerSession : ICommonSession
{
/// <inheritdoc />
internal SessionStatus Status { get; set; } = SessionStatus.Connecting;
/// <inheritdoc />
@@ -26,17 +25,15 @@ namespace Robust.Client.Player
/// <inheritdoc />
public NetUserId UserId { get; }
/// <inheritdoc cref="IPlayerSession" />
internal string Name { get; set; } = "<Unknown>";
/// <inheritdoc cref="IPlayerSession" />
/// <inheritdoc />
string ICommonSession.Name
{
get => this.Name;
set => this.Name = value;
}
/// <inheritdoc />
internal short Ping { get; set; }
/// <inheritdoc />
@@ -51,7 +48,7 @@ namespace Robust.Client.Player
/// <summary>
/// Creates an instance of a PlayerSession.
/// </summary
/// </summary>
public PlayerSession(NetUserId user)
{
UserId = user;

View File

@@ -6,7 +6,9 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Client.Serialization
@@ -19,7 +21,7 @@ namespace Robust.Client.Serialization
bool skipHook,
ISerializationContext? context = null)
{
if (!node.TryGetNode("type", out var typeNode))
if (!node.TryGet("type", out var typeNode))
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
if (typeNode is not ValueDataNode typeValueDataNode)
@@ -32,7 +34,7 @@ namespace Robust.Client.Serialization
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
var newNode = (MappingDataNode)node.Copy();
newNode.RemoveNode("type");
newNode.Remove("type");
return serializationManager.Read(type, newNode, context, skipHook);
}
@@ -40,7 +42,7 @@ namespace Robust.Client.Serialization
IDependencyCollection dependencies,
ISerializationContext? context)
{
if (!node.TryGetNode("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
if (!node.TryGet("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
{
return new ErrorNode(node, "Missing/Invalid type", true);
}
@@ -53,14 +55,14 @@ namespace Robust.Client.Serialization
return new ErrorNode(node, $"Failed to resolve type: {valueNode.Value}", true);
}
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().RemoveNode("type"));
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().Remove("type"));
}
public DataNode Write(ISerializationManager serializationManager, AppearanceVisualizer value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var mapping = serializationManager.WriteValueAs<MappingDataNode>(value.GetType(), value, alwaysWrite, context);
mapping.AddNode("type", new ValueDataNode(value.GetType().Name));
mapping.Add("type", new ValueDataNode(value.GetType().Name));
return mapping;
}

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

@@ -41,7 +41,7 @@ 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)
{

View File

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

View File

@@ -3,7 +3,9 @@
<VBoxContainer>
<OutputPanel Name="Output" VerticalExpand="True">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252add" Padding="3" />
<gfx:StyleBoxFlat BackgroundColor="#25252add"
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
</OutputPanel.StyleBoxOverride>
</OutputPanel>
<HistoryLineEdit Name="CommandBar" PlaceHolder="{Loc 'console-line-edit-placeholder'}" />

View File

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

View File

@@ -4,6 +4,7 @@ using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.State;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
@@ -62,7 +63,7 @@ namespace Robust.Client.UserInterface.CustomControls
_timeDebug = new DebugTimePanel(gameTiming1, IoCManager.Resolve<IClientGameStateManager>());
AddChild(_timeDebug);
_frameGraph = new FrameGraph(gameTiming1);
_frameGraph = new FrameGraph(gameTiming1, IoCManager.Resolve<IConfigurationManager>());
AddChild(_frameGraph);
AddChild(_debugMemoryPanel = new DebugMemoryPanel());

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
@@ -9,6 +11,7 @@ namespace Robust.Client.UserInterface.CustomControls
public sealed class FrameGraph : Control
{
private readonly IGameTiming _gameTiming;
private readonly IConfigurationManager _cfg;
/// <summary>
/// How many frames we show at once.
@@ -26,11 +29,6 @@ namespace Robust.Client.UserInterface.CustomControls
/// </summary>
private const int FrameHeight = 60;
/// <summary>
/// The target frame rate at which we are "good".
/// </summary>
private const int TargetFrameRate = 60;
// We keep track of frame times in a ring buffer.
private readonly float[] _frameTimes = new float[TrackedFrames];
private readonly BitArray _gcMarkers = new(TrackedFrames);
@@ -39,9 +37,10 @@ namespace Robust.Client.UserInterface.CustomControls
private int _frameIndex;
private int _lastGCCount;
public FrameGraph(IGameTiming gameTiming)
public FrameGraph(IGameTiming gameTiming, IConfigurationManager cfg)
{
_gameTiming = gameTiming;
_cfg = cfg;
HorizontalAlignment = HAlignment.Left;
}
@@ -66,6 +65,8 @@ namespace Robust.Client.UserInterface.CustomControls
{
base.Draw(handle);
var target = _cfg.GetCVar(CVars.DebugTargetFps);
Span<Vector2> triangle = stackalloc Vector2[3];
float maxHeight = 0;
@@ -73,7 +74,7 @@ namespace Robust.Client.UserInterface.CustomControls
{
var currentFrameIndex = MathHelper.Mod(_frameIndex - 1 - i, TrackedFrames);
var frameTime = _frameTimes[currentFrameIndex];
maxHeight = System.Math.Max(maxHeight, FrameHeight * (frameTime * TargetFrameRate));
maxHeight = System.Math.Max(maxHeight, FrameHeight * (frameTime * target));
}
float ratio = maxHeight > PixelHeight ? PixelHeight / maxHeight : 1;
@@ -81,16 +82,16 @@ namespace Robust.Client.UserInterface.CustomControls
{
var currentFrameIndex = MathHelper.Mod(_frameIndex - 1 - i, TrackedFrames);
var frameTime = _frameTimes[currentFrameIndex];
var frameHeight = FrameHeight * (frameTime * TargetFrameRate);
var frameHeight = FrameHeight * (frameTime * target);
var x = FrameWidth * UIScale * (TrackedFrames - 1 - i);
var rect = new UIBox2(x, PixelHeight - (frameHeight * ratio), x + FrameWidth * UIScale, PixelHeight);
Color color;
if (frameTime > 1f / (TargetFrameRate / 2 - 1))
if (frameTime > 1f / (target / 2 - 1))
{
color = Color.Red;
}
else if (frameTime > 1f / (TargetFrameRate - 1))
else if (frameTime > 1f / (target - 1))
{
color = Color.Yellow;
}

View File

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

View File

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

View File

@@ -58,7 +58,7 @@ namespace Robust.Client.ViewVariables
var styleOther = false;
var type = obj.GetType();
var members = new List<(MemberInfo, VVAccess, object? value, Action<object, bool> onValueChanged, Type)>();
var members = new List<(MemberInfo, VVAccess, object? value, Action<object?, bool> onValueChanged, Type)>();
foreach (var fieldInfo in type.GetAllFields())
{

View File

@@ -306,7 +306,6 @@ namespace Robust.Server
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_entityManager.Initialize();
IoCManager.Resolve<IEntityLookup>().Initialize();
IoCManager.Resolve<ISerializationManager>().Initialize();
@@ -319,6 +318,7 @@ namespace Robust.Server
IoCManager.Resolve<IServerConsoleHost>().Initialize();
_entityManager.Startup();
IoCManager.Resolve<IEntityLookup>().Startup();
_stateManager.Initialize();
_scriptHost.Initialize();

View File

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

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
@@ -6,10 +7,10 @@ using Robust.Shared.Physics;
namespace Robust.Server.Debugging
{
[UsedImplicitly]
internal class DebugDrawingManager : IDebugDrawingManager
{
[Dependency] private readonly IServerNetManager _net = default!;
[Dependency] private readonly IPhysicsManager _physics = default!;
public void Initialize()
{

View File

@@ -5,21 +5,21 @@ using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class BasicActorComponent : Component, IActorComponent
public class ActorComponent : Component
{
public override string Name => "BasicActor";
public override string Name => "Actor";
[ViewVariables] public IPlayerSession playerSession { get; internal set; } = default!;
[ViewVariables] public IPlayerSession PlayerSession { get; internal set; } = default!;
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
// Warning: careful here, Detach removes this component, make sure this is after the base shutdown
// to prevent infinite recursion
// ReSharper disable once ConstantConditionalAccessQualifier
playerSession?.DetachFromEntity();
PlayerSession?.DetachFromEntity();
}
}
@@ -27,6 +27,7 @@ namespace Robust.Server.GameObjects
/// Raised on an entity whenever a player attaches to this entity.
/// </summary>
[Serializable]
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class PlayerAttachedMsg : ComponentMessage
{
public IPlayerSession NewPlayer { get; }
@@ -41,6 +42,7 @@ namespace Robust.Server.GameObjects
/// Raised on an entity whenever a player detaches from this entity.
/// </summary>
[Serializable]
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public class PlayerDetachedMsg : ComponentMessage
{
public IPlayerSession OldPlayer { get; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -80,13 +80,9 @@ namespace Robust.Server.GameObjects
Identifier = id
};
var players = (playerFilter as IFilter).Recipients;
foreach (var player in players)
{
RaiseNetworkEvent(msg, player.ConnectedClient);
}
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, players);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
/// <inheritdoc />
@@ -105,21 +101,14 @@ namespace Robust.Server.GameObjects
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
IList<ICommonSession> players;
var recipients = (playerFilter as IFilter).Recipients;
// We clone the filter here as to not modify the original instance.
if (range > 0.0f)
players = PasInRange(recipients, entity.Transform.MapPosition, range);
else
players = recipients;
playerFilter = playerFilter.Clone().AddInRange(entity.Transform.MapPosition, range);
foreach (var player in players)
{
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
RaiseNetworkEvent(msg, playerFilter);
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
/// <inheritdoc />
@@ -136,29 +125,14 @@ namespace Robust.Server.GameObjects
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
IList<ICommonSession> players;
var recipients = (playerFilter as IFilter).Recipients;
// We clone the filter here as to not modify the original instance.
if (range > 0.0f)
players = PasInRange(recipients, coordinates.ToMap(EntityManager), range);
else
players = recipients;
playerFilter = playerFilter.Clone().AddInRange(coordinates.ToMap(EntityManager), range);
foreach (var player in players)
{
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
RaiseNetworkEvent(msg, playerFilter);
private static List<ICommonSession> PasInRange(IEnumerable<ICommonSession> players, MapCoordinates position, float range)
{
return players.Where(x =>
x.AttachedEntity != null &&
position.InRange(x.AttachedEntity.Transform.MapPosition, range))
.ToList();
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
}
}
}

View File

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

View File

@@ -219,9 +219,7 @@ namespace Robust.Server.GameObjects
public override void Shutdown()
{
base.Shutdown();
UnsubscribeLocalEvent<MoveEvent>();
UnsubscribeLocalEvent<EntityInitializedMessage>();
UnsubscribeLocalEvent<EntityDeletedMessage>();
_mapManager.OnGridCreated -= HandleGridCreated;
_mapManager.OnGridRemoved -= HandleGridRemoval;
_mapManager.TileChanged -= HandleTileChanged;
@@ -320,7 +318,12 @@ namespace Robust.Server.GameObjects
/// <param name="moveEvent"></param>
private void HandleEntityMove(MoveEvent moveEvent)
{
if (moveEvent.Sender.Deleted || moveEvent.NewPosition.GetGridId(EntityManager) == GridId.Invalid || !moveEvent.NewPosition.IsValid(EntityManager))
// TODO: When Acruid does TileEntities we may actually be able to delete this system if tile lookups become fast enough
var gridId = moveEvent.NewPosition.GetGridId(EntityManager);
if (moveEvent.Sender.Deleted ||
gridId == GridId.Invalid ||
!moveEvent.NewPosition.IsValid(EntityManager))
{
HandleEntityRemove(moveEvent.Sender);
return;
@@ -332,7 +335,7 @@ namespace Robust.Server.GameObjects
}
// Memory leak protection
var gridBounds = _mapManager.GetGrid(moveEvent.Sender.Transform.GridID).WorldBounds;
var gridBounds = _mapManager.GetGrid(gridId).WorldBounds;
if (!gridBounds.Contains(moveEvent.Sender.Transform.WorldPosition))
{
HandleEntityRemove(moveEvent.Sender);

View File

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

View File

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

View File

@@ -137,6 +137,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component,
ComponentMessage message)
{

View File

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

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -9,10 +10,9 @@ using Robust.Shared.Timing;
namespace Robust.Server.Map
{
[UsedImplicitly]
internal sealed class ServerMapManager : MapManager, IServerMapManager
{
[Dependency] private readonly INetManager _netManager = default!;
private readonly List<(GameTick tick, GridId gridId)> _gridDeletionHistory = new();
private readonly List<(GameTick tick, MapId mapId)> _mapDeletionHistory = new();

View File

@@ -15,7 +15,9 @@ using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -767,7 +769,7 @@ namespace Robust.Server.Maps
// Don't need to write it if nothing was written!
if (compMapping.Children.Count != 0)
{
compMapping.AddNode("type", new ValueDataNode(component.Name));
compMapping.Add("type", new ValueDataNode(component.Name));
// Something actually got written!
components.Add(compMapping.ToYamlNode());
}

View File

@@ -212,7 +212,7 @@ namespace Robust.Server.Placement
foreach (IEntity entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))
{
if (entity.Deleted || entity.HasComponent<IMapGridComponent>() || entity.HasComponent<IActorComponent>())
if (entity.Deleted || entity.HasComponent<IMapGridComponent>() || entity.HasComponent<ActorComponent>())
continue;
entity.Delete();
}
@@ -223,10 +223,10 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBegin(IEntity mob, int range, string objectType, string alignOption)
{
if (!mob.TryGetComponent<IActorComponent>(out var actor))
if (!mob.TryGetComponent<ActorComponent>(out var actor))
return;
var playerConnection = actor.playerSession.ConnectedClient;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
@@ -244,10 +244,10 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementBeginTile(IEntity mob, int range, string tileType, string alignOption)
{
if (!mob.TryGetComponent<IActorComponent>(out var actor))
if (!mob.TryGetComponent<ActorComponent>(out var actor))
return;
var playerConnection = actor.playerSession.ConnectedClient;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;
@@ -265,10 +265,10 @@ namespace Robust.Server.Placement
/// </summary>
public void SendPlacementCancel(IEntity mob)
{
if (!mob.TryGetComponent<IActorComponent>(out var actor))
if (!mob.TryGetComponent<ActorComponent>(out var actor))
return;
var playerConnection = actor.playerSession.ConnectedClient;
var playerConnection = actor.PlayerSession.ConnectedClient;
if (playerConnection == null)
return;

View File

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

View File

@@ -55,10 +55,24 @@ namespace Robust.Server.Player
private readonly Dictionary<string, NetUserId> _userIdMap = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions => _sessions.Values;
public IEnumerable<ICommonSession> NetworkedSessions => Sessions;
/// <inheritdoc />
public IEnumerable<ICommonSession> Sessions => _sessions.Values;
public IEnumerable<ICommonSession> Sessions
{
get
{
_sessionsLock.EnterReadLock();
try
{
return _sessions.Values;
}
finally
{
_sessionsLock.ExitReadLock();
}
}
}
/// <inheritdoc />
[ViewVariables]
@@ -109,11 +123,20 @@ namespace Robust.Server.Player
return false;
}
if (_sessions.TryGetValue(userId, out var iSession))
_sessionsLock.EnterReadLock();
try
{
session = iSession;
return true;
if (_sessions.TryGetValue(userId, out var iSession))
{
session = iSession;
return true;
}
}
finally
{
_sessionsLock.ExitReadLock();
}
session = null;
return false;
@@ -210,7 +233,9 @@ namespace Robust.Server.Player
try
{
foreach (var s in _sessions.Values)
{
s.JoinGame();
}
}
finally
{
@@ -237,7 +262,9 @@ namespace Robust.Server.Player
try
{
foreach (var s in _sessions.Values)
{
s.DetachFromEntity();
}
}
finally
{
@@ -286,9 +313,7 @@ namespace Robust.Server.Player
try
{
return
_sessions.Values.Where(predicate)
.Cast<IPlayerSession>()
.ToList();
_sessions.Values.Where(predicate).ToList();
}
finally
{

View File

@@ -118,8 +118,8 @@ namespace Robust.Server.Player
return;
}
var actorComponent = a.AddComponent<BasicActorComponent>();
actorComponent.playerSession = this;
var actorComponent = a.AddComponent<ActorComponent>();
actorComponent.PlayerSession = this;
AttachedEntity = a;
a.SendMessage(actorComponent, new PlayerAttachedMsg(this));
a.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSystemMessage(a, this));
@@ -137,11 +137,11 @@ namespace Robust.Server.Player
throw new InvalidOperationException("Tried to detach player, but my entity does not exist!");
}
if (AttachedEntity.TryGetComponent<BasicActorComponent>(out var actor))
if (AttachedEntity.TryGetComponent<ActorComponent>(out var actor))
{
AttachedEntity.SendMessage(actor, new PlayerDetachedMsg(this));
AttachedEntity.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerDetachedSystemMessage(AttachedEntity));
AttachedEntity.RemoveComponent<BasicActorComponent>();
AttachedEntity.RemoveComponent<ActorComponent>();
AttachedEntity = null;
UpdatePlayerState();
}

View File

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

View File

@@ -415,14 +415,26 @@ namespace Robust.Shared
CVarDef.Create("physics.maxangularcorrection", 8.0f / 180.0f * MathF.PI);
// - Maximums
// Squared
// 35 m/s, AKA half a tile per frame allowed. Divide this by frametime to get units per second.
/// <summary>
/// Maximum linear velocity per second.
/// Make sure that MaxLinVelocity / <see cref="NetTickrate"/> is around 0.5 or higher so that moving objects don't go through walls.
/// MaxLinVelocity is compared to the dot product of linearVelocity * frameTime.
/// </summary>
/// <remarks>
/// Default is 35 m/s. Around half a tile per tick at 60 ticks per second.
/// </remarks>
public static readonly CVarDef<float> MaxLinVelocity =
CVarDef.Create("physics.maxlinvelocity", 0.56f);
CVarDef.Create("physics.maxlinvelocity", 35f);
// Squared
/// <summary>
/// Maximum angular velocity in full rotations per second.
/// MaxAngVelocity is compared to the squared rotation.
/// </summary>
/// <remarks>
/// Default is 15 rotations per second. Approximately a quarter rotation per tick at 60 ticks per second.
/// </remarks>
public static readonly CVarDef<float> MaxAngVelocity =
CVarDef.Create("physics.maxangvelocity", 0.5f * MathF.PI);
CVarDef.Create("physics.maxangvelocity", 15f);
/*
* DISCORD
@@ -440,5 +452,16 @@ namespace Robust.Shared
public static readonly CVarDef<bool> TexturePreloadingEnabled =
CVarDef.Create("res.texturepreloadingenabled", true, CVar.CLIENTONLY);
/*
* DEBUG
*/
/// <summary>
/// Target framerate for things like the frame graph.
/// </summary>
public static readonly CVarDef<int> DebugTargetFps =
CVarDef.Create("debug.target_fps", 60, CVar.CLIENTONLY | CVar.ARCHIVE);
}
}

View File

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

View File

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

View File

@@ -1,11 +1,30 @@
namespace Robust.Shared.Enums
{
/// <summary>
/// Represents a grammatical gender of an object.
/// </summary>
public enum Gender : byte
{
/// <summary>
/// Object has no gender and a gender probably makes no sense. Think inanimate objects. It/it.
/// </summary>
Neuter = 0,
/// <summary>
/// Object is something you would consider as *capable of having a gender*, but *doesn't*.
/// Think people without (visible/unambiguous) gender. They/them.
/// </summary>
Epicene,
/// <summary>
/// Female gender. She/her
/// </summary>
Female,
/// <summary>
/// Male gender. He/his
/// </summary>
Male,
Neuter,
}
}

View File

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

View File

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

View File

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

View File

@@ -98,7 +98,6 @@ namespace Robust.Shared.GameObjects
if (_bodyType == value)
return;
var oldAnchored = _bodyType == BodyType.Static;
var oldType = _bodyType;
_bodyType = value;
@@ -121,14 +120,7 @@ namespace Robust.Shared.GameObjects
RegenerateContacts();
var anchored = value == BodyType.Static;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsBodyTypeChangedEvent(_bodyType, oldType), false);
if (oldAnchored != anchored)
{
SendMessage(new AnchoredChangedMessage(Anchored));
}
}
}
@@ -900,35 +892,6 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Whether or not the entity is anchored in place.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use BodyType.Static instead")]
public bool Anchored
{
get => BodyType == BodyType.Static;
set
{
var anchored = BodyType == BodyType.Static;
if (anchored == value)
return;
if (value)
{
_bodyType = BodyType.Static;
}
else
{
_bodyType = BodyType.Dynamic;
}
SendMessage(new AnchoredChangedMessage(Anchored));
Dirty();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool Predict
{
@@ -1331,6 +1294,7 @@ namespace Robust.Shared.GameObjects
if (preventCollideMessage.Cancelled) return false;
#pragma warning disable 618
foreach (var comp in Owner.GetAllComponents<ICollideSpecial>())
{
if (comp.PreventCollide(other)) return false;
@@ -1340,21 +1304,12 @@ namespace Robust.Shared.GameObjects
{
if (comp.PreventCollide(this)) return false;
}
#pragma warning restore 618
return true;
}
}
public class AnchoredChangedMessage : ComponentMessage
{
public readonly bool Anchored;
public AnchoredChangedMessage(bool anchored)
{
Anchored = anchored;
}
}
/// <summary>
/// Directed event raised when an entity's physics BodyType changes.
/// </summary>
@@ -1370,11 +1325,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
public BodyType Old { get; }
/// <summary>
/// Whether the body is "anchored".
/// </summary>
public bool Anchored => New == BodyType.Static;
public PhysicsBodyTypeChangedEvent(BodyType newType, BodyType oldType)
{
New = newType;

View File

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

View File

@@ -1,9 +1,14 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Enums;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects.Components.Localization
{
/// <summary>
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent]
public class GrammarComponent : Component
{
@@ -11,15 +16,33 @@ namespace Robust.Shared.GameObjects.Components.Localization
public override uint? NetID => NetIDs.GRAMMAR;
[ViewVariables]
[DataField("localizationId")]
public string LocalizationId = "";
[DataField("attributes")]
public Dictionary<string, string> Attributes { get; } = new();
[ViewVariables]
[DataField("gender")]
public Gender? Gender = null;
public Gender? Gender
{
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
set
{
if (value.HasValue)
Attributes["gender"] = value.Value.ToString();
else
Attributes.Remove("gender");
}
}
[ViewVariables]
[DataField("proper")]
public bool? ProperNoun = null;
public bool? ProperNoun
{
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
set
{
if (value.HasValue)
Attributes["proper"] = value.Value.ToString();
else
Attributes.Remove("proper");
}
}
}
}

View File

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

View File

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

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