Compare commits

...

58 Commits

Author SHA1 Message Date
Metal Gear Sloth
39ae3ac653 Optimise physics do not research 2021-03-24 22:35:17 +11:00
Pieter-Jan Briers
e48f4027e5 Probably fix running Robust directly for some people. 2021-03-23 21:28:36 +01:00
ShadowCommander
2fa1e98faf Fix CopyWithTypeSerializer not copying when null (#1651) 2021-03-22 11:02:32 +01:00
Pieter-Jan Briers
cedfa0ee2f Nothing to see here. 2021-03-21 20:37:10 +01:00
Acruid
92f44b390e SoundSystem Improvements (#1649) 2021-03-21 16:35:52 +01:00
Pieter-Jan Briers
65a42f9209 Prototype reloading now fires an event. 2021-03-21 16:25:52 +01:00
Acruid
ebf53248cf TestLogHandler now fails the test if a warning or higher is logged. 2021-03-19 13:42:13 -07:00
Acruid
289f637e8a Entity Lifetime Levels (#1644)
* Added an entity lifetime levels property.
Added exception when recursively deleting an entity.

* Add a directed event 'EntityTerminatingEvent' for right before an entity is deleted.

* Added MapInit lifestage to entities.
2021-03-18 22:53:05 -07:00
metalgearsloth
d7c13f30c8 Fix showbb awake (#1632)
* Fix showbb awake

* Slight tweak

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-17 13:58:26 -07:00
Vera Aguilera Puerto
0dac17ae5e ConfigurationManager.OnValueChanged's invokeImmediately now accounts for overriden values correctly. 2021-03-17 20:22:44 +01:00
Pieter-Jan Briers
9a19a774fa Use stencil test to cull FOV-hidden lights early.
Massive shader optimization.
2021-03-17 13:16:47 +01:00
Pieter-Jan Briers
81f49d5eb2 Fix moving to the end of a textbox. 2021-03-17 01:22:45 +01:00
DrSmugleaf
4f3b4ac2d2 Changes for content server nullability (#1642) 2021-03-16 15:47:49 +01:00
Pieter-Jan Briers
e428056b52 Rldrsc now works with textures. 2021-03-16 12:39:19 +01:00
DrSmugleaf
8dc9d2989a Fix not being able to use shared entity systems in update order (#1638) 2021-03-16 12:38:31 +01:00
Paul
fd8c90dcbb reverting cringe (moved controller metrics cvar get to server) 2021-03-16 12:05:56 +01:00
Vera Aguilera Puerto
ffe4e5a8ab Add Enabled property to CollisionWake component. (#1641)
* Add Enabled property to CollisionWake component.

* Set property in HandleComponentState
2021-03-16 11:38:46 +01:00
Paul Ritter
6e5026d270 adds prometheus logging to physicscontrollers (#1640) 2021-03-16 11:32:46 +01:00
metalgearsloth
946c4166dc Move RootControl frameupdate to after queue (#1625)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-16 09:15:18 +01:00
Paul Ritter
7d2fb85a04 adds custom typeserializers (#1636)
retires DataFieldWithConstantAttribute & DataFieldWithFlagAttribute in favor of new customtypeserializers
adds prototypeidvalidation, just needs to be added to the corresponding fields
fixes some behaviour in yamllinter
2021-03-15 13:24:29 +01:00
metalgearsloth
d6ec078519 Fix static sleeping crash (#1630)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-14 12:44:49 +11:00
DrSmugleaf
32256fc4d9 Remove printing ticks in integration tests (#1627) 2021-03-13 20:12:49 +01:00
DrSmugleaf
37bbdfe7ff Fix serialization logging not printing messages (#1628) 2021-03-13 20:12:41 +01:00
metalgearsloth
c906675cdf Set collidable on CollisionWake removal (#1626)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-13 20:13:00 +11:00
DrSmugleaf
90bb5574c1 Add a CVar to disable texture preloading for tests (#1623) 2021-03-13 13:25:10 +11:00
Acruid
7b50dcd969 Removed IEntityManager.SpawnEntityNoMapInit. Every entity spawned into an uninitialized map does not have mapinit ran, so this is useless. 2021-03-11 22:17:31 -08:00
Pieter-Jan Briers
8d82f48a8f Various GLES fixes. 2021-03-11 13:06:30 +01:00
Pieter-Jan Briers
469f9fd219 Remove #line directives from shaders.
They hurt debugging more than they helped.
2021-03-11 13:06:18 +01:00
Pieter-Jan Briers
1a5783ab4e Probably fix tests 2021-03-11 11:47:58 +01:00
Clyybber
3d25886d79 Set velocity for audio sources, enabling doppler effect (#1622) 2021-03-11 11:44:01 +01:00
Pieter-Jan Briers
516b2cd372 Handle surrogate pairs correctly in LineEdit. 2021-03-10 16:55:12 +01:00
Pieter-Jan Briers
3cfcfa0be2 Render fallback character for unavailable characters. 2021-03-10 16:54:52 +01:00
Pieter-Jan Briers
69328087bd Added AsRune property to TextEventArgs 2021-03-10 16:54:13 +01:00
Pieter-Jan Briers
1bf8b2a52b Use Rune for rendering text instead of char.
Fixes crashes with surrogates.
2021-03-09 23:25:27 +01:00
Pieter-Jan Briers
fc6dc6f4e1 Add/fix Rune APIs for sandbox. 2021-03-09 23:24:33 +01:00
Pieter-Jan Briers
31c1feca4e Debug console history improvements.
No longer blows up if history cannot be read/written thanks to file locking.

Made it more async so it won't waste main thread init time.
2021-03-09 22:28:58 +01:00
Pieter-Jan Briers
3ed1eef2ab Fix build. 2021-03-09 21:44:46 +01:00
Pieter-Jan Briers
1394a017bb Fix IL verification throwing if a verifier error does not need to be formatted. 2021-03-09 21:41:50 +01:00
metalgearsloth
6b0670d5f1 Break joints on container insertion; semi-related to break pulling on container insertion (#1620)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:57 -08:00
metalgearsloth
f573331541 Fix physics joint disconnect spam (#1619)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:35 -08:00
metalgearsloth
a7218cd3b8 Make Visible a Shared Property for sprites (#1615)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:16 -08:00
Acruid
f7e8178736 Added new ComponentEvents system in IEventBus. (#1601)
* Added new ComponentEventBus, combined it with IEventBus.

* Removed all traces of IEntity from ComponentDependencies.
Removed IEntityManager dependency from ComponentManager.

* Added entity create/delete events to IEntityManager.

* ComponentEvents now use EntitySystemMessages instead of their custom ComponentEvent class.

* Component events are now just overloads of entity events.

* Removed obsolete EntitySystemMessage, now everything uses the base EntityEventArgs.

* Add a bool argument for if the message should be broadcast as well as directed.
Fix ordering and init issues of events in EntityManager.

* Changed names from Component/Entity events to Directed/Broadcast.

* Fix bugs and unit tests.
2021-03-09 11:02:24 -08:00
Pieter-Jan Briers
31f921e4aa Use ProfileOptimization to speed up startup. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
aa1c25637c Allow disabling nvidia optimus via env var. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
71f2c48463 Call GC.Collect after game init.
Cleans up any gen 2 garbage from init and the stutter shouldn't be the end of the world.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
d65f4ca898 RSI & texture preloading.
All RSIs and textures are now loaded ahead of time in client startup. This is well threaded and is extremely fast.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
b35568ffe5 Disable path case checks by default.
The idea was that these are Task.Run'd so don't influence performance. That was before we started threading the hell out of startup.

We're getting more stuff like YAML linting now which should hopefully be able to catch 99% of this. And louder because it was always just a warning before.
2021-03-09 12:29:59 +01:00
Acruid
a0d241e551 Removes some things that should not have been in the last PR. 2021-03-09 02:06:13 -08:00
GraniteSidewalk
33a6934582 Large number of changes to Shaders and Overlays (#1387)
* AAAAAAAAAAAAA

* Organization

* Still doesnt work

* Formatting

* It works!!

* More changes to everything

* Beginning of changes to overlays

* Makes the overlay manager GUID based (also it was very messy, still messy but i fixed some of it)

* Stencils are easy

* Questionable changes to overlays

* Minor change to HLR

* Fixed duplicate overlays when calling some commands (Like showbb)

* Fixes misleading message

* Adds a variety of worldspaces for overlays to choose from

* Caching

* Address reviews

* Merging pains

* ah.

* ahhhhh

* minor overlaymanager changes

* Work

* fix

* Merge??

* Fixes null errors

* Force update

* Delete whatever the fuck this is?

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-03-09 01:52:16 -08:00
metalgearsloth
f237a8bbbc Optimise static body sleeping (#1618)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 19:47:51 +11:00
Pieter-Jan Briers
4bc775c01c RSI loader improvements:
1. Stop using NJsonSchema, it didn't do anything useful.
2. Use System.Text.Json instead of Newtonsoft.Json.
3. General cleanup of the code, using arrays instead of lists, etc...
2021-03-08 11:18:19 +01:00
Pieter-Jan Briers
93b4d81505 Optimize ImageSharp blitting. 2021-03-08 11:15:33 +01:00
Pieter-Jan Briers
0afb85a09e Fix some missing re-pooling of ImageSharp images. 2021-03-08 09:45:22 +01:00
Metal Gear Sloth
7b9315cea4 Significantly lower physics speedcap 2021-03-08 15:46:50 +11:00
Metal Gear Sloth
dc3af45096 Fix anchored message 2021-03-08 15:00:08 +11:00
metalgearsloth
00ce0179ae Allow kinematic controllers to have an impulse applied (#1612)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-08 12:10:20 +11:00
ShadowCommander
81947ba3d8 Fix buckling (#1611) 2021-03-07 15:25:11 -08:00
DrSmugleaf
49327279d0 Fix nullability errors in physics ContactHead code (#1609) 2021-03-07 23:15:55 +01:00
183 changed files with 3194 additions and 1561 deletions

View File

@@ -28,5 +28,10 @@ void fragment()
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);
if (occlusion >= 1.0)
{
discard;
}
COLOR = vec4(0.0, 0.0, 0.0, 1.0 - occlusion);
}

View File

@@ -11,7 +11,6 @@ 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;
@@ -321,6 +320,11 @@ namespace Robust.Client.Audio.Midi
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

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -41,14 +42,14 @@ namespace Robust.Client.Debugging
_debugColliders = value;
if (value)
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
_prototypeManager, _inputManager, _physicsManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(PhysicsOverlay));
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
@@ -66,13 +67,13 @@ namespace Robust.Client.Debugging
_debugPositions = value;
if (value)
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(EntityPositionOverlay));
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
@@ -91,8 +92,8 @@ namespace Robust.Client.Debugging
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
: base(nameof(PhysicsOverlay))
{
_componentManager = compMan;
_eyeManager = eyeMan;
@@ -193,9 +194,9 @@ namespace Robust.Client.Debugging
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, Color.White);
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
@@ -265,7 +266,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager) : base(nameof(EntityPositionOverlay))
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_entityManager = entityManager;
_eyeManager = eyeManager;

View File

@@ -1,10 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
@@ -38,13 +39,13 @@ namespace Robust.Client.Debugging
_debugDrawRays = value;
if (value)
if (value && !_overlayManager.HasOverlay<DebugDrawRayOverlay>())
{
_overlayManager.AddOverlay(new DebugDrawRayOverlay(this));
}
else
{
_overlayManager.RemoveOverlay(nameof(DebugDrawRayOverlay));
_overlayManager.RemoveOverlay<DebugDrawRayOverlay>();
}
}
}
@@ -81,7 +82,7 @@ namespace Robust.Client.Debugging
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner) : base(nameof(DebugDrawRayOverlay))
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}

View File

@@ -24,6 +24,7 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -55,7 +56,7 @@ namespace Robust.Client.Debugging
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsDebugOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
@@ -119,7 +120,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system) : base(nameof(PhysicsDebugOverlay))
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_physics = system;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Management;
using System.Net;
using System.Threading.Tasks;
using Robust.Client.Audio.Midi;
@@ -119,6 +120,8 @@ namespace Robust.Client
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
@@ -161,6 +164,7 @@ namespace Robust.Client
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_resourceCache.PreloadTextures();
_userInterfaceManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
@@ -188,6 +192,8 @@ namespace Robust.Client
_authManager.LoadFromEnv();
GC.Collect();
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)

View File

@@ -212,12 +212,6 @@ namespace Robust.Client.GameObjects
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntityNoMapInit(string? protoName, EntityCoordinates coordinates)
{
return SpawnEntity(protoName, coordinates);
}
protected override EntityUid GenerateEntityUid()
{
return new(_nextClientEntityUid++);

View File

@@ -45,12 +45,12 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
SendSystemNetworkMessage(message, default(uint));
}
public void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.SystemMessage;
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
{
throw new NotSupportedException();
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -19,6 +19,7 @@ using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -36,13 +37,13 @@ namespace Robust.Client.GameObjects
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set => _visible = value;
}
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
private int drawDepth = DrawDepthTag.Default;
/// <summary>
@@ -2084,6 +2085,7 @@ namespace Robust.Client.GameObjects
public IEntityManager EntityManager { get; } = null!;
public string Name { get; set; } = string.Empty;
public EntityUid Uid { get; } = EntityUid.Invalid;
EntityLifeStage IEntity.LifeStage { get => _lifeStage; set => _lifeStage = value; }
public bool Initialized { get; } = false;
public bool Initializing { get; } = false;
public bool Deleted { get; } = true;
@@ -2100,6 +2102,7 @@ namespace Robust.Client.GameObjects
public IMetaDataComponent MetaData { get; } = null!;
private Dictionary<Type, IComponent> _components = new();
private EntityLifeStage _lifeStage;
public T AddComponent<T>() where T : Component, new()
{
@@ -2188,11 +2191,7 @@ namespace Robust.Client.GameObjects
{
return null;
}
public void Shutdown()
{
}
public void Delete()
{
}

View File

@@ -9,7 +9,7 @@ namespace Robust.Client.GameObjects
public static class EntityManagerExt
{
public static void RaisePredictiveEvent<T>(this IEntityManager entityManager, T msg)
where T : EntitySystemMessage
where T : EntityEventArgs
{
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
DebugTools.AssertNotNull(localPlayer);

View File

@@ -8,7 +8,6 @@ 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.Player;
@@ -165,6 +164,12 @@ namespace Robust.Client.GameObjects
Logger.Warning("Interrupting positional audio, can't set position.");
stream.Source.StopPlaying();
}
if (stream.TrackingEntity != null)
{
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
}
}
}
}
@@ -188,7 +193,7 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -204,7 +209,7 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream Play(AudioStream stream, AudioParams? audioParams = null)
private IPlayingAudioStream Play(AudioStream stream, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
ApplyAudioParams(audioParams, source);
@@ -226,7 +231,7 @@ namespace Robust.Client.GameObjects
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -243,7 +248,7 @@ namespace Robust.Client.GameObjects
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
if (!source.SetPosition(entity.Transform.WorldPosition))
@@ -272,7 +277,7 @@ namespace Robust.Client.GameObjects
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -289,7 +294,7 @@ namespace Robust.Client.GameObjects
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);

View File

@@ -106,7 +106,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Event raised by a <see cref="ClientOccluderComponent"/> when it needs to be recalculated.
/// </summary>
internal sealed class OccluderDirtyEvent : EntitySystemMessage
internal sealed class OccluderDirtyEvent : EntityEventArgs
{
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
{

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
@@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
overlayManager.RemoveOverlay(typeof(EffectOverlay));
}
public void CreateEffect(EffectSystemMessage message)
@@ -329,7 +330,6 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
@@ -337,8 +337,7 @@ namespace Robust.Client.GameObjects
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager)
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();

View File

@@ -157,7 +157,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public class PlayerAttachSysMessage : EntitySystemMessage
public class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.GameStates
private uint _nextInputCmdSeq = 1;
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
private readonly Queue<(uint sequence, GameTick sourceTick, EntitySystemMessage msg, object sessionMsg)>
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
_pendingSystemMessages
= new();
@@ -126,7 +126,7 @@ namespace Robust.Client.GameStates
_nextInputCmdSeq++;
}
public uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage
public uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs
{
if (!Predicting)
{
@@ -298,7 +298,7 @@ namespace Robust.Client.GameStates
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
((IEntityEventBus) _entities.EventBus).ProcessEventQueue();
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
}

View File

@@ -70,6 +70,6 @@ namespace Robust.Client.GameStates
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(FullInputCmdMessage message);
uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage;
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
}
}

View File

@@ -3,6 +3,7 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -28,12 +29,12 @@ namespace Robust.Client.GameStates
/// <inheritdoc />
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly Font _font;
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
public NetEntityOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -42,12 +43,12 @@ namespace Robust.Client.GameStates
_gameStateManager.GameStateApplied += HandleGameStateApplied;
}
private void HandleGameStateApplied(GameStateAppliedArgs args)
{
if(_gameTiming.InPrediction) // we only care about real server states.
return;
// Shift traffic history down one
for (var i = 0; i < _netEnts.Count; i++)
{
@@ -74,7 +75,7 @@ namespace Robust.Client.GameStates
if (netEnt.Id != entityState.Uid)
continue;
//TODO: calculate size of state and record it here.
netEnt.Traffic[^1] = 1;
netEnt.LastUpdate = gameState.ToSequence;
@@ -102,7 +103,7 @@ namespace Robust.Client.GameStates
for (int i = 0; i < _netEnts.Count; i++)
{
var netEnt = _netEnts[i];
if(_entityManager.EntityExists(netEnt.Id))
{
//TODO: Whoever is working on PVS remake, change the InPVS detection.
@@ -123,7 +124,7 @@ namespace Robust.Client.GameStates
_netEnts[i] = netEnt; // copy struct back
}
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
{
if (!_netManager.IsConnected)
@@ -131,7 +132,7 @@ namespace Robust.Client.GameStates
// remember, 0,0 is top left of ui with +X right and +Y down
var screenHandle = (DrawingHandleScreen)handle;
for (int i = 0; i < _netEnts.Count; i++)
{
var netEnt = _netEnts[i];
@@ -179,20 +180,19 @@ namespace Robust.Client.GameStates
return Color.Green; // Entity in PVS, but not updated recently.
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, textColor);
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
baseLine += new Vector2(advance, 0);
}
}
@@ -238,14 +238,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -34,7 +36,7 @@ namespace Robust.Client.GameStates
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
public NetGraphOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -142,20 +144,20 @@ namespace Robust.Client.GameStates
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, Color.White);
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
@@ -183,14 +185,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.AddOverlay(new NetGraphOverlay());
shell.WriteLine("Enabled network overlay.");
}
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
shell.WriteLine("Disabled network overlay.");
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
@@ -5,6 +6,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using System;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
@@ -18,7 +20,7 @@ namespace Robust.Client.GameStates
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
public NetInterpOverlay() : base(nameof(NetInterpOverlay))
public NetInterpOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
@@ -85,14 +87,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (bValue && !overlayMan.HasOverlay(nameof(NetInterpOverlay)))
if (bValue && !overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.AddOverlay(new NetInterpOverlay());
shell.WriteLine("Enabled network interp overlay.");
}
else if (overlayMan.HasOverlay(nameof(NetInterpOverlay)))
else if (overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.RemoveOverlay(nameof(NetInterpOverlay));
overlayMan.RemoveOverlay<NetInterpOverlay>();
shell.WriteLine("Disabled network interp overlay.");
}
}

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -482,7 +482,7 @@ namespace Robust.Client.Graphics.Clyde
var (x, y) = position;
if (!ValidatePosition(x, y))
if (!AreFinite(x, y))
{
return false;
}
@@ -503,7 +503,7 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
private static bool ValidatePosition(float x, float y)
private static bool AreFinite(float x, float y)
{
if (float.IsFinite(x) && float.IsFinite(y))
{
@@ -513,6 +513,22 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
public void SetVelocity(Vector2 velocity)
{
_checkDisposed();
var (x, y) = velocity;
if (!AreFinite(x, y))
{
return;
}
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
_checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();
@@ -667,7 +683,6 @@ namespace Robust.Client.Graphics.Clyde
_checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
{
if (FilterHandle == 0)
@@ -694,7 +709,7 @@ namespace Robust.Client.Graphics.Clyde
var (x, y) = position;
if (!ValidatePosition(x, y))
if (!AreFinite(x, y))
{
return false;
}
@@ -706,7 +721,7 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
private static bool ValidatePosition(float x, float y)
private static bool AreFinite(float x, float y)
{
if (float.IsFinite(x) && float.IsFinite(y))
{
@@ -716,6 +731,22 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
public void SetVelocity(Vector2 velocity)
{
_checkDisposed();
var (x, y) = velocity;
if (!AreFinite(x, y))
{
return;
}
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
_checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();

View File

@@ -12,7 +12,8 @@ namespace Robust.Client.Graphics.Clyde
static Clyde()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.ProcessArchitecture == Architecture.X64)
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
{
try
{

View File

@@ -7,6 +7,8 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
{
@@ -68,8 +70,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
_mainViewport.Eye = _eyeManager.CurrentEye;
RenderViewport(_mainViewport);
RenderViewport(_mainViewport); //Worldspace overlays are rendered here.
{
var handle = _renderHandle.DrawingHandleScreen;
var tex = _mainViewport.RenderTarget.Texture;
@@ -107,25 +108,67 @@ namespace Robust.Client.Graphics.Clyde
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list)
{
overlay.ClydeRender(_renderHandle, space);
}
FlushRenderQueue();
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list) {
if (overlay.RequestScreenTexture) {
FlushRenderQueue();
UpdateOverlayScreenTexture(space, _mainViewport.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer()) {
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space);
FlushRenderQueue();
}
}
}
private void DrawEntitiesAndWorldOverlay(Viewport viewport, Box2 worldBounds)
private ClydeTexture? ScreenBufferTexture;
private GLHandle screenBufferHandle;
private Vector2 lastFrameSize;
/// <summary>
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
/// </summary>
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture) {
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
List<Overlay> oTargets = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays) {
if (overlay.RequestScreenTexture && overlay.Space == space) {
oTargets.Add(overlay);
}
}
if (oTargets.Count > 0 && ScreenBufferTexture != null) {
if (lastFrameSize != _framebufferSize) {
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0, _hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, _framebufferSize.X, _framebufferSize.Y, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
}
lastFrameSize = _framebufferSize;
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
foreach (Overlay overlay in oTargets) {
overlay.ScreenTexture = ScreenBufferTexture;
}
oTargets.Clear();
return true;
}
return false;
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
{
return;
}
RenderOverlays(OverlaySpace.WorldSpaceBelowEntities);
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
@@ -267,14 +310,6 @@ namespace Robust.Client.Graphics.Clyde
_drawingSpriteList.Clear();
FlushRenderQueue();
// Cleanup remainders
foreach (var overlay in worldOverlays)
{
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
}
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -368,12 +403,21 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntitiesAndWorldOverlay(viewport, worldBounds);
DrawEntities(viewport, worldBounds);
}
RenderOverlays(OverlaySpace.WorldSpaceBelowFOV);
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
GL.Clear(ClearBufferMask.StencilBufferBit);
GL.Enable(EnableCap.StencilTest);
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
GL.StencilMask(0xFF);
ApplyFovToBuffer(viewport, eye);
GL.StencilMask(0x00);
GL.Disable(EnableCap.StencilTest);
}
}
@@ -401,6 +445,14 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(OverlaySpace.WorldSpace);
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
GL.Disable(EnableCap.DepthTest);
RenderOverlays(OverlaySpace.WorldSpaceFOVStencil);
GL.Disable(EnableCap.StencilTest);
}
PopRenderStateFull(state);

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using ES20 = OpenToolkit.Graphics.ES20;
namespace Robust.Client.Graphics.Clyde
@@ -31,6 +32,27 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, glHandle.Handle);
CheckGlError();
GL.ActiveTexture(TextureUnit.Texture0);
}
private void CopyRenderTextureToTexture(RenderTexture source, ClydeTexture target) {
LoadedRenderTarget sourceLoaded = RtToLoaded(source);
bool pause = sourceLoaded != _currentBoundRenderTarget;
FullStoredRendererState? store = null;
if (pause) {
store = PushRenderStateFull();
BindRenderTargetFull(source);
CheckGlError();
}
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
CheckGlError();
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
CheckGlError();
if (pause && store != null) {
PopRenderStateFull((FullStoredRendererState)store);
}
}
private static long EstPixelSize(PixelInternalFormat format)

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using static Robust.Client.GameObjects.ClientOccluderComponent;
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
namespace Robust.Client.Graphics.Clyde
{
@@ -195,7 +196,6 @@ namespace Robust.Client.Graphics.Clyde
}
}
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
_lightHardShaderHandle = LoadShaderHandle("/Shaders/Internal/light-hard.swsl");
_fovShaderHandle = LoadShaderHandle("/Shaders/Internal/fov.swsl");
@@ -361,16 +361,23 @@ namespace Robust.Client.Graphics.Clyde
FinalizeDepthDraw();
}
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(_lightManager.AmbientLightColor);
GL.Clear(ClearBufferMask.ColorBufferBit);
CheckGlError();
GL.Enable(EnableCap.StencilTest);
_isStencilling = true;
var (lightW, lightH) = GetLightMapSize(viewport.Size);
GL.Viewport(0, 0, lightW, lightH);
CheckGlError();
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(_lightManager.AmbientLightColor);
GL.ClearStencil(0xFF);
GL.StencilMask(0xFF);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
CheckGlError();
ApplyLightingFovToBuffer(viewport, eye);
var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle].Program;
lightShader.Use();
@@ -382,6 +389,11 @@ namespace Robust.Client.Graphics.Clyde
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One);
CheckGlError();
GL.StencilFunc(StencilFunction.Equal, 0xFF, 0xFF);
CheckGlError();
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Keep);
CheckGlError();
var lastRange = float.NaN;
var lastPower = float.NaN;
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
@@ -463,11 +475,11 @@ namespace Robust.Client.Graphics.Clyde
}
ResetBlendFunc();
GL.Disable(EnableCap.StencilTest);
_isStencilling = false;
CheckGlError();
ApplyLightingFovToBuffer(viewport, eye);
BlurOntoWalls(viewport, eye);
MergeWallLayer(viewport);
@@ -690,6 +702,13 @@ namespace Robust.Client.Graphics.Clyde
fovShader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
fovShader.SetUniformMaybe("center", eye.Position.Position);
GL.StencilMask(0xFF);
CheckGlError();
GL.StencilFunc(StencilFunction.Always, 0, 0);
CheckGlError();
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Replace);
CheckGlError();
DrawBlit(viewport, fovShader);
if (_hasGLSamplerObjects)
@@ -939,7 +958,8 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8,
name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}");
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize, lightMapColorFormat,
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize,
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true),
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

View File

@@ -280,7 +280,7 @@ namespace Robust.Client.Graphics.Clyde
}
/// <summary>
/// Flush the render handle, processing and re-pooling all the command lists.
/// Flushes the render handle, processing and re-pooling all the command lists.
/// </summary>
private void FlushRenderQueue()
{
@@ -371,6 +371,7 @@ namespace Robust.Client.Graphics.Clyde
program.Use();
int textureUnitVal = 0;
// Assign shader parameters to uniform since they may be dirty.
foreach (var (name, value) in instance.Parameters)
{
@@ -413,6 +414,15 @@ namespace Robust.Client.Graphics.Clyde
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
//Might change this in the future?
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
program.SetUniformTexture(name, cTarget);
textureUnitVal++;
break;
default:
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -428,7 +428,8 @@ namespace Robust.Client.Graphics.Clyde
private protected override void SetParameterImpl(string name, Texture value)
{
throw new NotImplementedException();
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetStencilOpImpl(StencilOp op)

View File

@@ -26,7 +26,7 @@ namespace Robust.Client.Graphics.Clyde
private readonly ConcurrentQueue<ClydeHandle> _textureDisposeQueue = new();
public Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null)
{
DebugTools.Assert(_mainThread == Thread.CurrentThread);
@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics.Clyde
return LoadTextureFromImage(image, name, loadParams);
}
public Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
{
DebugTools.Assert(_mainThread == Thread.CurrentThread);
@@ -56,19 +56,19 @@ namespace Robust.Client.Graphics.Clyde
{
// Disable sRGB so stuff doesn't get interpreter wrong.
actualParams.Srgb = false;
var img = ApplyA8Swizzle((Image<A8>) (object) image);
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
if (pixelType == typeof(L8) && !actualParams.Srgb)
{
var img = ApplyL8Swizzle((Image<L8>) (object) image);
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
}
// Flip image because OpenGL reads images upside down.
var copy = FlipClone(image);
using var copy = FlipClone(image);
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);
@@ -324,11 +324,13 @@ namespace Robust.Client.Graphics.Clyde
if (typeof(T) == typeof(A8))
{
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
return;
}
if (typeof(T) == typeof(L8))
{
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
return;
}
}

View File

@@ -315,6 +315,11 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
CreateMainViewport();
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
}
private void CreateMainViewport()

View File

@@ -101,7 +101,7 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null)
{
using (var image = Image.Load<Rgba32>(stream))
@@ -110,7 +110,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
{
return new DummyTexture((image.Width, image.Height));
@@ -267,6 +267,11 @@ namespace Robust.Client.Graphics.Clyde
{
// Nada.
}
public void SetVelocity(Vector2 velocity)
{
// Nada.
}
}
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;

View File

@@ -4,7 +4,6 @@ varying highp vec2 Pos;
uniform sampler2D lightMap;
uniform highp vec4 modulate;
#line 1000
// [SHADER_HEADER_CODE]
void main()
@@ -13,7 +12,6 @@ void main()
lowp vec4 COLOR;
#line 10000
// [SHADER_CODE]
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;

View File

@@ -89,9 +89,10 @@ uniform highp vec2 TEXTURE_PIXEL_SIZE;
// -- srgb emulation --
#ifdef HAS_SRGB
highp vec4 zTexture(highp vec2 uv)
highp vec4 zTextureSpec(sampler2D tex, highp vec2 uv)
{
return texture2D(TEXTURE, uv);
return texture2D(tex, uv);
}
highp vec4 zAdjustResult(highp vec4 col)
@@ -101,9 +102,9 @@ highp vec4 zAdjustResult(highp vec4 col)
#else
uniform lowp vec2 SRGB_EMU_CONFIG;
highp vec4 zTexture(highp vec2 uv)
highp vec4 zTextureSpec(sampler2D tex, highp vec2 uv)
{
highp vec4 col = texture2D(TEXTURE, uv);
highp vec4 col = texture2D(tex, uv);
if (SRGB_EMU_CONFIG.x > 0.5)
{
return zFromSrgb(col);
@@ -121,5 +122,10 @@ highp vec4 zAdjustResult(highp vec4 col)
}
#endif
highp vec4 zTexture(highp vec2 uv)
{
return zTextureSpec(TEXTURE, uv);
}
// -- Utilities End --

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -40,63 +41,48 @@ namespace Robust.Client.Graphics
return GetLineHeight(scale) - GetHeight(scale);
}
[Obsolete("Use GetAscent")] public int Ascent => GetAscent(1);
[Obsolete("Use GetHeight")] public int Height => GetHeight(1);
[Obsolete("Use GetDescent")] public int Descent => GetDescent(1);
[Obsolete("Use GetLineHeight")] public int LineHeight => GetLineHeight(1);
[Obsolete("Use GetLineSeparation")] public int LineSeparation => GetLineSeparation(1);
// Yes, I am aware that using char is bad.
// At the same time the font system is nowhere close to rendering Unicode so...
/// <summary>
/// Draw a character at a certain baseline position on screen.
/// </summary>
/// <param name="handle">The drawing handle to draw to.</param>
/// <param name="chr">
/// The Unicode code point to draw. Yes I'm aware about UTF-16 being crap,
/// do you think this system can draw anything except ASCII?
/// </param>
/// <param name="rune">The Unicode code point to draw.</param>
/// <param name="baseline">The baseline from which to draw the character.</param>
/// <param name="scale">DPI scale factor to render the font at.</param>
/// <param name="color">The color of the character to draw.</param>
/// <param name="fallback">If the character is not available, render "<22>" instead.</param>
/// <returns>How much to advance the cursor to draw the next character.</returns>
[Obsolete("Use DrawChar with scale support.")]
public float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, Color color)
{
return DrawChar(handle, chr, baseline, 1, color);
}
public abstract float DrawChar(
DrawingHandleScreen handle, Rune rune, Vector2 baseline, float scale,
Color color, bool fallback=true);
public abstract float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale,
Color color);
[Obsolete("Use Rune variant instead")]
public float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale, Color color)
{
return DrawChar(handle, (Rune) chr, baseline, scale, color);
}
/// <summary>
/// Gets metrics describing the dimensions and positioning of a single glyph in the font.
/// </summary>
/// <param name="chr">The character to fetch the glyph metrics for.</param>
/// <param name="rune">The unicode codepoint to fetch the glyph metrics for.</param>
/// <param name="scale">DPI scale factor to render the font at.</param>
/// <param name="fallback">
/// If the character is not available, return data for "<22>" instead.
/// This can still fail if the font does not define <20> itself.
/// </param>
/// <returns>
/// <c>null</c> if this font does not have a glyph for the specified character,
/// otherwise the metrics you asked for.
/// </returns>
/// <seealso cref="TryGetCharMetrics"/>
[Obsolete("Use GetCharMetrics with scale support.")]
public CharMetrics? GetCharMetrics(char chr)
{
return GetCharMetrics(chr, 1);
}
public abstract CharMetrics? GetCharMetrics(char chr, float scale);
public abstract CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true);
/// <summary>
/// Try-pattern version of <see cref="GetCharMetrics"/>.
/// </summary>
[Obsolete("Use TryGetCharMetrics with scale support.")]
public bool TryGetCharMetrics(char chr, out CharMetrics metrics)
public bool TryGetCharMetrics(Rune rune, float scale, out CharMetrics metrics, bool fallback=true)
{
return TryGetCharMetrics(chr, 1, out metrics);
}
public bool TryGetCharMetrics(char chr, float scale, out CharMetrics metrics)
{
var maybe = GetCharMetrics(chr, scale);
var maybe = GetCharMetrics(rune, scale);
if (maybe.HasValue)
{
metrics = maybe.Value;
@@ -128,15 +114,23 @@ namespace Robust.Client.Graphics
public override int GetDescent(float scale) => Handle.GetDescent(scale);
public override int GetLineHeight(float scale) => Handle.GetLineHeight(scale);
public override float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale, Color color)
public override float DrawChar(DrawingHandleScreen handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
{
var metrics = Handle.GetCharMetrics(chr, scale);
var metrics = Handle.GetCharMetrics(rune, scale);
if (!metrics.HasValue)
{
return 0;
if (fallback && !Rune.IsWhiteSpace(rune))
{
rune = new Rune('<27>');
metrics = Handle.GetCharMetrics(rune, scale);
if (!metrics.HasValue)
return 0;
}
else
return 0;
}
var texture = Handle.GetCharTexture(chr, scale);
var texture = Handle.GetCharTexture(rune, scale);
if (texture == null)
{
return metrics.Value.Advance;
@@ -147,9 +141,12 @@ namespace Robust.Client.Graphics
return metrics.Value.Advance;
}
public override CharMetrics? GetCharMetrics(char chr, float scale)
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
{
return Handle.GetCharMetrics(chr, scale);
var metrics = Handle.GetCharMetrics(rune, scale);
if (metrics == null && !Rune.IsWhiteSpace(rune) && fallback)
return Handle.GetCharMetrics(new Rune('<27>'), scale);
return metrics;
}
}
@@ -160,13 +157,13 @@ namespace Robust.Client.Graphics
public override int GetDescent(float scale) => default;
public override int GetLineHeight(float scale) => default;
public override float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale, Color color)
public override float DrawChar(DrawingHandleScreen handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
{
// Nada, it's a dummy after all.
return 0;
}
public override CharMetrics? GetCharMetrics(char chr, float scale)
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
{
// Nada, it's a dummy after all.
return null;

View File

@@ -31,10 +31,10 @@ namespace Robust.Client.Graphics
event Action<WindowFocusedEventArgs> OnWindowFocused;
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null);
Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>;
/// <summary>

View File

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

View File

@@ -1,22 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
[PublicAPI]
public interface IOverlayManager
{
void AddOverlay(Overlay overlay);
void RemoveOverlay(string id);
bool HasOverlay(string id);
bool AddOverlay(Overlay overlay);
Overlay GetOverlay(string id);
T GetOverlay<T>(string id) where T : Overlay;
bool RemoveOverlay(Overlay overlay);
bool RemoveOverlay(Type overlayClass);
bool RemoveOverlay<T>() where T : Overlay;
bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay);
bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay;
bool TryGetOverlay(Type overlayClass, out Overlay? overlay);
bool TryGetOverlay<T>(out T? overlay) where T : Overlay;
Overlay GetOverlay(Type overlayClass);
T GetOverlay<T>() where T : Overlay;
bool HasOverlay(Type overlayClass);
bool HasOverlay<T>() where T : Overlay;
IEnumerable<Overlay> AllOverlays { get; }
}

View File

@@ -1,65 +1,54 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using System;
namespace Robust.Client.Graphics
{
/// <summary>
/// An overlay is used for fullscreen drawing in the game, for example parallax.
/// An overlay is used for fullscreen drawing in the game. This can range from drawing parallax to a full screen shader.
/// </summary>
[PublicAPI]
public abstract class Overlay
{
/// <summary>
/// The ID of this overlay. This is used to identify it inside the <see cref="IOverlayManager"/>.
/// Determines when this overlay is drawn in the rendering queue.
/// </summary>
public string ID { get; }
public virtual bool AlwaysDirty => false;
public bool IsDirty => AlwaysDirty || _isDirty;
public bool Drawing { get; private set; }
public virtual OverlaySpace Space => OverlaySpace.ScreenSpace;
/// <summary>
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
/// some shaders will require it as a passed in uniform to operate.
/// </summary>
public virtual bool RequestScreenTexture => false;
/// <summary>
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.
/// </summary>
public Texture? ScreenTexture = null;
/// <summary>
/// Overlays on the same OverlaySpace will be drawn from lowest ZIndex to highest ZIndex. As an example, ZIndex -1 will be drawn before ZIndex 2.
/// This value is 0 by default. Overlays with same ZIndex will be drawn in an random order.
/// </summary>
public int? ZIndex { get; set; }
protected IOverlayManager OverlayManager { get; }
public int? ZIndex { get; set; }
private bool Disposed = false;
public virtual bool SubHandlesUseMainShader { get; } = true;
private bool _isDirty = true;
private readonly List<DrawingHandleBase> TempHandles = new();
private bool Disposed;
protected Overlay(string id)
public Overlay()
{
OverlayManager = IoCManager.Resolve<IOverlayManager>();
ID = id;
}
public void Dispose()
{
if (Disposed)
{
return;
}
Dispose(true);
Disposed = true;
GC.SuppressFinalize(this);
}
~Overlay()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
/// <summary>
/// If this function returns true, the target framebuffer will be wiped before applying this overlay to it.
/// </summary>
public virtual bool OverwriteTargetFrameBuffer(){
return false;
}
/// <summary>
@@ -69,50 +58,34 @@ namespace Robust.Client.Graphics
/// <param name="currentSpace">Current space that is being drawn. This function is called for every space that was set up in initialization.</param>
protected abstract void Draw(DrawingHandleBase handle, OverlaySpace currentSpace);
public void Dirty()
{
_isDirty = true;
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
~Overlay() {
Dispose();
}
public void Dispose() {
if (Disposed)
return;
else
DisposeBehavior();
}
protected virtual void DisposeBehavior(){
Disposed = true;
GC.SuppressFinalize(this);
}
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
internal void ClydeRender(IRenderHandle renderHandle, OverlaySpace currentSpace)
{
DrawingHandleBase handle;
if (currentSpace == OverlaySpace.WorldSpace)
handle = renderHandle.DrawingHandleWorld;
else
if (currentSpace == OverlaySpace.ScreenSpace || currentSpace == OverlaySpace.ScreenSpaceBelowWorld)
handle = renderHandle.DrawingHandleScreen;
else
handle = renderHandle.DrawingHandleWorld;
Draw(handle, currentSpace);
}
}
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace : byte
{
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b0000,
/// <summary>
/// This overlay will be drawn in the UI root, thus being in screen space.
/// </summary>
ScreenSpace = 0b0001,
/// <summary>
/// This overlay will be drawn in the world root, thus being in world space.
/// </summary>
WorldSpace = 0b0010,
/// <summary>
/// Drawn in screen coordinates, but behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b0100,
}
}

View File

@@ -1,13 +1,15 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
internal class OverlayManager : IOverlayManagerInternal
{
private readonly Dictionary<string, Overlay> _overlays = new();
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public void FrameUpdate(FrameEventArgs args)
{
@@ -17,59 +19,80 @@ namespace Robust.Client.Graphics
}
}
public void AddOverlay(Overlay overlay)
public bool AddOverlay(Overlay overlay)
{
if (_overlays.ContainsKey(overlay.ID))
{
throw new InvalidOperationException($"We already have an overlay with ID '{overlay.ID}'");
if(_overlays.ContainsKey(overlay.GetType()))
return false;
_overlays.Add(overlay.GetType(), overlay);
return true;
}
public bool RemoveOverlay(Type overlayClass)
{
if(!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("RemoveOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
_overlays.Add(overlay.ID, overlay);
return _overlays.Remove(overlayClass);
}
public Overlay GetOverlay(string id)
{
return _overlays[id];
public bool RemoveOverlay<T>() where T : Overlay{
return RemoveOverlay(typeof(T));
}
public T GetOverlay<T>(string id) where T : Overlay
{
return (T) GetOverlay(id);
public bool RemoveOverlay(Overlay overlay) {
return _overlays.Remove(overlay.GetType());
}
public bool HasOverlay(string id)
{
return _overlays.ContainsKey(id);
}
public void RemoveOverlay(string id)
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
{
if (!_overlays.TryGetValue(id, out var overlay))
{
return;
overlay = null;
if (!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("TryGetOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
overlay.Dispose();
_overlays.Remove(id);
return _overlays.TryGetValue(overlayClass, out overlay);
}
public bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay)
{
return _overlays.TryGetValue(id, out overlay);
}
public bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay
{
if (_overlays.TryGetValue(id, out var value))
{
overlay = (T) value;
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay {
overlay = null;
if(_overlays.TryGetValue(typeof(T), out Overlay? toReturn)){
overlay = (T)toReturn;
return true;
}
overlay = default;
return false;
}
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public Overlay GetOverlay(Type overlayClass) {
return _overlays[overlayClass];
}
public T GetOverlay<T>() where T : Overlay {
return (T)_overlays[typeof(T)];
}
public bool HasOverlay(Type overlayClass) {
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
Logger.Error("HasOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return _overlays.Remove(overlayClass);
}
public bool HasOverlay<T>() where T : Overlay {
return _overlays.Remove(typeof(T));
}
}
}

View File

@@ -19,10 +19,10 @@ namespace Robust.Client.Graphics
public sealed class State : IRsiStateLike
{
// List of delays for the frame to reach the next frame.
private readonly float[] Delays;
public readonly float[] Delays;
// 2D array for the texture to use for each animation frame at each direction.
private readonly Texture[][] Icons;
public readonly Texture[][] Icons;
internal State(Vector2i size, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
{

View File

@@ -1,47 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "RSI Image Format Validation Schema V1",
"description": "Robust Station Image",
"type": "object",
"definitions": {
"size": {
"type": "object",
"properties": {
"x": {"type": "integer", "minimum": 1},
"y": {"type": "integer", "minimum": 1}
},
"required": ["x","y"]
},
"directions": {
"type": "integer",
"enum": [1,4,8]
},
"state": {
"type": "object",
"properties": {
"name": {"type": "string"},
"flags": {"type": "object"}, //To be de-serialized as a Dictionary
"directions": {"$ref": "#/definitions/directions"},
"delays": {
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"items": {"type": "number", "minimum": 0, "exclusiveMinimum": true} //number == float
}
}
},
"required": ["name"] //'delays' is marked as optional in the spec
}
},
"properties": {
"version": {"type": "integer", "minimum": 1, "maximum": 1},
"size": {"$ref": "#/definitions/size"},
"states": {
"type": "array",
"items": {"$ref": "#/definitions/state"},
"minItems": 1
}
},
"required": ["version","size","states"]
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
using Robust.Shared.Maths;
namespace Robust.Client.Input
@@ -58,6 +59,7 @@ namespace Robust.Client.Input
}
public uint CodePoint { get; }
public Rune AsRune => new Rune(CodePoint);
}
public class KeyEventArgs : ModifierInputEventArgs

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -67,7 +68,7 @@ namespace Robust.Client.Physics
public override void Shutdown()
{
base.Shutdown();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsIslandOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}
private void HandleIslandSolveMessage(IslandSolveMessage message)
@@ -94,7 +95,7 @@ namespace Robust.Client.Physics
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsIslandOverlay() : base(nameof(PhysicsIslandOverlay))
public PhysicsIslandOverlay()
{
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
_eyeManager = IoCManager.Resolve<IEyeManager>();

View File

@@ -1,5 +1,7 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
namespace Robust.Client.Placement
{
public partial class PlacementManager
@@ -7,10 +9,9 @@ namespace Robust.Client.Placement
internal class PlacementOverlay : Overlay
{
private readonly PlacementManager _manager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PlacementOverlay(PlacementManager manager) : base("placement")
public PlacementOverlay(PlacementManager manager)
{
_manager = manager;
ZIndex = 100;

View File

@@ -2,6 +2,7 @@
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("Robust.Lite")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
#if NET5_0
[module: SkipLocalsInit]

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
@@ -53,20 +54,17 @@ namespace Robust.Client.Prototypes
private void ReloadPrototypeQueue()
{
#if !FULL_RELEASE
var then = DateTime.Now;
var sw = Stopwatch.StartNew();
var msg = NetManager.CreateNetMessage<MsgReloadPrototypes>();
msg.Paths = _reloadQueue.ToArray();
NetManager.ClientSendMessage(msg);
foreach (var path in _reloadQueue)
{
ReloadPrototypes(path);
}
ReloadPrototypes(_reloadQueue);
_reloadQueue.Clear();
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}

View File

@@ -10,5 +10,6 @@ namespace Robust.Client.ResourceManagement
void RsiLoaded(RsiLoadedEventArgs eventArgs);
void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null);
void PreloadTextures();
}
}

View File

@@ -0,0 +1,196 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
namespace Robust.Client.ResourceManagement
{
internal partial class ResourceCache
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public void PreloadTextures()
{
var sawmill = _logManager.GetSawmill("res.preload");
if (!_configurationManager.GetCVar(CVars.TexturePreloadingEnabled))
{
sawmill.Debug($"Skipping texture preloading due to CVar value.");
return;
}
PreloadTextures(sawmill);
PreloadRsis(sawmill);
}
private void PreloadTextures(ISawmill sawmill)
{
sawmill.Debug("Preloading textures...");
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<TextureResource>();
var texList = ContentFindFiles("/Textures/")
// Skip PNG files inside RSIs.
.Where(p => p.Extension == "png" && !p.ToString().Contains(".rsi/") && !resList.ContainsKey(p))
.Select(p => new TextureResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(texList, data =>
{
try
{
TextureResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in texList)
{
if (data.Bad)
continue;
try
{
TextureResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
var errors = 0;
foreach (var data in texList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var texResource = new TextureResource();
texResource.LoadFinish(this, data);
resList[data.Path] = texResource;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} textures ({CountErrored} errored) in {LoadTime}",
texList.Length,
errors,
sw.Elapsed);
}
private void PreloadRsis(ISawmill sawmill)
{
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<RSIResource>();
var rsiList = ContentFindFiles("/Textures/")
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
.Select(c => c.Directory)
.Where(p => !resList.ContainsKey(p))
.Select(p => new RSIResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(rsiList, data =>
{
try
{
RSIResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in rsiList)
{
if (data.Bad)
continue;
try
{
RSIResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
Parallel.ForEach(rsiList, data =>
{
if (data.Bad)
return;
try
{
RSIResource.LoadPostTexture(data);
}
catch (Exception e)
{
data.Bad = true;
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
}
});
var errors = 0;
foreach (var data in rsiList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var rsiRes = new RSIResource();
rsiRes.LoadFinish(this, data);
resList[data.Path] = rsiRes;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs ({CountErrored} errored) in {LoadTime}",
rsiList.Length,
errors,
sw.Elapsed);
}
}
}

View File

@@ -2,17 +2,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Newtonsoft.Json.Linq;
#if DEBUG
using NJsonSchema;
#endif
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,6 +20,14 @@ namespace Robust.Client.ResourceManagement
/// </summary>
public sealed class RSIResource : BaseResource
{
private static readonly float[] OneArray = {1};
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
AllowTrailingCommas = true
};
public RSI RSI { get; private set; } = default!;
/// <summary>
@@ -38,64 +42,59 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
var clyde = IoCManager.Resolve<IClyde>();
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
var loadStepData = new LoadStepData {Path = path};
LoadPreTexture(cache, loadStepData);
#if DEBUG
if (RSISchema != null)
{
var errors = RSISchema.Validate(manifestContents);
if (errors.Count != 0)
{
Logger.Error($"Unable to load RSI from '{path}', {errors.Count} errors:");
// Load atlas.
LoadTexture(clyde, loadStepData);
foreach (var error in errors)
{
Logger.Error("{0}", error.ToString());
}
LoadPostTexture(loadStepData);
LoadFinish(cache, loadStepData);
throw new RSILoadException($"{errors.Count} errors while loading RSI. See console.");
}
}
#endif
loadStepData.AtlasSheet.Dispose();
}
// Ok schema validated just fine.
var manifestJson = JObject.Parse(manifestContents);
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
}
var toAtlas = new List<(Image<Rgba32> src, Texture[][] output, int[][] indices, Vector2i[][] offsets, int totalFrameCount)>();
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
var metadata = LoadRsiMetadata(cache, data.Path);
var metaData = ParseMetaData(manifestJson);
var frameSize = metaData.Size;
var rsi = new RSI(frameSize, path);
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>();
var frameSize = metadata.Size;
var rsi = new RSI(frameSize, data.Path);
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>(stateCount);
// Do every state.
foreach (var stateObject in metaData.States)
for (var index = 0; index < metadata.States.Length; index++)
{
// Load image from disk.
var texPath = path / (stateObject.StateId + ".png");
var stream = cache.ContentFileRead(texPath);
Image<Rgba32> image;
using (stream)
{
image = Image.Load<Rgba32>(stream);
}
var sheetSize = new Vector2i(image.Width, image.Height);
ref var reg = ref toAtlas[index];
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
var stateObject = metadata.States[index];
// Load image from disk.
var texPath = data.Path / (stateObject.StateId + ".png");
using (var stream = cache.ContentFileRead(texPath))
{
reg.Src = Image.Load<Rgba32>(stream);
}
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
}
// Load all frames into a list so we can operate on it more sanely.
var frameCount = stateObject.Delays.Sum(delayList => delayList.Length);
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
var (foldedDelays, foldedIndices) = FoldDelays(stateObject.Delays);
@@ -108,29 +107,34 @@ namespace Robust.Client.ResourceManagement
callbackOffset[i] = new Vector2i[foldedIndices[0].Length];
}
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays, textures);
reg.Output = textures;
reg.Indices = foldedIndices;
reg.Offsets = callbackOffset;
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays,
textures);
rsi.AddState(state);
toAtlas.Add((image, textures, foldedIndices, callbackOffset, frameCount));
callbackOffsets[stateObject.StateId] = callbackOffset;
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.totalFrameCount);
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
var dimensionY = (int) MathF.Ceiling((float) totalFrameCount / dimensionX);
using var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
foreach (var (src, _, _, _, frameCount) in toAtlas)
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < frameCount; i++)
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (src.Width / frameSize.X);
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
@@ -141,30 +145,49 @@ namespace Robust.Client.ResourceManagement
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
src.Blit(srcBox, sheet, sheetPos);
reg.Src.Blit(srcBox, sheet, sheetPos);
}
sheetIndex += frameCount;
sheetIndex += reg.TotalFrameCount;
}
// Load atlas.
var texture = Texture.LoadFromImage(sheet, path.ToString());
for (var i = 0; i < toAtlas.Length; i++)
{
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
data.Rsi = rsi;
data.AtlasSheet = sheet;
data.AtlasList = toAtlas;
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
}
internal static void LoadPostTexture(LoadStepData data)
{
var dimX = data.DimX;
var toAtlas = data.AtlasList;
var frameSize = data.FrameSize;
var texture = data.AtlasTexture;
var sheetOffset = 0;
foreach (var (_, output, indices, offsets, frameCount) in toAtlas)
for (var toAtlasIndex = 0; toAtlasIndex < toAtlas.Length; toAtlasIndex++)
{
for (var i = 0; i < indices.Length; i++)
ref var reg = ref toAtlas[toAtlasIndex];
for (var i = 0; i < reg.Indices.Length; i++)
{
var dirIndices = indices[i];
var dirOutput = output[i];
var dirOffsets = offsets[i];
var dirIndices = reg.Indices[i];
var dirOutput = reg.Output[i];
var dirOffsets = reg.Offsets[i];
for (var j = 0; j < dirIndices.Length; j++)
{
var index = sheetOffset + dirIndices[j];
var sheetColumn = index % dimensionX;
var sheetRow = index / dimensionX;
var sheetColumn = index % dimX;
var sheetRow = index / dimX;
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
dirOffsets[j] = sheetPos;
@@ -172,22 +195,104 @@ namespace Robust.Client.ResourceManagement
}
}
sheetOffset += frameCount;
sheetOffset += reg.TotalFrameCount;
}
}
foreach (var (image, _, _, _, _) in toAtlas)
{
image.Dispose();
}
RSI = rsi;
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
RSI = data.Rsi;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(path, this, sheet, callbackOffsets));
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets));
}
}
private static RsiMetadata LoadRsiMetadata(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
// Ok schema validated just fine.
var manifestJson = JsonSerializer.Deserialize<RsiJsonMetadata>(manifestContents, SerializerOptions);
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
for (var stateI = 0; stateI < manifestJson.States.Length; stateI++)
{
var stateObject = manifestJson.States[stateI];
var stateName = stateObject.Name;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.Directions is { } dirVal)
{
dirValue = dirVal;
directions = dirVal switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.Delays != null)
{
delays = stateObject.Delays;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = OneArray;
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = OneArray;
}
}
states[stateI] = new StateMetadata(new RSI.StateId(stateName), directions, delays);
}
return new RsiMetadata(size, states);
}
/// <summary>
/// Folds a per-directional sets of animation delays
/// into an equivalent set of animation delays and indices that works for every direction.
@@ -336,109 +441,38 @@ namespace Robust.Client.ResourceManagement
return (floatDelays, arrayIndices);
}
internal static RsiMetadata ParseMetaData(JObject manifestJson)
internal sealed class LoadStepData
{
var size = manifestJson["size"]!.ToObject<Vector2i>();
var states = new List<StateMetadata>();
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
{
var stateName = stateObject["name"]!.ToObject<string>()!;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.TryGetValue("directions", out var dirJToken))
{
dirValue= dirJToken.ToObject<int>();
directions = dirValue switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.TryGetValue("delays", out var delayToken))
{
delays = delayToken.ToObject<float[][]>()!;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = new float[] {1};
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = new float[] {1};
}
}
states.Add(new StateMetadata(new RSI.StateId(stateName), directions, delays));
}
return new RsiMetadata(size, states);
public bool Bad;
public ResourcePath Path = default!;
public Image<Rgba32> AtlasSheet = default!;
public int DimX;
public StateReg[] AtlasList = default!;
public Vector2i FrameSize;
public Dictionary<RSI.StateId, Vector2i[][]> CallbackOffsets = default!;
public Texture AtlasTexture = default!;
public RSI Rsi = default!;
}
#if DEBUG
private static readonly JsonSchema? RSISchema = GetSchema();
private static JsonSchema? GetSchema()
internal struct StateReg
{
try
{
string schema;
using (var schemaStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Robust.Client.Graphics.RSI.RSISchema.json")!)
using (var schemaReader = new StreamReader(schemaStream))
{
schema = schemaReader.ReadToEnd();
}
return JsonSchema.FromJsonAsync(schema).Result;
}
catch (Exception e)
{
System.Console.WriteLine("Failed to load RSI JSON Schema!\n{0}", e);
return null;
}
public Image<Rgba32> Src;
public Texture[][] Output;
public int[][] Indices;
public Vector2i[][] Offsets;
public int TotalFrameCount;
}
#endif
internal sealed class RsiMetadata
{
public RsiMetadata(Vector2i size, List<StateMetadata> states)
public RsiMetadata(Vector2i size, StateMetadata[] states)
{
Size = size;
States = states;
}
public Vector2i Size { get; }
public List<StateMetadata> States { get; }
public StateMetadata[] States { get; }
}
internal sealed class StateMetadata
@@ -466,6 +500,17 @@ namespace Robust.Client.ResourceManagement
public float[][] Delays { get; }
}
// To be directly deserialized.
[UsedImplicitly]
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States)
{
}
[UsedImplicitly]
private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays)
{
}
}
[Serializable]

View File

@@ -1,7 +1,8 @@
using System.IO;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -9,41 +10,52 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.ResourceManagement
{
public class TextureResource : BaseResource
public sealed class TextureResource : BaseResource
{
public const float ClickThreshold = 0.25f;
private OwnedTexture _texture = default!;
public override ResourcePath Fallback => new("/Textures/noSprite.png");
public override ResourcePath? Fallback => new("/Textures/noSprite.png");
public Texture Texture { get; private set; } = default!;
public Texture Texture => _texture;
public override void Load(IResourceCache cache, ResourcePath path)
{
if (!cache.TryContentFileRead(path, out var stream))
{
throw new FileNotFoundException("Content file does not exist for texture");
}
var clyde = IoCManager.Resolve<IClyde>();
using (stream)
{
// Primarily for tracking down iCCP sRGB errors in the image files.
Logger.DebugS("res.tex", $"Loading texture {path}.");
var data = new LoadStepData {Path = path};
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
var manager = IoCManager.Resolve<IClyde>();
using var image = Image.Load<Rgba32>(stream);
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
}
}
LoadPreTexture(cache, data);
LoadTexture(clyde, data);
LoadFinish(cache, data);
}
private static TextureLoadParameters? _tryLoadTextureParameters(IResourceCache cache, ResourcePath path)
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
using (var stream = cache.ContentFileRead(data.Path))
{
data.Image = Image.Load<Rgba32>(stream);
}
data.LoadParameters = TryLoadTextureParameters(cache, data.Path) ?? TextureLoadParameters.Default;
}
internal static void LoadTexture(IClyde clyde, LoadStepData data)
{
data.Texture = clyde.LoadTextureFromImage(data.Image, data.Path.ToString(), data.LoadParameters);
}
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
_texture = data.Texture;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(data.Path, data.Image, this));
}
data.Image.Dispose();
}
private static TextureLoadParameters? TryLoadTextureParameters(IResourceCache cache, ResourcePath path)
{
var metaPath = path.WithName(path.Filename + ".yml");
if (cache.TryContentFileRead(metaPath, out var stream))
@@ -70,6 +82,38 @@ namespace Robust.Client.ResourceManagement
return null;
}
public override void Reload(IResourceCache cache, ResourcePath path, CancellationToken ct = default)
{
var clyde = IoCManager.Resolve<IClyde>();
var data = new LoadStepData {Path = path};
LoadPreTexture(cache, data);
if (data.Image.Width == Texture.Width && data.Image.Height == Texture.Height)
{
// Dimensions match, rewrite texture in place.
_texture.SetSubImage(Vector2i.Zero, data.Image);
}
else
{
// Dimensions do not match, make new texture.
_texture.Dispose();
LoadTexture(clyde, data);
_texture = data.Texture;
}
data.Image.Dispose();
}
internal sealed class LoadStepData
{
public ResourcePath Path = default!;
public Image<Rgba32> Image = default!;
public TextureLoadParameters LoadParameters;
public OwnedTexture Texture = default!;
public bool Bad;
}
// TODO: Due to a bug in Roslyn, NotNullIfNotNullAttribute doesn't work.
// So this can't work with both nullables and non-nullables at the same time.
// I decided to only have it work with non-nullables as such.

View File

@@ -14,7 +14,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
@@ -39,9 +38,6 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" Condition="'$(Configuration)' == 'Debug'">
<LogicalName>Robust.Client.Graphics.RSI.RSISchema.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>

View File

@@ -385,16 +385,16 @@ namespace Robust.Client.UserInterface.Controls
var offsetY = (int) (box.Height - font.GetHeight(UIScale)) / 2;
var baseLine = new Vector2i(0, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
foreach (var chr in text)
foreach (var rune in text.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
if (!(baseLine.X < box.Left || baseLine.X + metrics.Advance > box.Right))
{
font.DrawChar(handle, chr, baseLine, UIScale, color);
font.DrawChar(handle, rune, baseLine, UIScale, color);
}
baseLine += (metrics.Advance, 0);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using Robust.Client.Graphics;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
@@ -180,15 +181,15 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = CalcBaseline();
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (chr == '\n')
if (rune == new Rune('\n'))
{
newlines += 1;
baseLine = CalcBaseline();
}
var advance = font.DrawChar(handle, chr, baseLine, UIScale, actualFontColor);
var advance = font.DrawChar(handle, rune, baseLine, UIScale, actualFontColor);
baseLine += (advance, 0);
}
}
@@ -252,16 +253,16 @@ namespace Robust.Client.UserInterface.Controls
var font = ActualFont;
var height = font.GetHeight(UIScale);
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (chr == '\n')
if (rune == new Rune('\n'))
{
_cachedTextWidths.Add(0);
height += font.GetLineHeight(UIScale);
}
else
{
var metrics = font.GetCharMetrics(chr, UIScale);
var metrics = font.GetCharMetrics(rune, UIScale);
if (metrics == null)
{
continue;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Input;
@@ -23,6 +24,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StyleClassLineEditNotEditable = "notEditable";
public const string StylePseudoClassPlaceholder = "placeholder";
// It is assumed that these two positions are NEVER inside a surrogate pair in the text buffer.
private int _cursorPosition;
private int _selectionStart;
private string _text = "";
@@ -126,7 +128,11 @@ namespace Robust.Client.UserInterface.Controls
get => _cursorPosition;
set
{
_cursorPosition = MathHelper.Clamp(value, 0, _text.Length);
var clamped = MathHelper.Clamp(value, 0, _text.Length);
if (_text.Length != 0 && _text.Length != clamped && !Rune.TryGetRuneAt(_text, clamped, out _))
throw new ArgumentException("Cannot set cursor inside surrogate pair.");
_cursorPosition = clamped;
_selectionStart = _cursorPosition;
}
}
@@ -134,7 +140,14 @@ namespace Robust.Client.UserInterface.Controls
public int SelectionStart
{
get => _selectionStart;
set => _selectionStart = MathHelper.Clamp(value, 0, _text.Length);
set
{
var clamped = MathHelper.Clamp(value, 0, _text.Length);
if (_text.Length != 0 && _text.Length != clamped && !Rune.TryGetRuneAt(_text, clamped, out _))
throw new ArgumentException("Cannot set cursor inside surrogate pair.");
_selectionStart = clamped;
}
}
public int SelectionLower => Math.Min(_selectionStart, _cursorPosition);
@@ -275,7 +288,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
InsertAtCursor(((char) args.CodePoint).ToString());
InsertAtCursor(args.AsRune.ToString());
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
@@ -302,8 +315,16 @@ namespace Robust.Client.UserInterface.Controls
}
else if (_cursorPosition != 0)
{
_text = _text.Remove(_cursorPosition - 1, 1);
_cursorPosition -= 1;
var remPos = _cursorPosition - 1;
var remAmt = 1;
// If this is a low surrogate remove two chars to remove the whole pair.
if (char.IsLowSurrogate(_text[remPos]))
{
remPos -= 1;
remAmt = 2;
}
_text = _text.Remove(remPos, remAmt);
_cursorPosition -= remAmt;
changed = true;
}
@@ -330,7 +351,10 @@ namespace Robust.Client.UserInterface.Controls
}
else if (_cursorPosition < _text.Length)
{
_text = _text.Remove(_cursorPosition, 1);
var remAmt = 1;
if (char.IsHighSurrogate(_text[_cursorPosition]))
remAmt = 2;
_text = _text.Remove(_cursorPosition, remAmt);
changed = true;
}
@@ -352,10 +376,7 @@ namespace Robust.Client.UserInterface.Controls
}
else
{
if (_cursorPosition != 0)
{
_cursorPosition -= 1;
}
ShiftCursorLeft();
_selectionStart = _cursorPosition;
}
@@ -370,10 +391,7 @@ namespace Robust.Client.UserInterface.Controls
}
else
{
if (_cursorPosition != _text.Length)
{
_cursorPosition += 1;
}
ShiftCursorRight();
_selectionStart = _cursorPosition;
}
@@ -404,19 +422,13 @@ namespace Robust.Client.UserInterface.Controls
}
else if (args.Function == EngineKeyFunctions.TextCursorSelectLeft)
{
if (_cursorPosition != 0)
{
_cursorPosition -= 1;
}
ShiftCursorLeft();
args.Handle();
}
else if (args.Function == EngineKeyFunctions.TextCursorSelectRight)
{
if (_cursorPosition != _text.Length)
{
_cursorPosition += 1;
}
ShiftCursorRight();
args.Handle();
}
@@ -525,6 +537,32 @@ namespace Robust.Client.UserInterface.Controls
// Reset this so the cursor is always visible immediately after a keybind is pressed.
_resetCursorBlink();
void ShiftCursorLeft()
{
if (_cursorPosition == 0)
return;
_cursorPosition -= 1;
if (char.IsLowSurrogate(_text[_cursorPosition]))
_cursorPosition -= 1;
}
void ShiftCursorRight()
{
if (_cursorPosition == _text.Length)
return;
_cursorPosition += 1;
// Before you confuse yourself on "shouldn't this be high surrogate since shifting left checks low"
// (Because yes, I did myself too a week after writing it)
// char.IsLowSurrogate(_text[_cursorPosition]) means "is the cursor between a surrogate pair"
// because we ALREADY moved.
if (_cursorPosition != _text.Length && char.IsLowSurrogate(_text[_cursorPosition]))
_cursorPosition += 1;
}
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
@@ -555,11 +593,11 @@ namespace Robust.Client.UserInterface.Controls
var index = 0;
var chrPosX = contentBox.Left - _drawOffset;
var lastChrPostX = contentBox.Left - _drawOffset;
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
index += 1;
index += rune.Utf16SequenceLength;
continue;
}
@@ -570,7 +608,7 @@ namespace Robust.Client.UserInterface.Controls
lastChrPostX = chrPosX;
chrPosX += metrics.Advance;
index += 1;
index += rune.Utf16SequenceLength;
if (chrPosX > contentBox.Right)
{
@@ -586,6 +624,9 @@ namespace Robust.Client.UserInterface.Controls
if (index > 0 && distanceRight > distanceLeft)
{
index -= 1;
if (char.IsLowSurrogate(_text[index]))
index -= 1;
}
return index;
@@ -652,60 +693,87 @@ namespace Robust.Client.UserInterface.Controls
}
// Approach for NextWordPosition and PrevWordPosition taken from Avalonia.
private int NextWordPosition(string str, int cursor)
internal static int NextWordPosition(string str, int cursor)
{
if (cursor >= str.Length)
{
return str.Length;
}
var charClass = GetCharClass(str[cursor]);
var charClass = GetCharClass(Rune.GetRuneAt(str, cursor));
var i = cursor;
for (; i < str.Length && GetCharClass(str[i]) == charClass; i++)
{
}
for (; i < str.Length && GetCharClass(str[i]) == CharClass.Whitespace; i++)
{
}
IterForward(charClass);
IterForward(CharClass.Whitespace);
return i;
void IterForward(CharClass cClass)
{
while (i < str.Length)
{
var rune = Rune.GetRuneAt(str, i);
if (GetCharClass(rune) != cClass)
break;
i += rune.Utf16SequenceLength;
}
}
}
private int PrevWordPosition(string str, int cursor)
internal static int PrevWordPosition(string str, int cursor)
{
if (cursor == 0)
{
return 0;
}
var charClass = GetCharClass(str[cursor - 1]);
var startRune = GetRuneBackwards(str, cursor - 1);
var charClass = GetCharClass(startRune);
var i = cursor;
for (; i > 0 && GetCharClass(str[i - 1]) == charClass; i--)
{
}
IterBackward();
if (charClass == CharClass.Whitespace)
{
charClass = GetCharClass(str[i - 1]);
for (; i > 0 && GetCharClass(str[i - 1]) == charClass; i--)
{
}
if (!Rune.TryGetRuneAt(str, i - 1, out var midRune))
midRune = Rune.GetRuneAt(str, i - 2);
charClass = GetCharClass(midRune);
IterBackward();
}
return i;
void IterBackward()
{
while (i > 0)
{
var rune = GetRuneBackwards(str, i - 1);
if (GetCharClass(rune) != charClass)
break;
i -= rune.Utf16SequenceLength;
}
}
static Rune GetRuneBackwards(string str, int i)
{
return Rune.TryGetRuneAt(str, i, out var rune) ? rune : Rune.GetRuneAt(str, i - 1);
}
}
private CharClass GetCharClass(char chr)
internal static CharClass GetCharClass(Rune rune)
{
if (char.IsWhiteSpace(chr))
if (Rune.IsWhiteSpace(rune))
{
return CharClass.Whitespace;
}
if (char.IsLetterOrDigit(chr))
if (Rune.IsLetterOrDigit(rune))
{
return CharClass.AlphaNumeric;
}
@@ -713,7 +781,7 @@ namespace Robust.Client.UserInterface.Controls
return CharClass.Other;
}
private enum CharClass : byte
internal enum CharClass : byte
{
Other,
AlphaNumeric,
@@ -767,7 +835,7 @@ namespace Robust.Client.UserInterface.Controls
var posX = 0;
var actualCursorPosition = 0;
var actualSelectionStartPosition = 0;
foreach (var chr in renderedText)
foreach (var chr in renderedText.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
{
@@ -776,7 +844,7 @@ namespace Robust.Client.UserInterface.Controls
}
posX += metrics.Advance;
count += 1;
count += chr.Utf16SequenceLength;
if (count == _master._cursorPosition)
{
@@ -816,9 +884,9 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = (-drawOffset, offsetY + font.GetAscent(UIScale)) +
contentBox.TopLeft;
foreach (var chr in renderedText)
foreach (var rune in renderedText.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
@@ -832,7 +900,7 @@ namespace Robust.Client.UserInterface.Controls
// Make sure we're not off the left edge of the box.
if (baseLine.X + metrics.BearingX + metrics.Width >= contentBox.Left)
{
font.DrawChar(handle, chr, baseLine, UIScale, renderedTextColor);
font.DrawChar(handle, rune, baseLine, UIScale, renderedTextColor);
}
baseLine += (metrics.Advance, 0);

View File

@@ -163,9 +163,9 @@ namespace Robust.Client.UserInterface.Controls
var titleLength = 0;
// Get string length.
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
@@ -196,14 +196,14 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = new Vector2(0, font.GetAscent(UIScale)) + contentBox.TopLeft;
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
font.DrawChar(handle, chr, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
font.DrawChar(handle, rune, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
baseLine += new Vector2(metrics.Advance, 0);
}
@@ -295,9 +295,9 @@ namespace Robust.Client.UserInterface.Controls
var titleLength = 0;
// Get string length.
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}

View File

@@ -219,9 +219,9 @@ namespace Robust.Client.UserInterface.Controls
{
var offset = itemSelected.GetContentOffset(Vector2.Zero);
var baseLine = offset + (hOffset, vOffset + font.GetAscent(UIScale));
foreach (var chr in item.Text)
foreach (var rune in item.Text.EnumerateRunes())
{
baseLine += (font.DrawChar(handle, chr, baseLine, UIScale, Color.White), 0);
baseLine += (font.DrawChar(handle, rune, baseLine, UIScale, Color.White), 0);
}
}

View File

@@ -2,12 +2,14 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.ContentPack;
using Robust.Shared.Input;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -301,43 +303,95 @@ namespace Robust.Client.UserInterface.CustomControls
private async void _loadHistoryFromDisk()
{
CommandBar.ClearHistory();
Stream stream;
try
var sawmill = Logger.GetSawmill("dbgconsole");
var data = await Task.Run(async () =>
{
stream = _resourceManager.UserData.OpenRead(HistoryPath);
}
catch (FileNotFoundException)
{
// Nada, nothing to load in that case.
return;
}
try
{
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
Stream? stream = null;
for (var i = 0; i < 3; i++)
{
var data = JsonConvert.DeserializeObject<List<string>>(await reader.ReadToEndAsync());
CommandBar.ClearHistory();
CommandBar.History.AddRange(data);
CommandBar.HistoryIndex = CommandBar.History.Count;
try
{
stream = _resourceManager.UserData.OpenRead(HistoryPath);
break;
}
catch (FileNotFoundException)
{
// Nada, nothing to load in that case.
return null;
}
catch (IOException)
{
// File locked probably??
await Task.Delay(10);
}
}
}
finally
{
stream?.Dispose();
}
if (stream == null)
{
sawmill.Warning("Failed to load debug console history!");
return null;
}
try
{
return await JsonSerializer.DeserializeAsync<string[]>(stream);
}
catch (Exception e)
{
sawmill.Warning("Failed to load debug console history due to exception!\n{e}");
return null;
}
finally
{
// ReSharper disable once MethodHasAsyncOverload
stream.Dispose();
}
});
if (data == null)
return;
CommandBar.ClearHistory();
CommandBar.History.AddRange(data);
CommandBar.HistoryIndex = CommandBar.History.Count;
}
private void _flushHistoryToDisk()
private async void _flushHistoryToDisk()
{
using (var stream = _resourceManager.UserData.Create(HistoryPath))
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
CommandBar.HistoryIndex = CommandBar.History.Count;
var sawmill = Logger.GetSawmill("dbgconsole");
var newHistory = JsonSerializer.Serialize(CommandBar.History);
await Task.Run(async () =>
{
var data = JsonConvert.SerializeObject(CommandBar.History);
CommandBar.HistoryIndex = CommandBar.History.Count;
writer.Write(data);
}
Stream? stream = null;
for (var i = 0; i < 3; i++)
{
try
{
stream = _resourceManager.UserData.Create(HistoryPath);
break;
}
catch (IOException)
{
// Probably locking.
await Task.Delay(10);
}
}
if (stream == null)
{
sawmill.Warning("Failed to save debug console history!");
return;
}
// ReSharper disable once UseAwaitUsing
using var writer = new StreamWriter(stream, EncodingHelpers.UTF8);
// ReSharper disable once MethodHasAsyncOverload
writer.Write(newHistory);
});
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -67,6 +67,7 @@ namespace Robust.Client.UserInterface.CustomControls
Title = Loc.GetString("Entity Spawn Panel");
SetSize = (250, 300);
MinSize = (250, 200);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -65,7 +66,7 @@ namespace Robust.Client.UserInterface
var wordSizePixels = 0;
// The horizontal position of the text cursor.
var posX = 0;
var lastChar = 'A';
var lastRune = new Rune('A');
// If a word is larger than maxSizeX, we split it.
// We need to keep track of some data to split it into two words.
(int breakIndex, int wordSizePixels)? forceSplitData = null;
@@ -83,14 +84,14 @@ namespace Robust.Client.UserInterface
var text = tagText.Text;
// And go over every character.
for (var i = 0; i < text.Length; i++, breakIndexCounter++)
foreach (var rune in text.EnumerateRunes())
{
var chr = text[i];
breakIndexCounter += 1;
if (IsWordBoundary(lastChar, chr) || chr == '\n')
if (IsWordBoundary(lastRune, rune) || rune == new Rune('\n'))
{
// Word boundary means we know where the word ends.
if (posX > maxSizeX && lastChar != ' ')
if (posX > maxSizeX && lastRune != new Rune(' '))
{
DebugTools.Assert(wordStartBreakIndex.HasValue,
"wordStartBreakIndex can only be null if the word begins at a new line, in which case this branch shouldn't be reached as the word would be split due to being longer than a single line.");
@@ -109,22 +110,22 @@ namespace Robust.Client.UserInterface
forceSplitData = null;
// Just manually handle newlines.
if (chr == '\n')
if (rune == new Rune('\n'))
{
LineBreaks.Add(breakIndexCounter);
Height += font.GetLineHeight(uiScale);
maxUsedWidth = Math.Max(maxUsedWidth, posX);
posX = 0;
lastChar = chr;
lastRune = rune;
wordStartBreakIndex = null;
continue;
}
}
// Uh just skip unknown characters I guess.
if (!font.TryGetCharMetrics(chr, uiScale, out var metrics))
if (!font.TryGetCharMetrics(rune, uiScale, out var metrics))
{
lastChar = chr;
lastRune = rune;
continue;
}
@@ -165,7 +166,7 @@ namespace Robust.Client.UserInterface
}
}
lastChar = chr;
lastRune = rune;
}
}
@@ -185,7 +186,7 @@ namespace Robust.Client.UserInterface
Logger.Error("wordStartBreakIndex: null (duh)");
Logger.Error($"wordSizePixels: {wordSizePixels}");
Logger.Error($"posX: {posX}");
Logger.Error($"lastChar: {lastChar}");
Logger.Error($"lastChar: {lastRune}");
Logger.Error($"forceSplitData: {forceSplitData}");
Logger.Error($"LineBreaks: {string.Join(", ", LineBreaks)}");
@@ -247,9 +248,10 @@ namespace Robust.Client.UserInterface
case FormattedMessage.TagText tagText:
{
var text = tagText.Text;
for (var i = 0; i < text.Length; i++, globalBreakCounter++)
foreach (var rune in text.EnumerateRunes())
{
var chr = text[i];
globalBreakCounter += 1;
if (lineBreakIndex < LineBreaks.Count &&
LineBreaks[lineBreakIndex] == globalBreakCounter)
{
@@ -257,7 +259,7 @@ namespace Robust.Client.UserInterface
lineBreakIndex += 1;
}
var advance = font.DrawChar(handle, chr, baseLine, uiScale, currentColorTag.Color);
var advance = font.DrawChar(handle, rune, baseLine, uiScale, currentColorTag.Color);
baseLine += new Vector2(advance, 0);
}
@@ -268,9 +270,9 @@ namespace Robust.Client.UserInterface
}
[Pure]
private static bool IsWordBoundary(char a, char b)
private static bool IsWordBoundary(Rune a, Rune b)
{
return a == ' ' || b == ' ' || a == '-' || b == '-';
return a == new Rune(' ') || b == new Rune(' ') || a == new Rune('-') || b == new Rune('-');
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Console;
using Robust.Client.Graphics;
@@ -181,8 +181,6 @@ namespace Robust.Client.UserInterface
public void FrameUpdate(FrameEventArgs args)
{
RootControl.DoFrameUpdate(args);
// Process queued style & layout updates.
while (_styleUpdateQueue.Count != 0)
{
@@ -220,6 +218,8 @@ namespace Robust.Client.UserInterface
RunArrange(control);
}
RootControl.DoFrameUpdate(args);
// count down tooltip delay if we're not showing one yet and
// are hovering the mouse over a control without moving it
if (_tooltipDelay != null && !showingTooltip)

View File

@@ -42,19 +42,20 @@ namespace Robust.Client.Utility
{
var dstSpan = destination.GetPixelSpan();
var dstWidth = destination.Width;
var srcHeight = sourceRect.Height;
var srcWidth = sourceRect.Width;
var (ox, oy) = destinationOffset;
for (var y = 0; y < sourceRect.Height; y++)
for (var y = 0; y < srcHeight; y++)
{
var sourceRowOffset = sourceWidth * (y + sourceRect.Top) + sourceRect.Left;
var destRowOffset = dstWidth * (y + oy) + ox;
for (var x = 0; x < sourceRect.Width; x++)
{
var pixel = source[x + sourceRowOffset];
dstSpan[x + destRowOffset] = pixel;
}
var srcRow = source[sourceRowOffset..(sourceRowOffset + srcWidth)];
var dstRow = dstSpan[destRowOffset..(destRowOffset + srcWidth)];
srcRow.CopyTo(dstRow);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -41,6 +41,40 @@ namespace Robust.Client.Utility
return specifier.RsiStateLike().Default;
}
public static float[] FrameDelays(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Delays;
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new float[0];
default:
throw new NotImplementedException();
}
}
public static Texture[] FrameArr(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Icons[0];
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new Texture[0];
default:
throw new NotImplementedException();
}
}
public static IDirectionalTextureProvider DirFrame0(this SpriteSpecifier specifier)
{
return specifier.RsiStateLike();

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
@@ -141,6 +142,10 @@ namespace Robust.Server
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
{
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("AAAAAAAAAA");
_config.Initialize(true);
if (LoadConfigAndUserData)
@@ -178,6 +183,8 @@ namespace Robust.Server
_config.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_config);
//Sets up Logging
_logHandlerFactory = logHandlerFactory;
@@ -327,6 +334,8 @@ namespace Robust.Server
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
}
GC.Collect();
return false;
}

View File

@@ -51,7 +51,7 @@ namespace Robust.Server.GameObjects
}
}
public class PlayerAttachSystemMessage : EntitySystemMessage
public class PlayerAttachSystemMessage : EntityEventArgs
{
public PlayerAttachSystemMessage(IEntity entity, IPlayerSession newPlayer)
{
@@ -63,7 +63,7 @@ namespace Robust.Server.GameObjects
public IPlayerSession NewPlayer { get; }
}
public class PlayerDetachedSystemMessage : EntitySystemMessage
public class PlayerDetachedSystemMessage : EntityEventArgs
{
public PlayerDetachedSystemMessage(IEntity entity)
{

View File

@@ -8,6 +8,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -24,7 +25,7 @@ namespace Robust.Server.GameObjects
[DataField("visible")]
private bool _visible = true;
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
private int _drawDepth = DrawDepthTag.Default;
[DataField("scale")]
@@ -54,17 +55,19 @@ namespace Robust.Server.GameObjects
get => _drawDepth;
set
{
if (_drawDepth == value) return;
_drawDepth = value;
Dirty();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set
{
if (_visible == value) return;
_visible = value;
Dirty();
}

View File

@@ -2,7 +2,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
public sealed class EntityDeletedMessage : EntitySystemMessage
public sealed class EntityDeletedMessage : EntityEventArgs
{
public IEntity Entity { get; }

View File

@@ -12,7 +12,7 @@ namespace Robust.Server.GameObjects
/// <remarks>
/// List is empty if it's no longer intersecting any.
/// </remarks>
public sealed class TileLookupUpdateMessage : EntitySystemMessage
public sealed class TileLookupUpdateMessage : EntityEventArgs
{
public Dictionary<GridId, List<Vector2i>>? NewIndices { get; }

View File

@@ -1,20 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Server.Player;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.GameObjects
{
[UsedImplicitly]
public class AudioSystem : EntitySystem, IAudioSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
private const int AudioDistanceRange = 25;
private uint _streamIndex;
@@ -66,147 +63,6 @@ namespace Robust.Server.GameObjects
return unchecked(_streamIndex++);
}
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
/// <param name="predicate">The predicate that will be used to send the audio to players, or null to send to everyone.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
/// <param name="recipients"></param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayGlobal(string filename, AudioParams? audioParams = null, Func<IPlayerSession, bool>? predicate = null, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
{
FileName = filename,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
if (predicate == null && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
IList<IPlayerSession> players = predicate != null ? _playerManager.GetPlayersBy(predicate) : _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
var player = players[i];
if (player == excludedSession)
{
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Sessions that won't receive the audio message.</param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayFromEntity(string filename, IEntity entity, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioEntityMessage
{
FileName = filename,
Coordinates = entity.Transform.Coordinates,
EntityUid = entity.Uid,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
// send to every player
if (range <= 0 && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
List<IPlayerSession> players;
if (range > 0.0f)
players = _playerManager.GetPlayersInRange(entity.Transform.Coordinates, range);
else
players = _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
var player = players[i];
if (player == excludedSession)
{
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayAtCoords(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
if (range <= 0 && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
List<IPlayerSession> players;
if (range > 0.0f)
players = _playerManager.GetPlayersInRange(coordinates, range);
else
players = _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
var player = players[i];
if (player == excludedSession)
{
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <inheritdoc />
public int DefaultSoundRange => AudioDistanceRange;
@@ -214,7 +70,7 @@ namespace Robust.Server.GameObjects
public int OcclusionCollisionMask { get; set; }
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
public IPlayingAudioStream Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
@@ -234,7 +90,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
public IPlayingAudioStream Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
//TODO: Calculate this from PAS
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
@@ -254,7 +110,7 @@ namespace Robust.Server.GameObjects
var recipients = (playerFilter as IFilter).Recipients;
if (range > 0.0f)
players = PASInRange(recipients, entity.Transform.MapPosition, range);
players = PasInRange(recipients, entity.Transform.MapPosition, range);
else
players = recipients;
@@ -267,7 +123,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
public IPlayingAudioStream Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
//TODO: Calculate this from PAS
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
@@ -285,7 +141,7 @@ namespace Robust.Server.GameObjects
var recipients = (playerFilter as IFilter).Recipients;
if (range > 0.0f)
players = PASInRange(recipients, coordinates.ToMap(EntityManager), range);
players = PasInRange(recipients, coordinates.ToMap(EntityManager), range);
else
players = recipients;
@@ -297,7 +153,7 @@ namespace Robust.Server.GameObjects
return new AudioSourceServer(this, id, players);
}
private static List<ICommonSession> PASInRange(IEnumerable<ICommonSession> players, MapCoordinates position, float range)
private static List<ICommonSession> PasInRange(IEnumerable<ICommonSession> players, MapCoordinates position, float range)
{
return players.Where(x =>
x.AttachedEntity != null &&

View File

@@ -1,4 +1,6 @@
using JetBrains.Annotations;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -10,11 +12,19 @@ namespace Robust.Server.GameObjects
public class PhysicsSystem : SharedPhysicsSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public override void Initialize()
{
base.Initialize();
_mapManager.OnGridCreated += HandleGridCreated;
LoadMetricCVar();
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
}
private void LoadMetricCVar()
{
MetricsEnabled = _configurationManager.GetCVar(CVars.MetricsEnabled);
}
public override void Shutdown()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -132,14 +132,6 @@ namespace Robust.Server.GameObjects
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntityNoMapInit(string? protoName, EntityCoordinates coordinates)
{
var newEnt = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) newEnt);
return newEnt;
}
/// <inheritdoc />
public List<EntityState>? GetEntityStates(GameTick fromTick, IPlayerSession player)
{

View File

@@ -87,7 +87,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;
@@ -98,7 +98,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel targetConnection)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;

View File

@@ -27,7 +27,7 @@ namespace Robust.Server.Player
/// Do not call this directly for most content code.
/// </summary>
/// <param name="a">The entity to attach to.</param>
void AttachToEntity(IEntity a);
void AttachToEntity(IEntity? a);
/// <summary>
/// Detaches this player from an entity.

View File

@@ -1,8 +1,8 @@
using Robust.Shared.GameStates;
using Robust.Server.GameObjects;
using System;
using Robust.Server.GameObjects;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
@@ -43,7 +43,7 @@ namespace Robust.Server.Player
private SessionStatus _status = SessionStatus.Connecting;
/// <inheritdoc />
[ViewVariables]
internal string Name { get; set; }
@@ -109,7 +109,7 @@ namespace Robust.Server.Player
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <inheritdoc />
public void AttachToEntity(IEntity a)
public void AttachToEntity(IEntity? a)
{
DetachFromEntity();

View File

@@ -1,4 +1,4 @@
using System;
using System.Diagnostics;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.IoC;
@@ -14,7 +14,7 @@ namespace Robust.Server.Prototypes
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConGroupController _conGroups = default!;
public ServerPrototypeManager() : base()
public ServerPrototypeManager()
{
RegisterIgnore("shader");
}
@@ -35,14 +35,11 @@ namespace Robust.Server.Prototypes
return;
}
var then = DateTime.Now;
var sw = Stopwatch.StartNew();
foreach (var path in msg.Paths)
{
ReloadPrototypes(path);
}
ReloadPrototypes(msg.Paths);
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -16,12 +17,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public int X;
[JsonInclude] public int X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public int Y;
[JsonInclude] public int Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -12,12 +13,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public uint X;
[JsonInclude] public uint X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public uint Y;
[JsonInclude] public uint Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -74,7 +74,7 @@ namespace Robust.Shared.Audio
return GetAudio()?.Play(playerFilter, filename, coordinates, audioParams);
}
internal class QueryAudioSystem : EntitySystemMessage
internal class QueryAudioSystem : EntityEventArgs
{
public IAudioSystem? Audio { get; set; }
}

View File

@@ -93,6 +93,10 @@ namespace Robust.Shared
public static readonly CVarDef<int> SysWinTickPeriod =
CVarDef.Create("sys.win_tick_period", 3, CVar.SERVERONLY);
// On non-FULL_RELEASE builds, use ProfileOptimization/tiered JIT to speed up game startup.
public static readonly CVarDef<bool> SysProfileOpt =
CVarDef.Create("sys.profile_opt", true);
#if DEBUG
public static readonly CVarDef<float> NetFakeLoss = CVarDef.Create("net.fakeloss", 0f, CVar.CHEAT);
public static readonly CVarDef<float> NetFakeLagMin = CVarDef.Create("net.fakelagmin", 0f, CVar.CHEAT);
@@ -331,6 +335,9 @@ namespace Robust.Shared
/// A small length used as a collision and constraint tolerance. Usually it is
/// chosen to be numerically significant, but visually insignificant.
/// </summary>
/// <remarks>
/// Note that some joints may have this cached and not update on value change.
/// </remarks>
public static readonly CVarDef<float> LinearSlop =
CVarDef.Create("physics.linearslop", 0.005f);
@@ -372,8 +379,9 @@ namespace Robust.Shared
// - Maximums
// Squared
// 35 m/s, AKA half a tile per frame allowed. Divide this by frametime to get units per second.
public static readonly CVarDef<float> MaxLinVelocity =
CVarDef.Create("physics.maxlinvelocity", 4.0f);
CVarDef.Create("physics.maxlinvelocity", 0.56f);
// Squared
public static readonly CVarDef<float> MaxAngVelocity =
@@ -385,5 +393,15 @@ namespace Robust.Shared
public static readonly CVarDef<bool> DiscordEnabled =
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
/*
* RES
*/
public static readonly CVarDef<bool> ResCheckPathCasing =
CVarDef.Create("res.checkpathcasing", false);
public static readonly CVarDef<bool> TexturePreloadingEnabled =
CVarDef.Create("res.texturepreloadingenabled", true, CVar.CLIENTONLY);
}
}

View File

@@ -251,7 +251,7 @@ namespace Robust.Shared.Configuration
if (invokeImmediately)
{
onValueChanged((T) (reg.Value ?? reg.DefaultValue)!);
onValueChanged(GetCVar<T>(name));
}
}

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.Containers
/// Raised when the contents of a container have been modified.
/// </summary>
[PublicAPI]
public abstract class ContainerModifiedMessage : EntitySystemMessage
public abstract class ContainerModifiedMessage : EntityEventArgs
{
/// <summary>
/// The container being acted upon.

View File

@@ -219,7 +219,8 @@ namespace Robust.Shared.ContentPack
continue;
}
var msg = $"{name}: ILVerify: {string.Format(res.Message, res.Args)}";
var formatted = res.Args == null ? res.Message : string.Format(res.Message, res.Args);
var msg = $"{name}: ILVerify: {formatted}";
try
{

View File

@@ -18,16 +18,19 @@ namespace Robust.Shared.ContentPack
{
private readonly DirectoryInfo _directory;
private readonly ISawmill _sawmill;
private readonly bool _checkCasing;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="directory">Directory to mount.</param>
/// <param name="sawmill"></param>
public DirLoader(DirectoryInfo directory, ISawmill sawmill)
/// <param name="checkCasing"></param>
public DirLoader(DirectoryInfo directory, ISawmill sawmill, bool checkCasing)
{
_directory = directory;
_sawmill = sawmill;
_checkCasing = checkCasing;
}
/// <inheritdoc />
@@ -80,6 +83,9 @@ namespace Robust.Shared.ContentPack
[Conditional("DEBUG")]
private void CheckPathCasing(ResourcePath path)
{
if (!_checkCasing)
return;
// Run this inside the thread pool due to overhead.
Task.Run(() =>
{

View File

@@ -345,7 +345,7 @@ namespace Robust.Shared.ContentPack
// Otherwise it would load the assemblies a second time which is an amazing way to have everything break.
if (_useLoadContext)
{
Logger.DebugS("res.mod", $"RESOLVING DEFAULT: {name}");
_logManager.GetSawmill("res.mod").Debug($"RESOLVING DEFAULT: {name}");
foreach (var module in LoadedModules)
{
if (module.GetName().Name == name.Name)

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
/// <summary>
/// Get the full directory path that the executable is located in.
/// </summary>
private static string GetExecutableDirectory()
internal static string GetExecutableDirectory()
{
// TODO: remove this shitty hack, either through making it less hardcoded into shared,
// or by making our file structure less spaghetti somehow.

View File

@@ -135,7 +135,7 @@ namespace Robust.Shared.ContentPack
throw new DirectoryNotFoundException("Specified directory does not exist: " + pathInfo.FullName);
}
var loader = new DirLoader(pathInfo, Logger.GetSawmill("res"));
var loader = new DirLoader(pathInfo, Logger.GetSawmill("res"), _config.GetCVar(CVars.ResCheckPathCasing));
AddRoot(prefix, loader);
}

View File

@@ -567,6 +567,7 @@ Types:
- "System.Text.Encoding get_UTF8()"
- "System.Text.Encoding get_UTF32()"
NormalizationForm: { } # Enum
Rune: { All: True }
StringBuilder:
Methods:
- "bool Equals(System.ReadOnlySpan`1<char>)"
@@ -1131,7 +1132,7 @@ Types:
- "string[] Split(string, System.StringSplitOptions)"
- "string[] Split(string[], int, System.StringSplitOptions)"
- "string[] Split(string[], System.StringSplitOptions)"
- "StringRuneEnumerator EnumerateRunes()"
- "System.Text.StringRuneEnumerator EnumerateRunes()"
- "System.CharEnumerator GetEnumerator()"
- "System.ReadOnlySpan`1<char> op_Implicit(string)"
- "System.TypeCode GetTypeCode()"

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Robust.Shared.Enums {
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace {
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b000000,
/// <summary>
/// This overlay will be drawn in screen coordinates in the UI space above the world.
/// </summary>
ScreenSpace = 0b000001,
/// <summary>
/// This overlay will be drawn directly above normal worldspace, but a stencil equivalent to the FOV will be applied.
/// You likely want to use <see cref="WorldSpaceBelowFOV"/>. This space should only be used if you fully understand why you need this.
/// </summary>
WorldSpaceFOVStencil = 0b000010,
/// <summary>
/// This overlay will be drawn above entities, lighting, and FOV.
/// </summary>
WorldSpace = 0b000100,
/// <summary>
/// This overlay will be drawn beneath FOV; above lighting and entities.
/// </summary>
WorldSpaceBelowFOV = 0b001000,
/// <summary>
/// This overlay will be drawn beneath entities, lighting, and FOV; above grids.
/// </summary>
WorldSpaceBelowEntities = 0b010000,
/// <summary>
/// This overlay will be drawn in screen coordinates behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b100000,
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -12,6 +12,7 @@ namespace Robust.Shared.GameObjects
public class ComponentDependencyManager : IComponentDependencyManager
{
[IoC.Dependency] private readonly IComponentFactory _componentFactory = null!;
[IoC.Dependency] private readonly IComponentManager _componentManager = null!;
/// <summary>
/// Cache of queries and their corresponding field offsets
@@ -22,18 +23,16 @@ namespace Robust.Shared.GameObjects
new();
/// <inheritdoc />
public void OnComponentAdd(IEntity entity, IComponent newComp)
public void OnComponentAdd(EntityUid eUid, IComponent newComp)
{
SetDependencyForEntityComponents(entity, newComp.GetType(), newComp);
InjectIntoComponent(entity, newComp);
SetDependencyForEntityComponents(eUid, newComp, newComp.GetType());
InjectIntoComponent(eUid, newComp);
}
/// <summary>
/// Filling the dependencies of newComp by iterating over entity's components
/// </summary>
/// <param name="entity"></param>
/// <param name="newComp"></param>
public void InjectIntoComponent(IEntity entity, IComponent newComp)
private void InjectIntoComponent(EntityUid eUid, IComponent newComp)
{
var queries = GetPointerQueries(newComp);
@@ -43,7 +42,7 @@ namespace Robust.Shared.GameObjects
}
//get all present components in entity
foreach (var entityComp in entity.GetAllComponents())
foreach (var entityComp in _componentManager.GetComponents(eUid))
{
var entityCompReg = _componentFactory.GetRegistration(entityComp);
foreach (var reference in entityCompReg.References)
@@ -61,24 +60,22 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public void OnComponentRemove(IEntity entity, IComponent removedComp)
public void OnComponentRemove(EntityUid eUid, IComponent removedComp)
{
ClearRemovedComponentDependencies(removedComp);
SetDependencyForEntityComponents(entity, removedComp.GetType(), null);
SetDependencyForEntityComponents(eUid, null, removedComp.GetType());
}
/// <summary>
/// Updates all dependencies to type compType on the entity (in fields on components), to the value of comp
/// </summary>
/// <param name="entity"></param>
/// <param name="compType"></param>
/// <param name="comp"></param>
private void SetDependencyForEntityComponents(IEntity entity, Type compType, IComponent? comp)
private void SetDependencyForEntityComponents(EntityUid eUid, IComponent? comp, Type compType)
{
var compReg = _componentFactory.GetRegistration(compType);
//check if any are requesting our component as a dependency
foreach (var entityComponent in entity.GetAllComponents())
var entityComponents = _componentManager.GetComponents(eUid);
foreach (var entityComponent in entityComponents)
{
//get entry for out entityComponent
var queries = GetPointerQueries(entityComponent);
@@ -113,7 +110,7 @@ namespace Robust.Shared.GameObjects
}
}
private void SetField(object o, int offset, object? value)
private static void SetField(object o, int offset, object? value)
{
var asDummy = Unsafe.As<FieldOffsetDummy>(o);
ref var @ref = ref Unsafe.Add(ref asDummy.A, offset);
@@ -182,7 +179,7 @@ namespace Robust.Shared.GameObjects
return queries;
}
private Action<object>? GetEventMethod(MethodInfo[] methods, string methodName, MethodInfo getterMethod)
private static Action<object>? GetEventMethod(MethodInfo[] methods, string methodName, MethodInfo getterMethod)
{
var method = methods.FirstOrDefault(m => m.Name == methodName);
if (method == null) return null;
@@ -196,7 +193,7 @@ namespace Robust.Shared.GameObjects
return o => @delegate((T) o);
}
private int GetFieldOffset(Type type, FieldInfo field)
private static int GetFieldOffset(Type type, FieldInfo field)
{
var fieldOffsetField = typeof(FieldOffsetDummy).GetField("A")!;
var dynamicMethod = new DynamicMethod(

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System;
namespace Robust.Shared.GameObjects
@@ -15,12 +15,21 @@ namespace Robust.Shared.GameObjects
/// </summary>
public IComponent Component { get; }
/// <summary>
/// EntityUid of the entity this component belongs to.
/// </summary>
public EntityUid OwnerUid { get; }
/// <summary>
/// Constructs a new instance of <see cref="ComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
protected ComponentEventArgs(IComponent component) => Component = component;
/// <param name="ownerUid">EntityUid of the entity this component belongs to.</param>
protected ComponentEventArgs(IComponent component, EntityUid ownerUid)
{
Component = component;
OwnerUid = ownerUid;
}
}
/// <summary>
@@ -32,7 +41,8 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="AddedComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
public AddedComponentEventArgs(IComponent component) : base(component) { }
/// <param name="uid">EntityUid of the entity this component belongs to.</param>
public AddedComponentEventArgs(IComponent component, EntityUid uid) : base(component, uid) { }
}
/// <summary>
@@ -44,7 +54,8 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="RemovedComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
public RemovedComponentEventArgs(IComponent component) : base(component) { }
/// <param name="uid">EntityUid of the entity this component belongs to.</param>
public RemovedComponentEventArgs(IComponent component, EntityUid uid) : base(component, uid) { }
}
/// <summary>
@@ -56,7 +67,7 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="DeletedComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
public DeletedComponentEventArgs(IComponent component) : base(component) { }
/// <param name="uid">EntityUid of the entity this component belongs to.</param>
public DeletedComponentEventArgs(IComponent component, EntityUid uid) : base(component, uid) { }
}
}

View File

@@ -64,6 +64,15 @@ namespace Robust.Shared.GameObjects
/// </summary>
private readonly HashSet<string> IgnoredComponentNames = new();
/// <inheritdoc />
public event Action<IComponentRegistration>? ComponentAdded;
/// <inheritdoc />
public event Action<(IComponentRegistration, Type)>? ComponentReferenceAdded;
/// <inheritdoc />
public event Action<string>? ComponentIgnoreAdded;
/// <inheritdoc />
public IEnumerable<Type> AllRegisteredTypes => types.Keys;
@@ -137,6 +146,7 @@ namespace Robust.Shared.GameObjects
{
netIDs[netID.Value] = registration;
}
ComponentAdded?.Invoke(registration);
}
[Obsolete("Use RegisterClass and Attributes instead of the Register/RegisterReference combo")]
@@ -158,6 +168,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException($"Attempted to register a reference twice: {@interface}");
}
registration.References.Add(@interface);
ComponentReferenceAdded?.Invoke((registration, @interface));
}
public void RegisterIgnore(string name, bool overwrite = false)
@@ -178,12 +189,13 @@ namespace Robust.Shared.GameObjects
}
IgnoredComponentNames.Add(name);
ComponentIgnoreAdded?.Invoke(name);
}
private void RemoveComponent(string name)
{
var registration = names[name];
names.Remove(registration.Name);
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
types.Remove(registration.Type);

View File

@@ -15,7 +15,6 @@ namespace Robust.Shared.GameObjects
public class ComponentManager : IComponentManager
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IComponentDependencyManager _componentDependencyManager = default!;
#if EXCEPTION_TOLERANCE
@@ -45,9 +44,17 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public event EventHandler<ComponentEventArgs>? ComponentDeleted;
private bool initialized;
public void Initialize()
{
if (initialized)
throw new InvalidOperationException("Already initialized.");
initialized = true;
FillComponentDict();
_componentFactory.ComponentAdded += OnComponentAdded;
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
}
/// <inheritdoc />
@@ -59,6 +66,26 @@ namespace Robust.Shared.GameObjects
_deleteSet.Clear();
FillComponentDict();
}
public void Dispose()
{
_componentFactory.ComponentAdded -= OnComponentAdded;
_componentFactory.ComponentReferenceAdded -= OnComponentReferenceAdded;
}
private void OnComponentAdded(IComponentRegistration obj)
{
_entTraitDict.Add(obj.Type, new Dictionary<EntityUid, Component>());
var netID = obj.NetID;
if (netID.HasValue)
_entNetIdDict.Add(netID.Value, new Dictionary<EntityUid, Component>());
}
private void OnComponentReferenceAdded((IComponentRegistration, Type) obj)
{
_entTraitDict.Add(obj.Item2, new Dictionary<EntityUid, Component>());
}
#region Component Management
@@ -120,17 +147,16 @@ namespace Robust.Shared.GameObjects
if (component.NetID != null)
{
// the main comp grid keeps this in sync
var netId = component.NetID.Value;
_entNetIdDict[netId].Add(uid, component);
// mark the component as dirty for networking
component.Dirty();
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component));
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component, uid));
}
_componentDependencyManager.OnComponentAdd(entity, component);
_componentDependencyManager.OnComponentAdd(entity.Uid, component);
component.OnAdd();
@@ -243,8 +269,8 @@ namespace Robust.Shared.GameObjects
component.Running = false;
component.OnRemove();
_componentDependencyManager.OnComponentRemove(_entityManager.GetEntity(uid), component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component));
_componentDependencyManager.OnComponentRemove(uid, component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
@@ -270,7 +296,7 @@ namespace Robust.Shared.GameObjects
component.Running = false;
component.OnRemove(); // Sets delete
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component));
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, component.Owner.Uid));
}
@@ -308,7 +334,7 @@ namespace Robust.Shared.GameObjects
// mark the owning entity as dirty for networking
component.Owner.Dirty();
ComponentDeleted?.Invoke(this, new DeletedComponentEventArgs(component));
ComponentDeleted?.Invoke(this, new DeletedComponentEventArgs(component, entityUid));
}
/// <inheritdoc />
@@ -354,8 +380,7 @@ namespace Robust.Shared.GameObjects
}
}
var ent = _entityManager.GetEntity(uid);
throw new KeyNotFoundException($"Entity {ent} does not have a component of type {type}");
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {type}");
}
/// <inheritdoc />
@@ -371,8 +396,7 @@ namespace Robust.Shared.GameObjects
}
}
var ent = _entityManager.GetEntity(uid);
throw new KeyNotFoundException($"Entity {ent} does not have a component of NetID {netId}");
throw new KeyNotFoundException($"Entity {uid} does not have a component of NetID {netId}");
}
/// <inheritdoc />

View File

@@ -1,6 +1,6 @@
namespace Robust.Shared.GameObjects
{
public class CollisionChangeMessage : EntitySystemMessage
public class CollisionChangeMessage : EntityEventArgs
{
public PhysicsComponent Body { get; }

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
@@ -9,7 +9,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Called every tick for colliding bodies. Called once per pair.
/// </summary>
public sealed class CollisionMessage : EntitySystemMessage
public sealed class CollisionMessage : EntityEventArgs
{
public readonly IPhysBody BodyA;
public readonly IPhysBody BodyB;
@@ -65,7 +65,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Sent whenever a <see cref="IPhysBody"/> is changed.
/// </summary>
public sealed class PhysicsUpdateMessage : EntitySystemMessage
public sealed class PhysicsUpdateMessage : EntityEventArgs
{
public PhysicsComponent Component { get; }
@@ -75,7 +75,7 @@ namespace Robust.Shared.GameObjects
}
}
public sealed class FixtureUpdateMessage : EntitySystemMessage
public sealed class FixtureUpdateMessage : EntityEventArgs
{
public PhysicsComponent Body { get; }

View File

@@ -1,4 +1,4 @@
/*
/*
* Farseer Physics Engine:
* Copyright (c) 2012 Ian Qvist
*
@@ -102,26 +102,29 @@ namespace Robust.Shared.GameObjects
if (_bodyType == value)
return;
var oldAnchored = _bodyType == BodyType.Static;
_bodyType = value;
ResetMassData();
if (_bodyType == BodyType.Static)
{
SetAwake(false);
_linVelocity = Vector2.Zero;
_angVelocity = 0.0f;
// SynchronizeFixtures(); TODO: When CCD
}
Awake = true;
else
{
SetAwake(true);
}
Force = Vector2.Zero;
Torque = 0.0f;
RegenerateContacts();
var oldAnchored = _bodyType == BodyType.Static;
var anchored = _bodyType == BodyType.Static;
var anchored = value == BodyType.Static;
if (oldAnchored != anchored)
{
@@ -143,36 +146,34 @@ namespace Robust.Shared.GameObjects
get => _awake;
set
{
if (_awake == value)
return;
if (_bodyType == BodyType.Static) return;
if (BodyType == BodyType.Static)
{
// Check nothing slipped through
DebugTools.Assert(!_awake);
return;
}
_awake = value;
if (value)
{
_sleepTime = 0.0f;
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(this));
}
else
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsSleepMessage(this));
ResetDynamics();
_sleepTime = 0.0f;
}
Dirty();
SetAwake(value);
}
}
private bool _awake = true;
private void SetAwake(bool value)
{
if (_awake == value) return;
_awake = value;
if (value)
{
_sleepTime = 0.0f;
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(this));
}
else
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsSleepMessage(this));
ResetDynamics();
_sleepTime = 0.0f;
}
Dirty();
}
/// <summary>
/// You can disable sleeping on this body. If you disable sleeping, the
/// body will be woken.
@@ -952,7 +953,7 @@ namespace Robust.Shared.GameObjects
public void ApplyLinearImpulse(in Vector2 impulse)
{
if (_bodyType != BodyType.Dynamic) return;
if ((_bodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) return;
Awake = true;
LinearVelocity += impulse * _invMass;
@@ -960,7 +961,7 @@ namespace Robust.Shared.GameObjects
public void ApplyAngularImpulse(float impulse)
{
if (_bodyType != BodyType.Dynamic) return;
if ((_bodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) return;
Awake = true;
AngularVelocity += impulse * InvI;

View File

@@ -1,10 +1,72 @@
using System;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// An optimisation component for stuff that should be set as collidable when its awake and non-collidable when asleep.
/// An optimisation component for stuff that should be set as collidable when it's awake and non-collidable when asleep.
/// </summary>
public sealed class CollisionWakeComponent : Component
{
public override string Name => "CollisionWake";
public override uint? NetID => NetIDs.COLLISION_WAKE;
[DataField("enabled")]
private bool _enabled = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled
{
get => _enabled;
set
{
if (value == _enabled) return;
_enabled = value;
Dirty();
RaiseStateChange();
}
}
private void RaiseStateChange()
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new CollisionWakeStateMessage(), false);
}
public override void OnRemove()
{
base.OnRemove();
if (Owner.TryGetComponent(out IPhysBody? body))
{
body.CanCollide = true;
}
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new CollisionWakeState(Enabled);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not CollisionWakeState state) return;
Enabled = state.Enabled;
}
[Serializable, NetSerializable]
public class CollisionWakeState : ComponentState
{
public bool Enabled { get; }
public CollisionWakeState(bool enabled) : base(NetIDs.COLLISION_WAKE)
{
Enabled = enabled;
}
}
}
}

View File

@@ -1,18 +1,18 @@
namespace Robust.Shared.GameObjects
namespace Robust.Shared.GameObjects
{
public interface IComponentDependencyManager
{
/// <summary>
/// Called when newComp has just been added into entity.
/// </summary>
/// <param name="entity">Entity in which newComp was added</param>
/// <param name="eUid">Entity in which newComp was added</param>
/// <param name="newComp">Added component</param>
public void OnComponentAdd(IEntity entity, IComponent newComp);
public void OnComponentAdd(EntityUid eUid, IComponent newComp);
/// <summary>
/// Called when newComp has just been removed from entity.
/// </summary>
/// <param name="entity">Entity out of which newComp was removed</param>
/// <param name="eUid">Entity out of which newComp was removed</param>
/// <param name="removedComp">Removed component</param>
public void OnComponentRemove(IEntity entity, IComponent removedComp); }
public void OnComponentRemove(EntityUid eUid, IComponent removedComp); }
}

View File

@@ -129,5 +129,6 @@ namespace Robust.Shared.GameObjects
IEnumerable<EntityUid> ChildEntityUids { get; }
Matrix3 GetLocalMatrix();
Matrix3 GetLocalMatrixInv();
void DetachParentToNull();
}
}

View File

@@ -57,9 +57,9 @@ namespace Robust.Shared.GameObjects
EntitySystem.Get<OccluderSystem>().AddOrUpdateEntity(Owner, Owner.Transform.Coordinates);
}
public override void OnRemove()
protected override void Shutdown()
{
base.OnRemove();
base.Shutdown();
var transform = Owner.Transform;
var map = transform.MapID;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -51,20 +51,6 @@ namespace Robust.Shared.GameObjects
_gridIndex = GridId.Invalid;
}
public override void OnRemove()
{
if(GridIndex != GridId.Invalid)
{
if(_mapManager.GridExists(_gridIndex))
{
Logger.DebugS("map", $"Entity {Owner.Uid} removed grid component, removing bound grid {_gridIndex}");
_mapManager.DeleteGrid(_gridIndex);
}
}
base.OnRemove();
}
/// <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