mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e16732eb7b | ||
|
|
91f61bb9de | ||
|
|
ddc91d05ec | ||
|
|
ef22842b90 | ||
|
|
303e2152d2 | ||
|
|
37fc0d0d2a | ||
|
|
53987e1e5d | ||
|
|
3216d7770b | ||
|
|
3203ca2ff4 | ||
|
|
e22254cd51 | ||
|
|
7ed722f669 | ||
|
|
4864096b2a | ||
|
|
5161385de4 | ||
|
|
98e009b38f | ||
|
|
3863ab8f62 | ||
|
|
f576eb5125 | ||
|
|
314742ccd8 | ||
|
|
f9074811f9 | ||
|
|
5f3e1eb378 | ||
|
|
3c1ee20ca1 | ||
|
|
3768f5e68e | ||
|
|
765a560380 | ||
|
|
39ae3ac653 | ||
|
|
e48f4027e5 | ||
|
|
2fa1e98faf | ||
|
|
cedfa0ee2f | ||
|
|
92f44b390e | ||
|
|
65a42f9209 | ||
|
|
ebf53248cf | ||
|
|
289f637e8a | ||
|
|
d7c13f30c8 | ||
|
|
0dac17ae5e | ||
|
|
9a19a774fa | ||
|
|
81f49d5eb2 | ||
|
|
4f3b4ac2d2 | ||
|
|
e428056b52 | ||
|
|
8dc9d2989a | ||
|
|
fd8c90dcbb | ||
|
|
ffe4e5a8ab | ||
|
|
6e5026d270 | ||
|
|
946c4166dc | ||
|
|
7d2fb85a04 | ||
|
|
d6ec078519 | ||
|
|
32256fc4d9 | ||
|
|
37bbdfe7ff | ||
|
|
c906675cdf | ||
|
|
90bb5574c1 | ||
|
|
7b50dcd969 | ||
|
|
8d82f48a8f | ||
|
|
469f9fd219 | ||
|
|
1a5783ab4e | ||
|
|
3d25886d79 | ||
|
|
516b2cd372 | ||
|
|
3cfcfa0be2 | ||
|
|
69328087bd | ||
|
|
1bf8b2a52b | ||
|
|
fc6dc6f4e1 | ||
|
|
31c1feca4e | ||
|
|
3ed1eef2ab | ||
|
|
1394a017bb | ||
|
|
6b0670d5f1 | ||
|
|
f573331541 | ||
|
|
a7218cd3b8 | ||
|
|
f7e8178736 | ||
|
|
31f921e4aa | ||
|
|
aa1c25637c | ||
|
|
71f2c48463 | ||
|
|
d65f4ca898 | ||
|
|
b35568ffe5 | ||
|
|
a0d241e551 | ||
|
|
33a6934582 | ||
|
|
f237a8bbbc | ||
|
|
4bc775c01c | ||
|
|
93b4d81505 | ||
|
|
0afb85a09e | ||
|
|
7b9315cea4 | ||
|
|
dc3af45096 | ||
|
|
00ce0179ae | ||
|
|
81947ba3d8 | ||
|
|
49327279d0 | ||
|
|
0936cf3c7f | ||
|
|
43b75a69c2 | ||
|
|
c17c8d7a11 | ||
|
|
223fd8126f | ||
|
|
1d5559be4a | ||
|
|
0b749ff8bb | ||
|
|
069fa89fcb | ||
|
|
80f9f24243 | ||
|
|
93018c9843 | ||
|
|
e2675271d0 | ||
|
|
d1f7edecef | ||
|
|
b5a3c0b988 | ||
|
|
06e62b031a |
Submodule Lidgren.Network/Lidgren.Network updated: 73554e6061...5fc11c2b2b
4
Resources/Prototypes/dummy_entity.yml
Normal file
4
Resources/Prototypes/dummy_entity.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- type: entity
|
||||
name: blank entity
|
||||
id: BlankEntity
|
||||
abstract: true
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
10
Robust.Analyzers/Diagnostics.cs
Normal file
10
Robust.Analyzers/Diagnostics.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Generators
|
||||
{
|
||||
public static class Diagnostics
|
||||
{
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
}
|
||||
}
|
||||
42
Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Normal file
42
Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Generators;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class MeansImplicitAssigmentSuppressor : DiagnosticSuppressor
|
||||
{
|
||||
const string MeansImplicitAssignmentAttribute = "Robust.Shared.MeansImplicitAssignmentAttribute";
|
||||
|
||||
public override void ReportSuppressions(SuppressionAnalysisContext context)
|
||||
{
|
||||
var implAttr = context.Compilation.GetTypeByMetadataName(MeansImplicitAssignmentAttribute);
|
||||
foreach (var reportedDiagnostic in context.ReportedDiagnostics)
|
||||
{
|
||||
if(reportedDiagnostic.Id != Diagnostics.MeansImplicitAssignment.SuppressedDiagnosticId) continue;
|
||||
|
||||
var node = reportedDiagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(reportedDiagnostic.Location.SourceSpan);
|
||||
if (node == null) continue;
|
||||
|
||||
var symbol = context.GetSemanticModel(reportedDiagnostic.Location.SourceTree).GetDeclaredSymbol(node);
|
||||
|
||||
if (symbol == null || !symbol.GetAttributes().Any(a =>
|
||||
a.AttributeClass?.GetAttributes().Any(attr =>
|
||||
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, implAttr)) == true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
context.ReportSuppression(Suppression.Create(
|
||||
Diagnostics.MeansImplicitAssignment,
|
||||
reportedDiagnostic));
|
||||
}
|
||||
}
|
||||
|
||||
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Diagnostics.MeansImplicitAssignment);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
|
||||
/// <seealso cref="AnimationPlayerComponent"/>
|
||||
public sealed class Animation
|
||||
{
|
||||
public readonly List<AnimationTrack> AnimationTracks = new();
|
||||
public List<AnimationTrack> AnimationTracks { get; private set; } = new();
|
||||
|
||||
public TimeSpan Length { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
{
|
||||
@@ -14,7 +15,7 @@ namespace Robust.Client.Animations
|
||||
/// <summary>
|
||||
/// A list of key frames for when to fire flicks.
|
||||
/// </summary>
|
||||
public readonly List<KeyFrame> KeyFrames = new();
|
||||
public List<KeyFrame> KeyFrames { get; private set; } = new();
|
||||
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
|
||||
{
|
||||
@@ -36,8 +37,7 @@ namespace Robust.Client.Animations
|
||||
|
||||
var keyFrame = KeyFrames[keyFrameIndex];
|
||||
|
||||
EntitySystem.Get<AudioSystem>()
|
||||
.Play(keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
|
||||
SoundSystem.Play(Filter.Local(), keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
|
||||
}
|
||||
|
||||
return (keyFrameIndex, playingTime);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public abstract class AnimationTrackProperty : AnimationTrack
|
||||
{
|
||||
public readonly List<KeyFrame> KeyFrames = new();
|
||||
public List<KeyFrame> KeyFrames { get; protected set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// How to interpolate values when between two keyframes.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Animations
|
||||
/// <summary>
|
||||
/// A list of key frames for when to fire flicks.
|
||||
/// </summary>
|
||||
public readonly List<KeyFrame> KeyFrames = new();
|
||||
public List<KeyFrame> KeyFrames { get; private set; } = new();
|
||||
|
||||
// TODO: Should this layer key be per keyframe maybe?
|
||||
/// <summary>
|
||||
|
||||
@@ -11,8 +11,8 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
@@ -156,6 +158,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
@@ -300,7 +303,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
@@ -317,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
|
||||
|
||||
@@ -24,6 +24,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
@@ -53,6 +54,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IClientGameStateManager, ClientGameStateManager>();
|
||||
IoCManager.Register<IBaseClient, BaseClient>();
|
||||
IoCManager.Register<IPlayerManager, PlayerManager>();
|
||||
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
|
||||
IoCManager.Register<IStateManager, StateManager>();
|
||||
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
|
||||
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
|
||||
@@ -62,8 +64,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
switch (mode)
|
||||
@@ -90,8 +90,9 @@ namespace Robust.Client
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IEyeManager, EyeManager>();
|
||||
|
||||
IoCManager.Register<IPlacementManager, PlacementManager>();
|
||||
IoCManager.Register<IOverlayManager, OverlayManager>();
|
||||
IoCManager.Register<IOverlayManagerInternal, OverlayManager>();
|
||||
@@ -99,7 +100,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
|
||||
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
|
||||
IoCManager.Register<IScriptClient, ScriptClient>();
|
||||
//IoCManager.Register<IXamlCompiler, XamlCompiler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement.ResourceTypes;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
44
Robust.Client/Console/Commands/MonitorCommands.cs
Normal file
44
Robust.Client/Console/Commands/MonitorCommands.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class LsMonitorCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lsmonitor";
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
foreach (var monitor in clyde.EnumerateMonitors())
|
||||
{
|
||||
shell.WriteLine(
|
||||
$"[{monitor.Id}] {monitor.Name}: {monitor.Size.X}x{monitor.Size.Y}@{monitor.RefreshRate}Hz");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SetMonitorCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "setmonitor";
|
||||
public string Description => "";
|
||||
public string Help => "Usage: setmonitor <id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
var id = int.Parse(args[0]);
|
||||
var monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
|
||||
clyde.SetWindowMonitor(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Robust.Client/Console/Commands/PhysicsOverlayCommands.cs
Normal file
41
Robust.Client/Console/Commands/PhysicsOverlayCommands.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class PhysicsOverlayCommands : IConsoleCommand
|
||||
{
|
||||
public string Command => "physics";
|
||||
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
|
||||
public string Help => $"{Command} <overlay>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine($"Invalid number of args supplied");
|
||||
return;
|
||||
}
|
||||
|
||||
var system = EntitySystem.Get<DebugPhysicsSystem>();
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "contactnormals":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactNormals;
|
||||
break;
|
||||
case "contactpoints":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactPoints;
|
||||
break;
|
||||
case "shapes":
|
||||
system.Flags ^= PhysicsDebugFlags.Shapes;
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine($"{args[0]} is not a recognised overlay");
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Robust.Client/Console/Commands/VelocitiesCommand.cs
Normal file
17
Robust.Client/Console/Commands/VelocitiesCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class VelocitiesCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showvelocities";
|
||||
public string Description => "Displays your angular and linear velocities";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<VelocityDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
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;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
@@ -38,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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,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;
|
||||
@@ -136,7 +140,7 @@ namespace Robust.Client.Debugging
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {((IPhysicsComponent)body).Anchored}");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
@@ -157,20 +161,22 @@ namespace Robust.Client.Debugging
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
|
||||
foreach (var physBody in _physicsManager.GetCollidingEntities(mapId, viewport))
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Entity.Transform;
|
||||
|
||||
var worldBox = physBody.WorldAABB;
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
|
||||
foreach (var shape in physBody.PhysicsShapes)
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, physBody.SleepAccumulator / (float) physBody.SleepThreshold);
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
@@ -187,9 +193,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);
|
||||
}
|
||||
}
|
||||
@@ -233,6 +239,16 @@ namespace Robust.Client.Debugging
|
||||
_handle.DrawCircle(origin, radius, color);
|
||||
}
|
||||
|
||||
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
|
||||
{
|
||||
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
|
||||
}
|
||||
|
||||
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
|
||||
{
|
||||
_handle.DrawLine(start, end, color);
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 transform)
|
||||
{
|
||||
_handle.SetTransform(transform);
|
||||
@@ -249,7 +265,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
164
Robust.Client/Debugging/DebugPhysicsSystem.cs
Normal file
164
Robust.Client/Debugging/DebugPhysicsSystem.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Farseer Physics Engine:
|
||||
* Copyright (c) 2012 Ian Qvist
|
||||
*
|
||||
* Original source Box2D:
|
||||
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
/* Heavily inspired by Farseer */
|
||||
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
{
|
||||
/*
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
*/
|
||||
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
|
||||
|
||||
public PhysicsDebugFlags Flags
|
||||
{
|
||||
get => _flags;
|
||||
set
|
||||
{
|
||||
if (value == _flags) return;
|
||||
|
||||
if (_flags == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
|
||||
|
||||
if (value == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
|
||||
|
||||
_flags = value;
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsDebugFlags _flags;
|
||||
|
||||
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
|
||||
{
|
||||
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
{
|
||||
Manifold manifold = contact.Manifold;
|
||||
|
||||
if (manifold.PointCount == 0)
|
||||
return;
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
|
||||
PointState[] state1, state2;
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
Vector2 normal;
|
||||
contact.GetWorldManifold(out normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
if (fixtureA == null)
|
||||
_points[i] = new ContactPoint();
|
||||
|
||||
ContactPoint cp = _points[PointCount];
|
||||
cp.Position = points[i];
|
||||
cp.Normal = normal;
|
||||
cp.State = state2[i];
|
||||
_points[PointCount] = cp;
|
||||
++PointCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ContactPoint
|
||||
{
|
||||
public Vector2 Normal;
|
||||
public Vector2 Position;
|
||||
public PointState State;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum PhysicsDebugFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
ContactPoints = 1 << 0,
|
||||
ContactNormals = 1 << 1,
|
||||
Shapes = 1 << 2,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsDebugOverlay : Overlay
|
||||
{
|
||||
private DebugPhysicsSystem _physics = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public PhysicsDebugOverlay(DebugPhysicsSystem system)
|
||||
{
|
||||
_physics = system;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
if (_physics.Flags == PhysicsDebugFlags.None) return;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
// Port DebugDrawing over.
|
||||
}
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
{
|
||||
const float axisScale = 0.3f;
|
||||
|
||||
for (int i = 0; i < _physics.PointCount; ++i)
|
||||
{
|
||||
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
|
||||
|
||||
if (point.State == PointState.Add)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
|
||||
else if (point.State == PointState.Persist)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
|
||||
{
|
||||
Vector2 p1 = point.Position;
|
||||
Vector2 p2 = p1 + point.Normal * axisScale;
|
||||
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
|
||||
}
|
||||
}
|
||||
|
||||
_physics.PointCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Audio.Midi;
|
||||
@@ -27,6 +28,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -44,7 +46,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
@@ -79,6 +81,74 @@ namespace Robust.Client
|
||||
}
|
||||
|
||||
public bool Startup(Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
return false;
|
||||
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
|
||||
_modLoader.SetEnableSandboxing(true);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
|
||||
{
|
||||
Logger.Fatal("Errors while loading content assemblies.");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var loadedModule in _modLoader.LoadedModules)
|
||||
{
|
||||
_configurationManager.LoadCVarsFromAssembly(loadedModule);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
|
||||
// Call Init in game assemblies.
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
|
||||
|
||||
_resourceCache.PreloadTextures();
|
||||
_userInterfaceManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
_gameStateManager.Initialize();
|
||||
_placementManager.Initialize();
|
||||
_viewVariablesManager.Initialize();
|
||||
_scriptClient.Initialize();
|
||||
|
||||
_client.Initialize();
|
||||
_discord.Initialize();
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
|
||||
|
||||
if (_commandLineArgs?.Username != null)
|
||||
{
|
||||
_client.PlayerNameOverride = _commandLineArgs.Username;
|
||||
}
|
||||
|
||||
_authManager.LoadFromEnv();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
||||
&& LaunchState.ConnectEndpoint != null)
|
||||
{
|
||||
_client.ConnectToServer(LaunchState.ConnectEndpoint);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
ReadInitialLaunchState();
|
||||
|
||||
@@ -118,6 +188,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);
|
||||
@@ -128,6 +200,13 @@ namespace Robust.Client
|
||||
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
|
||||
}
|
||||
|
||||
_clyde.TextEntered += TextEntered;
|
||||
_clyde.MouseMove += MouseMove;
|
||||
_clyde.KeyUp += KeyUp;
|
||||
_clyde.KeyDown += KeyDown;
|
||||
_clyde.MouseWheel += MouseWheel;
|
||||
_clyde.CloseWindow += Shutdown;
|
||||
|
||||
// Bring display up as soon as resources are mounted.
|
||||
if (!_clyde.Initialize())
|
||||
{
|
||||
@@ -136,63 +215,7 @@ namespace Robust.Client
|
||||
|
||||
_clyde.SetWindowTitle("Space Station 14");
|
||||
|
||||
_fontManager.Initialize();
|
||||
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
|
||||
_modLoader.SetEnableSandboxing(true);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
|
||||
{
|
||||
Logger.Fatal("Errors while loading content assemblies.");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var loadedModule in _modLoader.LoadedModules)
|
||||
{
|
||||
_configurationManager.LoadCVarsFromAssembly(loadedModule);
|
||||
}
|
||||
|
||||
// Call Init in game assemblies.
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
|
||||
|
||||
_userInterfaceManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
_gameStateManager.Initialize();
|
||||
_placementManager.Initialize();
|
||||
_viewVariablesManager.Initialize();
|
||||
_scriptClient.Initialize();
|
||||
|
||||
_client.Initialize();
|
||||
_discord.Initialize();
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
|
||||
|
||||
if (_commandLineArgs?.Username != null)
|
||||
{
|
||||
_client.PlayerNameOverride = _commandLineArgs.Username;
|
||||
}
|
||||
|
||||
_authManager.LoadFromEnv();
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
||||
&& LaunchState.ConnectEndpoint != null)
|
||||
{
|
||||
_client.ConnectToServer(LaunchState.ConnectEndpoint);
|
||||
}
|
||||
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -269,17 +292,13 @@ namespace Robust.Client
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
_taskManager.ProcessPendingTasks();
|
||||
_userInterfaceManager.Update(frameEventArgs);
|
||||
|
||||
// GameStateManager is in full control of the simulation update.
|
||||
if (_client.RunLevel >= ClientRunLevel.Connected)
|
||||
{
|
||||
_componentManager.CullRemovedComponents();
|
||||
_gameStateManager.ApplyGameState();
|
||||
_entityManager.Update(frameEventArgs.DeltaSeconds);
|
||||
_playerManager.Update(frameEventArgs.DeltaSeconds);
|
||||
}
|
||||
|
||||
_stateManager.Update(frameEventArgs);
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
@@ -320,7 +339,7 @@ namespace Robust.Client
|
||||
logManager.GetSawmill("discord").Level = LogLevel.Warning;
|
||||
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("szr").Level = LogLevel.Info;
|
||||
// logManager.GetSawmill("loc").Level = LogLevel.Error;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Error;
|
||||
|
||||
#if DEBUG_ONLY_FCE_INFO
|
||||
#if DEBUG_ONLY_FCE_LOG
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Robust.Client
|
||||
RegisterReflection();
|
||||
}
|
||||
|
||||
|
||||
internal static void RegisterReflection()
|
||||
{
|
||||
// Gets a handle to the shared and the current (client) dll.
|
||||
|
||||
@@ -24,7 +24,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Register<PhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysBody>();
|
||||
RegisterReference<PhysicsComponent, IPhysicsComponent>();
|
||||
|
||||
Register<CollisionWakeComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
|
||||
Register<InputComponent>();
|
||||
@@ -54,7 +59,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionExposeDataComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
@@ -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++);
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.Client.GameObjects
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void TickUpdate()
|
||||
{
|
||||
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -15,13 +14,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ViewVariables]
|
||||
private Dictionary<object, object> data = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("visuals")]
|
||||
internal List<AppearanceVisualizer> Visualizers = new();
|
||||
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
private static bool _didRegisterSerializer;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _appearanceDirty;
|
||||
|
||||
@@ -107,18 +104,6 @@ namespace Robust.Client.GameObjects
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
if (!_didRegisterSerializer)
|
||||
{
|
||||
YamlObjectSerializer.RegisterTypeSerializer(typeof(AppearanceVisualizer),
|
||||
new VisualizerTypeSerializer(_reflectionManager));
|
||||
_didRegisterSerializer = true;
|
||||
}
|
||||
|
||||
serializer.DataFieldCached(ref Visualizers, "visuals", new List<AppearanceVisualizer>());
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -131,78 +116,6 @@ namespace Robust.Client.GameObjects
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
class VisualizerTypeSerializer : YamlObjectSerializer.TypeSerializer
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
public VisualizerTypeSerializer(IReflectionManager reflectionManager)
|
||||
{
|
||||
_reflectionManager = reflectionManager;
|
||||
}
|
||||
|
||||
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
|
||||
{
|
||||
var mapping = (YamlMappingNode) node;
|
||||
var nodeType = mapping.GetNode("type");
|
||||
switch (nodeType.AsString())
|
||||
{
|
||||
case SpriteLayerToggle.NAME:
|
||||
var keyString = mapping.GetNode("key").AsString();
|
||||
object key;
|
||||
if (_reflectionManager.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
var layer = mapping.GetNode("layer").AsInt();
|
||||
return new SpriteLayerToggle(key, layer);
|
||||
|
||||
default:
|
||||
var visType = _reflectionManager.LooseGetType(nodeType.AsString());
|
||||
if (!typeof(AppearanceVisualizer).IsAssignableFrom(visType))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var vis = (AppearanceVisualizer) Activator.CreateInstance(visType)!;
|
||||
vis.LoadData(mapping);
|
||||
return vis;
|
||||
}
|
||||
}
|
||||
|
||||
public override YamlNode TypeToNode(object obj, YamlObjectSerializer serializer)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case SpriteLayerToggle spriteLayerToggle:
|
||||
YamlScalarNode key;
|
||||
if (spriteLayerToggle.Key is Enum)
|
||||
{
|
||||
var name = spriteLayerToggle.Key.GetType().FullName;
|
||||
key = new YamlScalarNode($"{name}.{spriteLayerToggle.Key}");
|
||||
}
|
||||
else
|
||||
{
|
||||
key = new YamlScalarNode(spriteLayerToggle.Key.ToString());
|
||||
}
|
||||
|
||||
return new YamlMappingNode
|
||||
{
|
||||
{new YamlScalarNode("type"), new YamlScalarNode(SpriteLayerToggle.NAME)},
|
||||
{new YamlScalarNode("key"), key},
|
||||
{new YamlScalarNode("layer"), new YamlScalarNode(spriteLayerToggle.SpriteLayer.ToString())},
|
||||
};
|
||||
default:
|
||||
// TODO: A proper way to do serialization here.
|
||||
// I can't use the ExposeData system here since that's specific to entity serializers.
|
||||
return new YamlMappingNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class SpriteLayerToggle : AppearanceVisualizer
|
||||
{
|
||||
@@ -223,15 +136,9 @@ namespace Robust.Client.GameObjects
|
||||
/// Handles the visualization of data inside of an appearance component.
|
||||
/// Implementations of this class are NOT bound to a specific entity, they are flyweighted across multiple.
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public abstract class AppearanceVisualizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Load data from the prototype declaring this visualizer, to configure settings and such.
|
||||
/// </summary>
|
||||
public virtual void LoadData(YamlMappingNode node)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an entity to be managed by this appearance controller.
|
||||
/// DO NOT assume this is your only entity. Visualizers are shared.
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -20,7 +22,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Horrible hack to get around ordering issues.
|
||||
private bool _setCurrentOnInitialize;
|
||||
private bool _setDrawFovOnInitialize;
|
||||
[DataField("drawFov")]
|
||||
private bool _setDrawFovOnInitialize = true;
|
||||
[DataField("zoom")]
|
||||
private Vector2 _setZoomOnInitialize = Vector2.One/2f;
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
@@ -148,6 +152,7 @@ namespace Robust.Client.GameObjects
|
||||
Zoom = state.Zoom;
|
||||
Offset = state.Offset;
|
||||
Rotation = state.Rotation;
|
||||
VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
@@ -157,15 +162,6 @@ namespace Robust.Client.GameObjects
|
||||
Current = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataFieldCached(ref _setZoomOnInitialize, "zoom", Vector2.One/2f);
|
||||
serializer.DataFieldCached(ref _setDrawFovOnInitialize, "drawFov", true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
|
||||
/// keep the view following the entity.
|
||||
|
||||
@@ -1,105 +1,51 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class IconComponent : Component
|
||||
public class IconComponent : Component, ISerializationHooks
|
||||
{
|
||||
public override string Name => "Icon";
|
||||
public IDirectionalTextureProvider? Icon { get; private set; }
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[DataField("sprite")]
|
||||
private ResourcePath? rsi;
|
||||
[DataField("state")]
|
||||
private string? stateID;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (rsi != null && stateID != null)
|
||||
{
|
||||
Icon = new SpriteSpecifier.Rsi(rsi, stateID).Frame0();
|
||||
}
|
||||
}
|
||||
|
||||
public const string LogCategory = "go.comp.icon";
|
||||
const string SerializationCache = "icon";
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
// TODO: Does this need writing?
|
||||
if (serializer.Reading)
|
||||
{
|
||||
Icon = TextureForConfig(serializer, _resourceCache);
|
||||
}
|
||||
}
|
||||
|
||||
private static IRsiStateLike TextureForConfig(ObjectSerializer serializer, IResourceCache resourceCache)
|
||||
{
|
||||
DebugTools.Assert(serializer.Reading);
|
||||
|
||||
if (serializer.TryGetCacheData<IRsiStateLike>(SerializationCache, out var dirTex))
|
||||
{
|
||||
return dirTex;
|
||||
}
|
||||
|
||||
var tex = serializer.ReadDataField<string?>("texture", null);
|
||||
if (!string.IsNullOrWhiteSpace(tex))
|
||||
{
|
||||
dirTex = resourceCache.GetResource<TextureResource>(SpriteComponent.TextureRoot / tex).Texture;
|
||||
serializer.SetCacheData(SerializationCache, dirTex);
|
||||
return dirTex;
|
||||
}
|
||||
|
||||
RSI rsi;
|
||||
|
||||
var rsiPath = serializer.ReadDataField<string?>("sprite", null);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rsiPath))
|
||||
{
|
||||
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
|
||||
serializer.SetCacheData(SerializationCache, dirTex);
|
||||
return dirTex;
|
||||
}
|
||||
|
||||
var path = SpriteComponent.TextureRoot / rsiPath;
|
||||
|
||||
try
|
||||
{
|
||||
rsi = resourceCache.GetResource<RSIResource>(path).RSI;
|
||||
}
|
||||
catch
|
||||
{
|
||||
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
|
||||
serializer.SetCacheData(SerializationCache, dirTex);
|
||||
return dirTex;
|
||||
}
|
||||
|
||||
var stateId = serializer.ReadDataField<string?>("state", null);
|
||||
if (string.IsNullOrWhiteSpace(stateId))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "No state specified.");
|
||||
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
|
||||
serializer.SetCacheData(SerializationCache, dirTex);
|
||||
return dirTex;
|
||||
}
|
||||
|
||||
if (rsi.TryGetState(stateId, out var state))
|
||||
{
|
||||
serializer.SetCacheData(SerializationCache, state);
|
||||
return state;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "State '{0}' does not exist on RSI.", stateId);
|
||||
return resourceCache.GetFallback<TextureResource>().Texture;
|
||||
}
|
||||
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
|
||||
}
|
||||
|
||||
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
if (!prototype.Components.TryGetValue("Icon", out var mapping))
|
||||
if (!prototype.Components.TryGetValue("Icon", out var compData))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return TextureForConfig(YamlObjectSerializer.NewReader(mapping), resourceCache);
|
||||
|
||||
return TextureForConfig((IconComponent)compData, resourceCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -17,14 +19,7 @@ namespace Robust.Client.GameObjects
|
||||
/// The context that will be made active for a client that attaches to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string ContextName { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction("context", InputContextContainer.DefaultContextName, value => ContextName = value, () => ContextName);
|
||||
}
|
||||
[DataField("context")]
|
||||
public string ContextName { get; set; } = InputContextContainer.DefaultContextName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Animations;
|
||||
@@ -7,13 +7,14 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
public class PointLightComponent : Component, IPointLightComponent
|
||||
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
@@ -134,16 +135,25 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private float _radius = 5;
|
||||
[DataField("radius")]
|
||||
private float _radius = 5f;
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
private bool _lightOnParent;
|
||||
[DataField("color")]
|
||||
private Color _color = Color.White;
|
||||
private Vector2 _offset;
|
||||
[DataField("offset")]
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
[DataField("enabled")]
|
||||
private bool _enabled = true;
|
||||
[DataField("autoRot")]
|
||||
private bool _maskAutoRotate;
|
||||
private Angle _rotation;
|
||||
private float _energy;
|
||||
private float _softness;
|
||||
[DataField("energy")]
|
||||
private float _energy = 1f;
|
||||
[DataField("softness")]
|
||||
private float _softness = 1f;
|
||||
[DataField("mask")]
|
||||
private string? _maskPath;
|
||||
|
||||
/// <summary>
|
||||
@@ -169,6 +179,14 @@ namespace Robust.Client.GameObjects
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (_maskPath != null)
|
||||
{
|
||||
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(_maskPath);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -180,7 +198,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if ((message is ParentChangedMessage msg))
|
||||
if (message is ParentChangedMessage msg)
|
||||
{
|
||||
HandleTransformParentChanged(msg);
|
||||
}
|
||||
@@ -204,19 +222,6 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataFieldCached(ref _offset, "offset", Vector2.Zero);
|
||||
serializer.DataFieldCached(ref _radius, "radius", 5f);
|
||||
serializer.DataFieldCached(ref _color, "color", Color.White);
|
||||
serializer.DataFieldCached(ref _enabled, "enabled", true);
|
||||
serializer.DataFieldCached(ref _energy, "energy", 1f);
|
||||
serializer.DataFieldCached(ref _softness, "softness", 1f);
|
||||
serializer.DataFieldCached(ref _maskAutoRotate, "autoRot", false);
|
||||
serializer.DataFieldCached(ref _visibleNested, "nestedvisible", true);
|
||||
serializer.DataFieldCached(ref _maskPath, "mask", null);
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
@@ -17,6 +17,9 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
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;
|
||||
@@ -25,17 +28,22 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
|
||||
IComponentDebug
|
||||
IComponentDebug, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
|
||||
[DataField("visible")]
|
||||
private bool _visible = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Visible
|
||||
public override bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set => _visible = value;
|
||||
}
|
||||
|
||||
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
||||
private int drawDepth = DrawDepthTag.Default;
|
||||
|
||||
/// <summary>
|
||||
@@ -48,6 +56,7 @@ namespace Robust.Client.GameObjects
|
||||
set => drawDepth = value;
|
||||
}
|
||||
|
||||
[DataField("scale")]
|
||||
private Vector2 scale = Vector2.One;
|
||||
|
||||
/// <summary>
|
||||
@@ -61,7 +70,8 @@ namespace Robust.Client.GameObjects
|
||||
set => scale = value;
|
||||
}
|
||||
|
||||
private Angle rotation;
|
||||
[DataField("rotation")]
|
||||
private Angle rotation = Angle.Zero;
|
||||
|
||||
[Animatable]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -71,6 +81,7 @@ namespace Robust.Client.GameObjects
|
||||
set => rotation = value;
|
||||
}
|
||||
|
||||
[DataField("offset")]
|
||||
private Vector2 offset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
@@ -84,6 +95,7 @@ namespace Robust.Client.GameObjects
|
||||
set => offset = value;
|
||||
}
|
||||
|
||||
[DataField("color")]
|
||||
private Color color = Color.White;
|
||||
|
||||
[Animatable]
|
||||
@@ -108,18 +120,152 @@ namespace Robust.Client.GameObjects
|
||||
set => _directional = value;
|
||||
}
|
||||
|
||||
[DataField("directional")]
|
||||
private bool _directional = true;
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
{
|
||||
get
|
||||
{
|
||||
var layerDatums = new List<PrototypeLayerData>();
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
layerDatums.Add(layer.ToPrototypeData());
|
||||
}
|
||||
|
||||
return layerDatums;
|
||||
}
|
||||
set
|
||||
{
|
||||
if(value == null) return;
|
||||
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
{
|
||||
var anyTextureAttempted = false;
|
||||
var layer = new Layer(this);
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
try
|
||||
{
|
||||
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
var theRsi = layer.RSI ?? BaseRSI;
|
||||
if (theRsi == null)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
|
||||
layerDatum.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateid = new RSI.StateId(layerDatum.State);
|
||||
layer.State = stateid;
|
||||
if (theRsi.TryGetState(stateid, out var state))
|
||||
{
|
||||
// Always use south because this layer will be cached in the serializer.
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
||||
stateid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.Texture =
|
||||
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
|
||||
{
|
||||
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.Shader = prototype.Instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Shader prototype '{0}' does not exist.",
|
||||
layerDatum.Shader);
|
||||
}
|
||||
}
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer.Rotation = layerDatum.Rotation;
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
layer.Scale = layerDatum.Scale;
|
||||
|
||||
Layers.Add(layer);
|
||||
|
||||
if (layerDatum.MapKeys != null)
|
||||
{
|
||||
var index = Layers.Count - 1;
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (LayerMap.ContainsKey(key))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
LayerMap.Add(key, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
UpdateIsInert();
|
||||
}
|
||||
}
|
||||
|
||||
private RSI? _baseRsi;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("rsi", priority: 2)]
|
||||
public RSI? BaseRSI
|
||||
{
|
||||
get => _baseRsi;
|
||||
set
|
||||
{
|
||||
_baseRsi = value;
|
||||
if (Layers == null || value == null)
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -147,6 +293,12 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("sprite", readOnly: true)] private string? rsi;
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
|
||||
|
||||
[DataField("state", readOnly: true)] private string? state;
|
||||
[DataField("texture", readOnly: true)] private string? texture;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded { get; set; }
|
||||
|
||||
@@ -158,11 +310,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
[ViewVariables] private Dictionary<object, int> LayerMap = new();
|
||||
[ViewVariables] private bool _layerMapShared;
|
||||
[ViewVariables] private List<Layer> Layers = default!;
|
||||
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
[Dependency] private readonly IReflectionManager reflectionManager = default!;
|
||||
[ViewVariables] private List<Layer> Layers = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; }
|
||||
|
||||
@@ -170,10 +318,9 @@ namespace Robust.Client.GameObjects
|
||||
private static ShaderInstance? _defaultShader;
|
||||
|
||||
[ViewVariables]
|
||||
private ShaderInstance? DefaultShader => _defaultShader ??
|
||||
(_defaultShader = prototypes
|
||||
.Index<ShaderPrototype>("shaded")
|
||||
.Instance());
|
||||
private ShaderInstance? DefaultShader => _defaultShader ??= prototypes
|
||||
.Index<ShaderPrototype>("shaded")
|
||||
.Instance();
|
||||
|
||||
public const string LogCategory = "go.comp.sprite";
|
||||
const string LayerSerializationCache = "spritelayer";
|
||||
@@ -181,6 +328,46 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; private set; }
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
try
|
||||
{
|
||||
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (layerDatums.Count == 0)
|
||||
{
|
||||
if (state != null || texture != null)
|
||||
{
|
||||
layerDatums.Insert(0, new PrototypeLayerData
|
||||
{
|
||||
TexturePath = string.IsNullOrWhiteSpace(texture) ? null : texture,
|
||||
State = string.IsNullOrWhiteSpace(state) ? null : state,
|
||||
Color = Color.White,
|
||||
Scale = Vector2.One,
|
||||
Visible = true,
|
||||
});
|
||||
state = null;
|
||||
texture = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (layerDatums.Count != 0)
|
||||
{
|
||||
LayerDatums = layerDatums;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update this sprite component to visibly match the current state of other at the time
|
||||
/// this is called. Does not keep them perpetually in sync.
|
||||
@@ -1005,9 +1192,14 @@ namespace Robust.Client.GameObjects
|
||||
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
|
||||
}
|
||||
|
||||
private bool _screenLock = false;
|
||||
private Direction _overrideDirection = Direction.South;
|
||||
private bool _enableOverrideDirection = false;
|
||||
[DataField("noRot")]
|
||||
private bool _screenLock = true;
|
||||
|
||||
[DataField("overrideDir")]
|
||||
private Direction _overrideDirection = Direction.East;
|
||||
|
||||
[DataField("enableOverrideDir")]
|
||||
private bool _enableOverrideDirection;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -1153,205 +1345,6 @@ namespace Robust.Client.GameObjects
|
||||
return texture;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataFieldCached(ref scale, "scale", Vector2.One);
|
||||
serializer.DataFieldCached(ref rotation, "rotation", Angle.Zero);
|
||||
serializer.DataFieldCached(ref offset, "offset", Vector2.Zero);
|
||||
serializer.DataFieldCached(ref drawDepth, "drawdepth", DrawDepthTag.Default,
|
||||
WithFormat.Constants<DrawDepthTag>());
|
||||
serializer.DataFieldCached(ref color, "color", Color.White);
|
||||
serializer.DataFieldCached(ref _visible, "visible", true);
|
||||
serializer.DataFieldCached(ref _directional, "directional", true); //TODO: Kill ME
|
||||
serializer.DataFieldCached(ref _screenLock, "noRot", true);
|
||||
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
|
||||
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
|
||||
|
||||
// TODO: Writing?
|
||||
if (!serializer.Reading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
var rsi = serializer.ReadDataField<string?>("sprite", null);
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
try
|
||||
{
|
||||
BaseRSI = resourceCache.GetResource<RSIResource>(rsiPath).RSI;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Layer> CloneLayers(List<Layer> source)
|
||||
{
|
||||
var clone = new List<Layer>(source.Count);
|
||||
foreach (var layer in source)
|
||||
{
|
||||
clone.Add(new Layer(layer, this));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
if (serializer.TryGetCacheData<List<Layer>>(LayerSerializationCache, out var layers))
|
||||
{
|
||||
LayerMap = serializer.GetCacheData<Dictionary<object, int>>(LayerMapSerializationCache);
|
||||
_layerMapShared = true;
|
||||
Layers = CloneLayers(layers);
|
||||
UpdateIsInert();
|
||||
return;
|
||||
}
|
||||
|
||||
layers = new List<Layer>();
|
||||
|
||||
var layerMap = new Dictionary<object, int>();
|
||||
|
||||
var layerData =
|
||||
serializer.ReadDataField("layers", new List<PrototypeLayerData>());
|
||||
|
||||
if(layerData.Count == 0){
|
||||
var baseState = serializer.ReadDataField<string?>("state", null);
|
||||
var texturePath = serializer.ReadDataField<string?>("texture", null);
|
||||
|
||||
if (baseState != null || texturePath != null)
|
||||
{
|
||||
layerData.Insert(0, new PrototypeLayerData
|
||||
{
|
||||
TexturePath = string.IsNullOrWhiteSpace(texturePath) ? null : texturePath,
|
||||
State = string.IsNullOrWhiteSpace(baseState) ? null : baseState,
|
||||
Color = Color.White,
|
||||
Scale = Vector2.One,
|
||||
Visible = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var layerDatum in layerData)
|
||||
{
|
||||
var anyTextureAttempted = false;
|
||||
var layer = new Layer(this);
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
try
|
||||
{
|
||||
layer.RSI = resourceCache.GetResource<RSIResource>(path).RSI;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
var theRsi = layer.RSI ?? BaseRSI;
|
||||
if (theRsi == null)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer has no RSI to load states from."
|
||||
+ "cannot use 'state' property. Prototype: '{0}'", Owner.Prototype?.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateid = new RSI.StateId(layerDatum.State);
|
||||
layer.State = stateid;
|
||||
if (theRsi.TryGetState(stateid, out var state))
|
||||
{
|
||||
// Always use south because this layer will be cached in the serializer.
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
||||
stateid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.Texture =
|
||||
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
|
||||
{
|
||||
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.Shader = prototype.Instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Shader prototype '{0}' does not exist. Prototype: '{1}'",
|
||||
layerDatum.Shader, Owner.Prototype?.ID);
|
||||
}
|
||||
}
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer.Rotation = layerDatum.Rotation;
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
layer.Scale = layerDatum.Scale;
|
||||
|
||||
layers.Add(layer);
|
||||
|
||||
if (layerDatum.MapKeys != null)
|
||||
{
|
||||
var index = layers.Count - 1;
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (reflectionManager.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (layerMap.ContainsKey(key))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
layerMap.Add(key, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Layers = layers;
|
||||
LayerMap = layerMap;
|
||||
_layerMapShared = true;
|
||||
serializer.SetCacheData(LayerSerializationCache, CloneLayers(Layers));
|
||||
serializer.SetCacheData(LayerMapSerializationCache, layerMap);
|
||||
UpdateIsInert();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
@@ -1663,7 +1656,7 @@ namespace Robust.Client.GameObjects
|
||||
Flip = 3,
|
||||
}
|
||||
|
||||
private class Layer : ISpriteLayer
|
||||
public class Layer : ISpriteLayer
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
@@ -1730,6 +1723,22 @@ namespace Robust.Client.GameObjects
|
||||
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
|
||||
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
|
||||
|
||||
public PrototypeLayerData ToPrototypeData()
|
||||
{
|
||||
return new PrototypeLayerData
|
||||
{
|
||||
Color = Color,
|
||||
Rotation = Rotation,
|
||||
Scale = Scale,
|
||||
//todo Shader = Shader,
|
||||
State = State.Name,
|
||||
Visible = Visible,
|
||||
RsiPath = RSI?.Path?.ToString(),
|
||||
//todo TexturePath = Textur
|
||||
//todo MapKeys
|
||||
};
|
||||
}
|
||||
|
||||
bool ISpriteLayer.Visible
|
||||
{
|
||||
get => Visible;
|
||||
@@ -2000,7 +2009,6 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2051,7 +2059,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (!anyTexture)
|
||||
yield return resourceCache.GetFallback<TextureResource>().Texture;
|
||||
|
||||
}
|
||||
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
@@ -2078,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;
|
||||
@@ -2094,10 +2102,12 @@ 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()
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
_components[typeof(T)] = comp;
|
||||
comp.Owner = this;
|
||||
@@ -2107,9 +2117,9 @@ namespace Robust.Client.GameObjects
|
||||
_components[typeof(ISpriteComponent)] = comp;
|
||||
}
|
||||
|
||||
if (Prototype != null && Prototype.Components.TryGetValue(comp.Name, out var node))
|
||||
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
|
||||
{
|
||||
comp.ExposeData(YamlObjectSerializer.NewReader(node));
|
||||
comp = serializationManager.Copy(node, comp)!;
|
||||
}
|
||||
|
||||
return comp;
|
||||
@@ -2140,11 +2150,6 @@ namespace Robust.Client.GameObjects
|
||||
return null!;
|
||||
}
|
||||
|
||||
public IComponent GetComponent(uint netID)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component) where T : class
|
||||
{
|
||||
component = null;
|
||||
@@ -2171,21 +2176,6 @@ namespace Robust.Client.GameObjects
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetComponent(uint netId, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IComponent? GetComponentOrNull(uint netId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6,41 +6,31 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent
|
||||
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
|
||||
private readonly Dictionary<object, BoundUserInterface> _openInterfaces =
|
||||
new();
|
||||
|
||||
private Dictionary<object, PrototypeData> _interfaceData = default!;
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
#pragma warning restore 649
|
||||
private readonly Dictionary<object, PrototypeData> _interfaces = new();
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
[DataField("interfaces", readOnly: true)]
|
||||
private List<PrototypeData> _interfaceData = new();
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
_interfaces.Clear();
|
||||
|
||||
const string cache = "ui_cache";
|
||||
|
||||
if (serializer.TryGetCacheData<Dictionary<object, PrototypeData>>(cache, out var interfaceData))
|
||||
foreach (var data in _interfaceData)
|
||||
{
|
||||
_interfaceData = interfaceData;
|
||||
return;
|
||||
_interfaces[data.UiKey] = data;
|
||||
}
|
||||
|
||||
var data = serializer.ReadDataFieldCached("interfaces", new List<PrototypeData>());
|
||||
interfaceData = new Dictionary<object, PrototypeData>();
|
||||
foreach (var prototypeData in data)
|
||||
{
|
||||
interfaceData[prototypeData.UiKey] = prototypeData;
|
||||
}
|
||||
|
||||
serializer.SetCacheData(cache, interfaceData);
|
||||
_interfaceData = interfaceData;
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
|
||||
@@ -81,7 +71,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
|
||||
{
|
||||
var data = _interfaceData[wrapped.UiKey];
|
||||
var data = _interfaces[wrapped.UiKey];
|
||||
// TODO: This type should be cached, but I'm too lazy.
|
||||
var type = _reflectionManager.LooseGetType(data.ClientType);
|
||||
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
@@ -9,14 +8,15 @@ 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;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AudioSystem : EntitySystem
|
||||
public class AudioSystem : EntitySystem, IAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
@@ -24,17 +24,21 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
public int OcclusionCollisionMask;
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(PlayAudioEntityHandler);
|
||||
SubscribeNetworkEvent<PlayAudioGlobalMessage>(PlayAudioGlobalHandler);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
@@ -141,7 +145,7 @@ namespace Robust.Client.GameObjects
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
@@ -160,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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +186,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
stream.Source.Dispose();
|
||||
stream.Done = true;
|
||||
stream.DoPlaybackDone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -184,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))
|
||||
{
|
||||
@@ -200,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);
|
||||
@@ -222,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))
|
||||
{
|
||||
@@ -239,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))
|
||||
@@ -268,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))
|
||||
{
|
||||
@@ -285,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);
|
||||
@@ -335,92 +344,30 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source.StopPlaying();
|
||||
}
|
||||
|
||||
public event Action? PlaybackDone;
|
||||
|
||||
public void DoPlaybackDone()
|
||||
{
|
||||
PlaybackDone?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPlayingAudioStream
|
||||
{
|
||||
void Stop();
|
||||
|
||||
event Action PlaybackDone;
|
||||
}
|
||||
|
||||
public static class AudioSystemExtensions
|
||||
{
|
||||
|
||||
/// <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="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this IEntity entity,
|
||||
string filename,
|
||||
AudioParams? audioParams,
|
||||
AudioSystem? audioSystem = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream following an entity.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this IEntity entity,
|
||||
AudioStream stream,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
/// <inheritdoc />
|
||||
public int DefaultSoundRange => 25;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(stream, entity, audioParams);
|
||||
return Play(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <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="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this EntityCoordinates coordinates,
|
||||
string filename,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(filename, coordinates, audioParams);
|
||||
return Play(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream at a static position.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this EntityCoordinates coordinates,
|
||||
AudioStream stream,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(stream, coordinates, audioParams);
|
||||
return Play(filename, coordinates, audioParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class VelocityDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
internal bool Enabled { get; set; }
|
||||
|
||||
private Label _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_label = new Label();
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (player == null || !player.TryGetComponent(out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(player.Transform.WorldPosition);
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular:{body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
@@ -242,65 +242,67 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (!Predicting) return;
|
||||
|
||||
using var _ = _timing.StartPastPredictionArea();
|
||||
|
||||
|
||||
if (_pendingInputs.Count > 0)
|
||||
using(var _ = _timing.StartPastPredictionArea())
|
||||
{
|
||||
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
|
||||
if (_pendingInputs.Count > 0)
|
||||
{
|
||||
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
|
||||
}
|
||||
|
||||
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
|
||||
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
|
||||
{
|
||||
var tick = new GameTick(t);
|
||||
_timing.CurTick = tick;
|
||||
|
||||
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
|
||||
{
|
||||
var inputCmd = pendingInputEnumerator.Current;
|
||||
|
||||
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name,
|
||||
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
|
||||
$"state={inputCmd.State}");
|
||||
|
||||
|
||||
input.PredictInputCommand(inputCmd);
|
||||
|
||||
hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
if (t != targetTick)
|
||||
{
|
||||
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
|
||||
// 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.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
|
||||
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
|
||||
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
|
||||
{
|
||||
var tick = new GameTick(t);
|
||||
_timing.CurTick = tick;
|
||||
|
||||
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
|
||||
{
|
||||
var inputCmd = pendingInputEnumerator.Current;
|
||||
|
||||
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name,
|
||||
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
|
||||
$"state={inputCmd.State}");
|
||||
|
||||
|
||||
input.PredictInputCommand(inputCmd);
|
||||
|
||||
hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
if (t != targetTick)
|
||||
{
|
||||
// Don't run EntitySystemManager.Update if this is the target tick,
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
|
||||
}
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
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;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
@@ -27,13 +30,13 @@ namespace Robust.Client.GameStates
|
||||
private const int TrafficHistorySize = 64; // Size of the traffic history bar in game ticks.
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace | OverlaySpace.WorldSpace;
|
||||
|
||||
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 +45,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 +77,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;
|
||||
@@ -94,15 +97,15 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize*2, pvsSize*2));
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange*2, pvsRange*2));
|
||||
|
||||
int timeout = _gameTiming.TickRate * 3;
|
||||
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,22 +126,58 @@ namespace Robust.Client.GameStates
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
if (!_netManager.IsConnected)
|
||||
return;
|
||||
|
||||
switch (currentSpace)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(handle);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleBase handle)
|
||||
{
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
|
||||
if(!pvsEnabled)
|
||||
return;
|
||||
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
|
||||
|
||||
var worldHandle = (DrawingHandleWorld)handle;
|
||||
|
||||
worldHandle.DrawRect(pvsBox, Color.Red, false);
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleBase handle)
|
||||
{
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
var screenHandle = (DrawingHandleScreen)handle;
|
||||
|
||||
var screenHandle = (DrawingHandleScreen) handle;
|
||||
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
if (!_entityManager.TryGetEntity(netEnt.Id, out var ent))
|
||||
{
|
||||
_netEnts.RemoveSwap(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
var xPos = 100;
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {_entityManager.GetEntity(netEnt.Id).Prototype?.ID}";
|
||||
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
@@ -179,20 +218,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);
|
||||
}
|
||||
}
|
||||
@@ -225,7 +263,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
||||
shell.WriteError("Invalid argument amount. Expected 1 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -238,14 +276,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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
@@ -34,7 +38,11 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
|
||||
|
||||
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
|
||||
private int _totalHistoryPayload; // sum of all data point sizes in bytes
|
||||
|
||||
public EntityUid WatchEntId { get; set; }
|
||||
|
||||
public NetGraphOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
@@ -58,7 +66,73 @@ namespace Robust.Client.GameStates
|
||||
// calc interp info
|
||||
var interpBuff = _gameStateManager.CurrentBufferSize - _gameStateManager.MinBufferSize;
|
||||
|
||||
_totalHistoryPayload += sz;
|
||||
_history.Add((toSeq, sz, lag, interpBuff));
|
||||
|
||||
// not watching an ent
|
||||
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
|
||||
return;
|
||||
|
||||
string? entStateString = null;
|
||||
string? entDelString = null;
|
||||
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
|
||||
|
||||
var entStates = args.AppliedState.EntityStates;
|
||||
if (entStates is not null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var entState in entStates)
|
||||
{
|
||||
if (entState.Uid == WatchEntId)
|
||||
{
|
||||
if(entState.ComponentChanges is not null)
|
||||
{
|
||||
sb.Append($"\n Changes:");
|
||||
foreach (var compChange in entState.ComponentChanges)
|
||||
{
|
||||
var del = compChange.Deleted ? 'D' : 'C';
|
||||
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
|
||||
}
|
||||
}
|
||||
|
||||
if (entState.ComponentStates is not null)
|
||||
{
|
||||
sb.Append($"\n States:");
|
||||
foreach (var compState in entState.ComponentStates)
|
||||
{
|
||||
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entStateString = sb.ToString();
|
||||
}
|
||||
|
||||
var entDeletes = args.AppliedState.EntityDeletions;
|
||||
if (entDeletes is not null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var entDelete in entDeletes)
|
||||
{
|
||||
if (entDelete == WatchEntId)
|
||||
{
|
||||
entDelString = "\n Deleted";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entStateString) || !string.IsNullOrWhiteSpace(entDelString))
|
||||
{
|
||||
var fullString = $"watchEnt: from={args.AppliedState.FromSequence}, to={args.AppliedState.ToSequence}, eid={WatchEntId}";
|
||||
if (!string.IsNullOrWhiteSpace(entStateString))
|
||||
fullString += entStateString;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entDelString))
|
||||
fullString += entDelString;
|
||||
|
||||
conShell.WriteLine(fullString + "\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -67,10 +141,16 @@ namespace Robust.Client.GameStates
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var over = _history.Count - HistorySize;
|
||||
if (over > 0)
|
||||
if (over <= 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < over; i++)
|
||||
{
|
||||
_history.RemoveRange(0, over);
|
||||
var point = _history[i];
|
||||
_totalHistoryPayload -= point.Payload;
|
||||
}
|
||||
|
||||
_history.RemoveRange(0, over);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
@@ -80,6 +160,7 @@ namespace Robust.Client.GameStates
|
||||
var leftMargin = 300;
|
||||
var width = HistorySize;
|
||||
var height = 500;
|
||||
var drawSizeThreshold = Math.Min(_totalHistoryPayload / HistorySize, 300);
|
||||
|
||||
// bottom payload line
|
||||
handle.DrawLine(new Vector2(leftMargin, height), new Vector2(leftMargin + width, height), Color.DarkGray.WithAlpha(0.8f));
|
||||
@@ -99,6 +180,12 @@ namespace Robust.Client.GameStates
|
||||
var yoff = height - state.Payload / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(xOff, height), new Vector2(xOff, yoff), Color.LightGreen.WithAlpha(0.8f));
|
||||
|
||||
// Draw size if above average
|
||||
if (drawSizeThreshold * 1.5 < state.Payload)
|
||||
{
|
||||
DrawString((DrawingHandleScreen) handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
}
|
||||
|
||||
// second tick marks
|
||||
if (state.Tick.Value % _gameTiming.TickRate == 0)
|
||||
{
|
||||
@@ -123,6 +210,10 @@ namespace Robust.Client.GameStates
|
||||
handle.DrawLine(new Vector2(xOff, height + LowerGraphOffset), new Vector2(xOff, height + LowerGraphOffset + state.interp * 6), interpColor.WithAlpha(0.8f));
|
||||
}
|
||||
|
||||
// average payload line
|
||||
var avgyoff = height - drawSizeThreshold / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(leftMargin, avgyoff), new Vector2(leftMargin + width, avgyoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// top payload warning line
|
||||
var warnYoff = height - _warningPayloadSize / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(leftMargin, warnYoff), new Vector2(leftMargin + width, warnYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
@@ -142,20 +233,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,17 +274,48 @@ 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NetWatchEntCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_watchent";
|
||||
public string Help => "net_watchent <0|EntityUid>";
|
||||
public string Description => "Dumps all network updates for an EntityId to the console.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Invalid argument amount. Expected 1 argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityUid.TryParse(args[0], out var eValue))
|
||||
{
|
||||
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
|
||||
return;
|
||||
}
|
||||
|
||||
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (overlayMan.HasOverlay(typeof(NetGraphOverlay)))
|
||||
{
|
||||
var netOverlay = overlayMan.GetOverlay<NetGraphOverlay>();
|
||||
|
||||
netOverlay.WatchEntId = eValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -29,7 +31,7 @@ namespace Robust.Client.GameStates
|
||||
handle.UseShader(_shader);
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysicsComponent>(true))
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysBody>(true))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = ((IComponent)boundingBox).Owner.Transform;
|
||||
@@ -42,7 +44,7 @@ namespace Robust.Client.GameStates
|
||||
if(transform.LerpDestination == null)
|
||||
continue;
|
||||
|
||||
var aabb = ((IPhysBody)boundingBox).AABB;
|
||||
var aabb = boundingBox.GetWorldAABB();
|
||||
|
||||
// if not on screen, or too small, continue
|
||||
if (!aabb.Translated(transform.WorldPosition).Intersects(viewport) || aabb.IsEmpty())
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
}
|
||||
|
||||
private void _audioCreateContext()
|
||||
@@ -81,7 +81,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _audioOpenDevice()
|
||||
{
|
||||
var preferredDevice = _configurationManager.GetCVar(CVars.AudioDevice);
|
||||
var preferredDevice = ConfigurationManager.GetCVar(CVars.AudioDevice);
|
||||
|
||||
// Open device.
|
||||
if (!string.IsNullOrEmpty(preferredDevice))
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Log;
|
||||
@@ -115,7 +115,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var prev = cap;
|
||||
var cVarName = $"display.ogl_block_{capName}";
|
||||
var block = _configurationManager.GetCVar<bool>(cVarName);
|
||||
var block = ConfigurationManager.GetCVar<bool>(cVarName);
|
||||
|
||||
if (block)
|
||||
{
|
||||
@@ -146,7 +146,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
foreach (var cvar in cvars)
|
||||
{
|
||||
_configurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
ConfigurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,13 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Buffers;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement.ResourceTypes;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
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)}");
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement.ResourceTypes;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using StencilOp = Robust.Client.Graphics.StencilOp;
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
@@ -14,6 +15,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using static Robust.Client.Utility.LiterallyJustMessageBox;
|
||||
@@ -46,6 +48,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Keep delegates around to prevent GC issues.
|
||||
private GLFWCallbacks.ErrorCallback _errorCallback = default!;
|
||||
private GLFWCallbacks.MonitorCallback _monitorCallback = default!;
|
||||
private GLFWCallbacks.CharCallback _charCallback = default!;
|
||||
private GLFWCallbacks.CursorPosCallback _cursorPosCallback = default!;
|
||||
private GLFWCallbacks.KeyCallback _keyCallback = default!;
|
||||
@@ -73,6 +76,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private Vector2 _lastMousePos;
|
||||
|
||||
// Can't use ClydeHandle because it's 64 bit.
|
||||
private int _nextWindowId = 1;
|
||||
private readonly Dictionary<int, MonitorReg> _monitors = new();
|
||||
|
||||
public event Action<TextEventArgs>? TextEntered;
|
||||
public event Action<MouseMoveEventArgs>? MouseMove;
|
||||
public event Action<KeyEventArgs>? KeyUp;
|
||||
public event Action<KeyEventArgs>? KeyDown;
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel;
|
||||
public event Action<string>? CloseWindow;
|
||||
public event Action? OnWindowScaleChanged;
|
||||
|
||||
// NOTE: in engine we pretend the framebuffer size is the screen size..
|
||||
// For practical reasons like UI rendering.
|
||||
public override Vector2i ScreenSize => _framebufferSize;
|
||||
@@ -148,24 +163,71 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
}
|
||||
|
||||
InitMonitors();
|
||||
InitCursors();
|
||||
|
||||
return InitWindow();
|
||||
}
|
||||
|
||||
private void InitMonitors()
|
||||
{
|
||||
var monitors = GLFW.GetMonitorsRaw(out var count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
SetupMonitor(monitors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupMonitor(Monitor* monitor)
|
||||
{
|
||||
var handle = _nextWindowId++;
|
||||
|
||||
DebugTools.Assert(GLFW.GetMonitorUserPointer(monitor) == null, "GLFW window already has user pointer??");
|
||||
|
||||
var name = GLFW.GetMonitorName(monitor);
|
||||
var videoMode = GLFW.GetVideoMode(monitor);
|
||||
var impl = new ClydeMonitorImpl(handle, name, (videoMode->Width, videoMode->Height), videoMode->RefreshRate);
|
||||
|
||||
GLFW.SetMonitorUserPointer(monitor, (void*) handle);
|
||||
_monitors[handle] = new MonitorReg
|
||||
{
|
||||
Id = handle,
|
||||
Impl = impl,
|
||||
Monitor = monitor
|
||||
};
|
||||
}
|
||||
|
||||
private void DestroyMonitor(Monitor* monitor)
|
||||
{
|
||||
var ptr = GLFW.GetMonitorUserPointer(monitor);
|
||||
|
||||
if (ptr == null)
|
||||
{
|
||||
var name = GLFW.GetMonitorName(monitor);
|
||||
Logger.WarningS("clyde.win", $"Monitor '{name}' had no user pointer set??");
|
||||
return;
|
||||
}
|
||||
|
||||
_monitors.Remove((int) ptr);
|
||||
GLFW.SetMonitorUserPointer(monitor, null);
|
||||
}
|
||||
|
||||
private bool InitWindow()
|
||||
{
|
||||
var width = _configurationManager.GetCVar(CVars.DisplayWidth);
|
||||
var height = _configurationManager.GetCVar(CVars.DisplayHeight);
|
||||
var width = ConfigurationManager.GetCVar(CVars.DisplayWidth);
|
||||
var height = ConfigurationManager.GetCVar(CVars.DisplayHeight);
|
||||
|
||||
Monitor* monitor = null;
|
||||
|
||||
if (WindowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
monitor = GLFW.GetPrimaryMonitor();
|
||||
monitor = GLFW.GetMonitors()[1];
|
||||
var mode = GLFW.GetVideoMode(monitor);
|
||||
width = mode->Width;
|
||||
height = mode->Height;
|
||||
|
||||
GLFW.WindowHint(WindowHintInt.RefreshRate, mode->RefreshRate);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -174,13 +236,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
|
||||
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
|
||||
|
||||
var renderer = (Renderer) _configurationManager.GetCVar<int>(CVars.DisplayRenderer);
|
||||
var renderer = (Renderer) ConfigurationManager.GetCVar<int>(CVars.DisplayRenderer);
|
||||
|
||||
Span<Renderer> renderers = (renderer == Renderer.Default) ? stackalloc Renderer[] {
|
||||
Renderer.OpenGL33,
|
||||
Renderer.OpenGL31,
|
||||
Renderer.OpenGLES2
|
||||
} : stackalloc Renderer[] {renderer};
|
||||
Span<Renderer> renderers = (renderer == Renderer.Default)
|
||||
? stackalloc Renderer[]
|
||||
{
|
||||
Renderer.OpenGL33,
|
||||
Renderer.OpenGL31,
|
||||
Renderer.OpenGLES2
|
||||
}
|
||||
: stackalloc Renderer[] {renderer};
|
||||
|
||||
foreach (Renderer r in renderers)
|
||||
{
|
||||
@@ -193,6 +258,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_isCore = renderer == Renderer.OpenGL33;
|
||||
break;
|
||||
}
|
||||
|
||||
// Window failed to init due to error.
|
||||
// Try not to treat the error code seriously.
|
||||
var code = GLFW.GetError(out string desc);
|
||||
@@ -206,9 +272,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var code = GLFW.GetError(out string desc);
|
||||
|
||||
var errorContent = "Failed to create the game window. " +
|
||||
"This probably means your GPU is too old to play the game. " +
|
||||
"That or update your graphic drivers\n" +
|
||||
$"The exact error is: [{code}]\n {desc}";
|
||||
"This probably means your GPU is too old to play the game. " +
|
||||
"That or update your graphic drivers\n" +
|
||||
$"The exact error is: [{code}]\n {desc}";
|
||||
|
||||
MessageBoxW(null,
|
||||
errorContent,
|
||||
@@ -301,6 +367,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
|
||||
}
|
||||
|
||||
_glfwWindow = GLFW.CreateWindow(width, height, string.Empty, monitor, null);
|
||||
}
|
||||
}
|
||||
@@ -393,11 +460,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Logger.ErrorS("clyde.win.glfw", "GLFW Error: [{0}] {1}", code, description);
|
||||
}
|
||||
|
||||
private void OnGlfwMonitor(Monitor* monitor, ConnectedState state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (state == ConnectedState.Connected)
|
||||
{
|
||||
SetupMonitor(monitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyMonitor(monitor);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CatchCallbackException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGlfwChar(Window* window, uint codepoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
_gameController.TextEntered(new TextEventArgs(codepoint));
|
||||
TextEntered?.Invoke(new TextEventArgs(codepoint));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -413,8 +499,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var delta = newPos - _lastMousePos;
|
||||
_lastMousePos = newPos;
|
||||
|
||||
var ev = new MouseMoveEventArgs(delta, newPos);
|
||||
_gameController.MouseMove(ev);
|
||||
MouseMove?.Invoke(new MouseMoveEventArgs(delta, newPos));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -461,11 +546,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
switch (action)
|
||||
{
|
||||
case InputAction.Release:
|
||||
_gameController.KeyUp(ev);
|
||||
KeyUp?.Invoke(ev);
|
||||
break;
|
||||
case InputAction.Press:
|
||||
case InputAction.Repeat:
|
||||
_gameController.KeyDown(ev);
|
||||
KeyDown?.Invoke(ev);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(action), action, null);
|
||||
@@ -477,7 +562,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
try
|
||||
{
|
||||
var ev = new MouseWheelEventArgs(((float) offsetX, (float) offsetY), _lastMousePos);
|
||||
_gameController.MouseWheel(ev);
|
||||
MouseWheel?.Invoke(ev);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -489,7 +574,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
try
|
||||
{
|
||||
_gameController.Shutdown("Window closed");
|
||||
CloseWindow?.Invoke("Window closed");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -533,6 +618,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
try
|
||||
{
|
||||
_windowScale = (xScale, yScale);
|
||||
OnWindowScaleChanged?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -568,6 +654,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void StoreCallbacks()
|
||||
{
|
||||
_errorCallback = OnGlfwError;
|
||||
_monitorCallback = OnGlfwMonitor;
|
||||
_charCallback = OnGlfwChar;
|
||||
_cursorPosCallback = OnGlfwCursorPos;
|
||||
_keyCallback = OnGlfwKey;
|
||||
@@ -590,6 +677,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.SetWindowTitle(_glfwWindow, title);
|
||||
}
|
||||
|
||||
public void SetWindowMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
var monitorImpl = (ClydeMonitorImpl) monitor;
|
||||
var reg = _monitors[monitorImpl.Id];
|
||||
|
||||
GLFW.SetWindowMonitor(
|
||||
_glfwWindow,
|
||||
reg.Monitor,
|
||||
0, 0,
|
||||
monitorImpl.Size.X, monitorImpl.Size.Y,
|
||||
monitorImpl.RefreshRate);
|
||||
}
|
||||
|
||||
public void RequestWindowAttention()
|
||||
{
|
||||
GLFW.RequestWindowAttention(_glfwWindow);
|
||||
@@ -654,18 +754,40 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
GLFW.GetWindowPos(_glfwWindow, out var x, out var y);
|
||||
_prevWindowPos = (x, y);
|
||||
var monitor = GLFW.GetPrimaryMonitor();
|
||||
var monitor = MonitorForWindow(_glfwWindow);
|
||||
var mode = GLFW.GetVideoMode(monitor);
|
||||
|
||||
GLFW.SetWindowMonitor(_glfwWindow, GLFW.GetPrimaryMonitor(), 0, 0, mode->Width, mode->Height,
|
||||
GLFW.SetWindowMonitor(_glfwWindow, monitor, 0, 0, mode->Width, mode->Height,
|
||||
mode->RefreshRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
GLFW.SetWindowMonitor(_glfwWindow, null, _prevWindowPos.X, _prevWindowPos.Y, _prevWindowSize.X, _prevWindowSize.Y, 0);
|
||||
GLFW.SetWindowMonitor(_glfwWindow, null, _prevWindowPos.X, _prevWindowPos.Y, _prevWindowSize.X,
|
||||
_prevWindowSize.Y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// glfwGetWindowMonitor only works for fullscreen windows.
|
||||
// Picks the monitor with the top-left corner of the window.
|
||||
private Monitor* MonitorForWindow(Window* window)
|
||||
{
|
||||
GLFW.GetWindowPos(window, out var winPosX, out var winPosY);
|
||||
var monitors = GLFW.GetMonitorsRaw(out var count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var monitor = monitors[i];
|
||||
GLFW.GetMonitorPos(monitor, out var monPosX, out var monPosY);
|
||||
var videoMode = GLFW.GetVideoMode(monitor);
|
||||
|
||||
var box = Box2i.FromDimensions(monPosX, monPosY, videoMode->Width, videoMode->Height);
|
||||
if (box.Contains(winPosX, winPosY))
|
||||
return monitor;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return GLFW.GetPrimaryMonitor();
|
||||
}
|
||||
|
||||
string IClipboardManager.GetText()
|
||||
{
|
||||
return GLFW.GetClipboardString(_glfwWindow);
|
||||
@@ -676,6 +798,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.SetClipboardString(_glfwWindow, text);
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
{
|
||||
return _monitors.Values.Select(c => c.Impl);
|
||||
}
|
||||
|
||||
// We can't let exceptions unwind into GLFW, as that can cause the CLR to crash.
|
||||
// And it probably messes up GLFW too.
|
||||
// So all the callbacks are passed to this method.
|
||||
@@ -689,5 +816,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_glfwExceptionList.Add(e);
|
||||
}
|
||||
|
||||
private sealed class MonitorReg
|
||||
{
|
||||
public int Id;
|
||||
public Monitor* Monitor;
|
||||
public ClydeMonitorImpl Impl = default!;
|
||||
}
|
||||
|
||||
private sealed class ClydeMonitorImpl : IClydeMonitor
|
||||
{
|
||||
public ClydeMonitorImpl(int id, string name, Vector2i size, int refreshRate)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Size = size;
|
||||
RefreshRate = refreshRate;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
public string Name { get; }
|
||||
public Vector2i Size { get; }
|
||||
public int RefreshRate { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
|
||||
if (!InitWindowing())
|
||||
{
|
||||
@@ -124,9 +124,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
protected override void ReadConfig()
|
||||
{
|
||||
base.ReadConfig();
|
||||
_lightmapDivider = _configurationManager.GetCVar(CVars.DisplayLightMapDivider);
|
||||
_maxLightsPerScene = _configurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
|
||||
_enableSoftShadows = _configurationManager.GetCVar(CVars.DisplaySoftShadows);
|
||||
_lightmapDivider = ConfigurationManager.GetCVar(CVars.DisplayLightMapDivider);
|
||||
_maxLightsPerScene = ConfigurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
|
||||
_enableSoftShadows = ConfigurationManager.GetCVar(CVars.DisplaySoftShadows);
|
||||
}
|
||||
|
||||
protected override void ReloadConfig()
|
||||
@@ -238,7 +238,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private (int major, int minor)? ParseGLOverrideVersion()
|
||||
{
|
||||
var overrideGLVersion = _configurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
var overrideGLVersion = ConfigurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
if (string.IsNullOrEmpty(overrideGLVersion))
|
||||
{
|
||||
return null;
|
||||
@@ -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()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
@@ -37,6 +38,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
|
||||
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
|
||||
|
||||
public event Action<TextEventArgs>? TextEntered;
|
||||
public event Action<MouseMoveEventArgs>? MouseMove;
|
||||
public event Action<KeyEventArgs>? KeyUp;
|
||||
public event Action<KeyEventArgs>? KeyDown;
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel;
|
||||
public event Action<string>? CloseWindow;
|
||||
|
||||
public Texture GetStockTexture(ClydeStockTexture stockTexture)
|
||||
{
|
||||
return new DummyTexture((1, 1));
|
||||
@@ -63,6 +71,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetWindowMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void RequestWindowAttention()
|
||||
{
|
||||
// Nada.
|
||||
@@ -86,6 +99,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action OnWindowScaleChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
// Nada.
|
||||
@@ -101,7 +120,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 +129,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));
|
||||
@@ -156,6 +175,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new Viewport();
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
{
|
||||
// TODO: Actually return something.
|
||||
yield break;
|
||||
}
|
||||
|
||||
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
|
||||
{
|
||||
return default;
|
||||
@@ -267,6 +292,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 --
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
internal abstract class ClydeBase
|
||||
{
|
||||
[Dependency] protected readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] protected readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] protected readonly IConfigurationManager ConfigurationManager = default!;
|
||||
|
||||
protected WindowMode WindowMode { get; private set; } = WindowMode.Windowed;
|
||||
protected bool VSync { get; private set; } = true;
|
||||
@@ -31,11 +30,11 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public virtual bool Initialize()
|
||||
{
|
||||
_configurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -51,8 +50,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
protected virtual void ReadConfig()
|
||||
{
|
||||
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);
|
||||
VSync = _configurationManager.GetCVar(CVars.DisplayVSync);
|
||||
WindowMode = (WindowMode) ConfigurationManager.GetCVar(CVars.DisplayWindowMode);
|
||||
VSync = ConfigurationManager.GetCVar(CVars.DisplayVSync);
|
||||
}
|
||||
|
||||
private void _vSyncChanged(bool newValue)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,9 +4,6 @@ using System.IO;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SharpFont;
|
||||
@@ -20,18 +17,18 @@ namespace Robust.Client.Graphics
|
||||
private const int SheetWidth = 256;
|
||||
private const int SheetHeight = 256;
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
private readonly IClyde _clyde;
|
||||
|
||||
private uint BaseFontDPI;
|
||||
private uint _baseFontDpi = 96;
|
||||
|
||||
private readonly Library _library;
|
||||
|
||||
private readonly Dictionary<(FontFaceHandle, int fontSize), FontInstanceHandle> _loadedInstances =
|
||||
new();
|
||||
|
||||
public FontManager()
|
||||
public FontManager(IClyde clyde)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_library = new Library();
|
||||
}
|
||||
|
||||
@@ -42,9 +39,9 @@ namespace Robust.Client.Graphics
|
||||
return handle;
|
||||
}
|
||||
|
||||
void IFontManagerInternal.Initialize()
|
||||
void IFontManagerInternal.SetFontDpi(uint fontDpi)
|
||||
{
|
||||
BaseFontDPI = (uint) _configuration.GetCVar(CVars.DisplayFontDpi);
|
||||
_baseFontDpi = fontDpi;
|
||||
}
|
||||
|
||||
public IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size)
|
||||
@@ -64,7 +61,7 @@ namespace Robust.Client.Graphics
|
||||
private ScaledFontData _generateScaledDatum(FontInstanceHandle instance, float scale)
|
||||
{
|
||||
var ftFace = instance.FaceHandle.Face;
|
||||
ftFace.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
|
||||
ftFace.SetCharSize(0, instance.Size, 0, (uint) (_baseFontDpi * scale));
|
||||
|
||||
var ascent = ftFace.Size.Metrics.Ascender.ToInt32();
|
||||
var descent = -ftFace.Size.Metrics.Descender.ToInt32();
|
||||
@@ -83,7 +80,7 @@ namespace Robust.Client.Graphics
|
||||
return;
|
||||
|
||||
var face = instance.FaceHandle.Face;
|
||||
face.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
|
||||
face.SetCharSize(0, instance.Size, 0, (uint) (_baseFontDpi * scale));
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
@@ -189,7 +186,7 @@ namespace Robust.Client.Graphics
|
||||
OwnedTexture GenSheet()
|
||||
{
|
||||
var sheet = _clyde.CreateBlankTexture<A8>((SheetWidth, SheetHeight),
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (_baseFontDpi * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
scaled.AtlasTextures.Add(sheet);
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -21,6 +22,7 @@ namespace Robust.Client.Graphics
|
||||
Vector2 DefaultWindowScale { get; }
|
||||
|
||||
void SetWindowTitle(string title);
|
||||
void SetWindowMonitor(IClydeMonitor monitor);
|
||||
|
||||
/// <summary>
|
||||
/// This is the magic method to make the game window ping you in the task bar.
|
||||
@@ -31,10 +33,12 @@ namespace Robust.Client.Graphics
|
||||
|
||||
event Action<WindowFocusedEventArgs> OnWindowFocused;
|
||||
|
||||
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
event Action OnWindowScaleChanged;
|
||||
|
||||
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>
|
||||
@@ -104,6 +108,8 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
IClydeViewport CreateViewport(Vector2i size, string? name = null);
|
||||
|
||||
IEnumerable<IClydeMonitor> EnumerateMonitors();
|
||||
}
|
||||
|
||||
// TODO: Maybe implement IDisposable for render targets. I got lazy and didn't.
|
||||
|
||||
@@ -20,5 +20,6 @@ namespace Robust.Client.Graphics
|
||||
void SetVolume(float decibels);
|
||||
void SetOcclusion(float blocks);
|
||||
void SetPlaybackPosition(float seconds);
|
||||
void SetVelocity(Vector2 velocity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,6 +17,13 @@ namespace Robust.Client.Graphics
|
||||
bool Initialize();
|
||||
void Ready();
|
||||
|
||||
event Action<TextEventArgs> TextEntered;
|
||||
event Action<MouseMoveEventArgs> MouseMove;
|
||||
event Action<KeyEventArgs> KeyUp;
|
||||
event Action<KeyEventArgs> KeyDown;
|
||||
event Action<MouseWheelEventArgs> MouseWheel;
|
||||
event Action<string> CloseWindow;
|
||||
|
||||
ClydeHandle LoadShader(ParsedShader shader, string? name = null);
|
||||
|
||||
void ReloadShader(ClydeHandle handle, ParsedShader newShader);
|
||||
|
||||
18
Robust.Client/Graphics/IClydeMonitor.cs
Normal file
18
Robust.Client/Graphics/IClydeMonitor.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a connected monitor on the user's system.
|
||||
/// </summary>
|
||||
public interface IClydeMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// This ID is not consistent between startups of the game.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
string Name { get; }
|
||||
Vector2i Size { get; }
|
||||
int RefreshRate { get; }
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
IFontFaceHandle Load(Stream stream);
|
||||
IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size);
|
||||
void Initialize();
|
||||
void SetFontDpi(uint fontDpi);
|
||||
}
|
||||
|
||||
internal interface IFontFaceHandle
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.ContainsKey(overlayClass);
|
||||
}
|
||||
|
||||
public bool HasOverlay<T>() where T : Overlay {
|
||||
return _overlays.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement.ResourceTypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
[Prototype("shader")]
|
||||
public sealed class ShaderPrototype : IPrototype
|
||||
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public string ID { get; private set; } = default!;
|
||||
[ViewVariables]
|
||||
[field: DataField("id", required: true)]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
private ShaderKind Kind;
|
||||
|
||||
@@ -31,11 +34,14 @@ namespace Robust.Client.Graphics
|
||||
private ShaderInstance? _cachedInstance;
|
||||
|
||||
private bool _stencilEnabled;
|
||||
private int _stencilRef;
|
||||
private int _stencilReadMask = unchecked((int) uint.MaxValue);
|
||||
private int _stencilWriteMask = unchecked((int) uint.MaxValue);
|
||||
private StencilFunc _stencilFunc = StencilFunc.Always;
|
||||
private StencilOp _stencilOp = StencilOp.Keep;
|
||||
private int _stencilRef => StencilDataHolder?.StencilRef ?? 0;
|
||||
private int _stencilReadMask => StencilDataHolder?.ReadMask ?? unchecked((int) uint.MaxValue);
|
||||
private int _stencilWriteMask => StencilDataHolder?.WriteMask ?? unchecked((int) uint.MaxValue);
|
||||
private StencilFunc _stencilFunc => StencilDataHolder?.StencilFunc ?? StencilFunc.Always;
|
||||
private StencilOp _stencilOp => StencilDataHolder?.StencilOp ?? StencilOp.Keep;
|
||||
|
||||
[DataField("stencil")]
|
||||
private StencilData? StencilDataHolder;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a ready-to-use instance of this shader.
|
||||
@@ -64,12 +70,12 @@ namespace Robust.Client.Graphics
|
||||
switch (Kind)
|
||||
{
|
||||
case ShaderKind.Source:
|
||||
instance = _clyde.InstanceShader(Source!.ClydeHandle);
|
||||
instance = IoCManager.Resolve<IClydeInternal>().InstanceShader(Source!.ClydeHandle);
|
||||
_applyDefaultParameters(instance);
|
||||
break;
|
||||
|
||||
case ShaderKind.Canvas:
|
||||
instance = _clyde.InstanceShader(CompiledCanvasShader);
|
||||
instance = IoCManager.Resolve<IClydeInternal>().InstanceShader(CompiledCanvasShader);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -95,135 +101,108 @@ namespace Robust.Client.Graphics
|
||||
return Instance().Duplicate();
|
||||
}
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
ID = mapping.GetNode("id").ToString();
|
||||
[DataField("kind", readOnly: true, required: true)] private string _rawKind = default!;
|
||||
[DataField("path", readOnly: true)] private ResourcePath? path;
|
||||
[DataField("params", readOnly: true)] private Dictionary<string, string>? paramMapping;
|
||||
[DataField("light_mode", readOnly: true)] private string? rawMode;
|
||||
[DataField("blend_mode", readOnly: true)] private string? rawBlendMode;
|
||||
|
||||
var kind = mapping.GetNode("kind").AsString();
|
||||
switch (kind)
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
switch (_rawKind)
|
||||
{
|
||||
case "source":
|
||||
Kind = ShaderKind.Source;
|
||||
ReadSourceKind(mapping);
|
||||
if (path == null) throw new InvalidOperationException();
|
||||
Source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(path);
|
||||
|
||||
if (paramMapping != null)
|
||||
{
|
||||
ShaderParams = new Dictionary<string, object>();
|
||||
foreach (var item in paramMapping!)
|
||||
{
|
||||
var name = item.Key;
|
||||
if (!Source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
|
||||
{
|
||||
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = _parseUniformValue(item.Value, uniformDefinition.Type.Type);
|
||||
ShaderParams.Add(name, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "canvas":
|
||||
Kind = ShaderKind.Canvas;
|
||||
ReadCanvasKind(mapping);
|
||||
var source = "";
|
||||
|
||||
if(rawMode != null)
|
||||
{
|
||||
switch (rawMode)
|
||||
{
|
||||
case "normal":
|
||||
break;
|
||||
|
||||
case "unshaded":
|
||||
source += "light_mode unshaded;\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid light mode: '{rawMode}'");
|
||||
}
|
||||
}
|
||||
|
||||
if(rawBlendMode != null){
|
||||
switch (rawBlendMode)
|
||||
{
|
||||
case "mix":
|
||||
source += "blend_mode mix;\n";
|
||||
break;
|
||||
|
||||
case "add":
|
||||
source += "blend_mode add;\n";
|
||||
break;
|
||||
|
||||
case "subtract":
|
||||
source += "blend_mode subtract;\n";
|
||||
break;
|
||||
|
||||
case "multiply":
|
||||
source += "blend_mode multiply;\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid blend mode: '{rawBlendMode}'");
|
||||
}
|
||||
}
|
||||
|
||||
source += "void fragment() {\n COLOR = zTexture(UV);\n}";
|
||||
|
||||
var preset = ShaderParser.Parse(source, _resourceCache);
|
||||
CompiledCanvasShader = IoCManager.Resolve<IClydeInternal>().LoadShader(preset, $"canvas_preset_{ID}");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid shader kind: '{kind}'");
|
||||
throw new InvalidOperationException($"Invalid shader kind: '{_rawKind}'");
|
||||
}
|
||||
|
||||
// Load stencil data.
|
||||
if (mapping.TryGetNode("stencil", out YamlMappingNode? stencilData))
|
||||
{
|
||||
ReadStencilData(stencilData);
|
||||
}
|
||||
if (StencilDataHolder != null) _stencilEnabled = true;
|
||||
}
|
||||
|
||||
private void ReadStencilData(YamlMappingNode stencilData)
|
||||
[DataDefinition]
|
||||
public class StencilData
|
||||
{
|
||||
_stencilEnabled = true;
|
||||
[DataField("ref")] public int StencilRef;
|
||||
|
||||
if (stencilData.TryGetNode("ref", out var dataNode))
|
||||
{
|
||||
_stencilRef = dataNode.AsInt();
|
||||
}
|
||||
[DataField("op")] public StencilOp StencilOp;
|
||||
|
||||
if (stencilData.TryGetNode("op", out dataNode))
|
||||
{
|
||||
_stencilOp = dataNode.AsEnum<StencilOp>();
|
||||
}
|
||||
[DataField("func")] public StencilFunc StencilFunc;
|
||||
|
||||
if (stencilData.TryGetNode("func", out dataNode))
|
||||
{
|
||||
_stencilFunc = dataNode.AsEnum<StencilFunc>();
|
||||
}
|
||||
[DataField("readMask")] public int ReadMask = unchecked((int) uint.MaxValue);
|
||||
|
||||
if (stencilData.TryGetNode("readMask", out dataNode))
|
||||
{
|
||||
_stencilReadMask = dataNode.AsInt();
|
||||
}
|
||||
|
||||
if (stencilData.TryGetNode("writeMask", out dataNode))
|
||||
{
|
||||
_stencilWriteMask = dataNode.AsInt();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadSourceKind(YamlMappingNode mapping)
|
||||
{
|
||||
var path = mapping.GetNode("path").AsResourcePath();
|
||||
Source = _resourceCache.GetResource<ShaderSourceResource>(path);
|
||||
if (mapping.TryGetNode<YamlMappingNode>("params", out var paramMapping))
|
||||
{
|
||||
ShaderParams = new Dictionary<string, object>();
|
||||
foreach (var item in paramMapping)
|
||||
{
|
||||
var name = item.Key.AsString();
|
||||
if (!Source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
|
||||
{
|
||||
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = _parseUniformValue(item.Value, uniformDefinition.Type.Type);
|
||||
ShaderParams.Add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCanvasKind(YamlMappingNode mapping)
|
||||
{
|
||||
var source = "";
|
||||
|
||||
if (mapping.TryGetNode("light_mode", out var node))
|
||||
{
|
||||
switch (node.AsString())
|
||||
{
|
||||
case "normal":
|
||||
break;
|
||||
|
||||
case "unshaded":
|
||||
source += "light_mode unshaded;\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid light mode: '{node.AsString()}'");
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("blend_mode", out node))
|
||||
{
|
||||
switch (node.AsString())
|
||||
{
|
||||
case "mix":
|
||||
source += "blend_mode mix;\n";
|
||||
break;
|
||||
|
||||
case "add":
|
||||
source += "blend_mode add;\n";
|
||||
break;
|
||||
|
||||
case "subtract":
|
||||
source += "blend_mode subtract;\n";
|
||||
break;
|
||||
|
||||
case "multiply":
|
||||
source += "blend_mode multiply;\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid blend mode: '{node.AsString()}'");
|
||||
}
|
||||
}
|
||||
|
||||
source += "void fragment() {\n COLOR = zTexture(UV);\n}";
|
||||
|
||||
var preset = ShaderParser.Parse(source, _resourceCache);
|
||||
CompiledCanvasShader = _clyde.LoadShader(preset, $"canvas_preset_{ID}");
|
||||
[DataField("writeMask")] public int WriteMask = unchecked((int) uint.MaxValue);
|
||||
}
|
||||
|
||||
private static object _parseUniformValue(YamlNode node, ShaderDataType dataType)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,6 +19,8 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.Core;
|
||||
@@ -117,8 +119,8 @@ namespace Robust.Client.Input
|
||||
|
||||
public void SaveToUserData()
|
||||
{
|
||||
var mapping = new YamlMappingNode();
|
||||
var ser = YamlObjectSerializer.NewWriter(mapping);
|
||||
var mapping = new MappingDataNode();
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
|
||||
var modifiedBindings = _modifiedKeyFunctions
|
||||
.Select(p => _bindingsByFunction[p])
|
||||
@@ -141,15 +143,13 @@ namespace Robust.Client.Input
|
||||
.Where(p => _bindingsByFunction[p].Count == 0)
|
||||
.ToArray();
|
||||
|
||||
var version = 1;
|
||||
|
||||
ser.DataField(ref version, "version", 1);
|
||||
ser.DataField(ref modifiedBindings, "binds", Array.Empty<KeyBindingRegistration>());
|
||||
ser.DataField(ref leaveEmpty, "leaveEmpty", Array.Empty<BoundKeyFunction>());
|
||||
mapping.AddNode("version", new ValueDataNode("1"));
|
||||
mapping.AddNode("binds", serializationManager.WriteValue(modifiedBindings));
|
||||
mapping.AddNode("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
|
||||
|
||||
var path = new ResourcePath(KeybindsPath);
|
||||
using var writer = new StreamWriter(_resourceMan.UserData.Create(path));
|
||||
var stream = new YamlStream {new(mapping)};
|
||||
var stream = new YamlStream {new(mapping.ToMappingNode())};
|
||||
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||
}
|
||||
|
||||
@@ -416,12 +416,14 @@ namespace Robust.Client.Input
|
||||
|
||||
var mapping = (YamlMappingNode) yamlStream.Documents[0].RootNode;
|
||||
|
||||
var baseSerializer = YamlObjectSerializer.NewReader(mapping);
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
var robustMapping = mapping.ToDataNode() as MappingDataNode;
|
||||
if (robustMapping == null) throw new InvalidOperationException();
|
||||
|
||||
var foundBinds = baseSerializer.TryReadDataField<KeyBindingRegistration[]>("binds", out var baseKeyRegs);
|
||||
|
||||
if (foundBinds && baseKeyRegs != null && baseKeyRegs.Length > 0)
|
||||
if (robustMapping.TryGetNode("binds", out var BaseKeyRegsNode))
|
||||
{
|
||||
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
|
||||
|
||||
foreach (var reg in baseKeyRegs)
|
||||
{
|
||||
if (!NetworkBindMap.FunctionExists(reg.Function.FunctionName))
|
||||
@@ -447,11 +449,11 @@ namespace Robust.Client.Input
|
||||
}
|
||||
}
|
||||
|
||||
if (userData)
|
||||
if (userData && robustMapping.TryGetNode("leaveEmpty", out var node))
|
||||
{
|
||||
var foundLeaveEmpty = baseSerializer.TryReadDataField<BoundKeyFunction[]>("leaveEmpty", out var leaveEmpty);
|
||||
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
|
||||
|
||||
if (foundLeaveEmpty && leaveEmpty != null && leaveEmpty.Length > 0)
|
||||
if (leaveEmpty.Length > 0)
|
||||
{
|
||||
// Adding to _modifiedKeyFunctions means that these keybinds won't be loaded from the base file.
|
||||
// Because they've been explicitly cleared.
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Client.Input
|
||||
{
|
||||
public struct KeyBindingRegistration : IExposeData
|
||||
[DataDefinition]
|
||||
public class KeyBindingRegistration
|
||||
{
|
||||
[DataField("function")]
|
||||
public BoundKeyFunction Function;
|
||||
public KeyBindingType Type;
|
||||
[DataField("type")]
|
||||
public KeyBindingType Type = KeyBindingType.State;
|
||||
[DataField("key")]
|
||||
public Keyboard.Key BaseKey;
|
||||
[DataField("mod1")]
|
||||
public Keyboard.Key Mod1;
|
||||
[DataField("mod2")]
|
||||
public Keyboard.Key Mod2;
|
||||
[DataField("mod3")]
|
||||
public Keyboard.Key Mod3;
|
||||
[DataField("priority")]
|
||||
public int Priority;
|
||||
[DataField("canFocus")]
|
||||
public bool CanFocus;
|
||||
[DataField("canRepeat")]
|
||||
public bool CanRepeat;
|
||||
[DataField("allowSubCombs")]
|
||||
public bool AllowSubCombs;
|
||||
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(ref Function, "function", default);
|
||||
serializer.DataField(ref Type, "type", KeyBindingType.State);
|
||||
serializer.DataField(ref BaseKey, "key", default);
|
||||
serializer.DataField(ref Mod1, "mod1", default);
|
||||
serializer.DataField(ref Mod2, "mod2", default);
|
||||
serializer.DataField(ref Mod3, "mod3", default);
|
||||
serializer.DataField(ref Priority, "priority", 0);
|
||||
serializer.DataField(ref CanFocus, "canFocus", false);
|
||||
serializer.DataField(ref CanRepeat, "canRepeat", false);
|
||||
serializer.DataField(ref AllowSubCombs, "allowSubCombs", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
Robust.Client/Physics/BroadPhaseSystem.cs
Normal file
13
Robust.Client/Physics/BroadPhaseSystem.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Robust.Client/Physics/DebugPhysicsIslandSystem.cs
Normal file
136
Robust.Client/Physics/DebugPhysicsIslandSystem.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
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;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
internal sealed class PhysicsIslandCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showislands";
|
||||
public string Description => "Shows the current physics bodies involved in each physics island.";
|
||||
public string Help => "showislands";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 0)
|
||||
{
|
||||
shell.WriteLine("This command doesn't take args!");
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<DebugPhysicsIslandSystem>().Mode ^= DebugPhysicsIslandMode.Solve;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DebugPhysicsIslandSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public DebugPhysicsIslandMode Mode { get; set; } = DebugPhysicsIslandMode.None;
|
||||
|
||||
/*
|
||||
* Island solve debug:
|
||||
* This will draw above every body involved in a particular island solve.
|
||||
*/
|
||||
|
||||
public readonly Queue<(TimeSpan Time, List<IPhysBody> Bodies)> IslandSolve = new();
|
||||
public const float SolveDuration = 0.1f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IslandSolveMessage>(HandleIslandSolveMessage);
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsIslandOverlay());
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
while (IslandSolve.TryPeek(out var solve))
|
||||
{
|
||||
if (solve.Time.TotalSeconds + SolveDuration > _gameTiming.CurTime.TotalSeconds)
|
||||
{
|
||||
IslandSolve.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
|
||||
}
|
||||
|
||||
private void HandleIslandSolveMessage(IslandSolveMessage message)
|
||||
{
|
||||
if ((Mode & DebugPhysicsIslandMode.Solve) == 0x0) return;
|
||||
IslandSolve.Enqueue((_gameTiming.CurTime, message.Bodies));
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum DebugPhysicsIslandMode : ushort
|
||||
{
|
||||
None = 0,
|
||||
Solve = 1 << 0,
|
||||
Contacts = 1 << 1,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsIslandOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private DebugPhysicsIslandSystem _islandSystem = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public PhysicsIslandOverlay()
|
||||
{
|
||||
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
|
||||
_eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
|
||||
DrawIslandSolve(worldHandle);
|
||||
}
|
||||
|
||||
private void DrawIslandSolve(DrawingHandleWorld handle)
|
||||
{
|
||||
if ((_islandSystem.Mode & DebugPhysicsIslandMode.Solve) == 0x0) return;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var solve in _islandSystem.IslandSolve)
|
||||
{
|
||||
var ratio = (float) Math.Max(
|
||||
(solve.Time.TotalSeconds + DebugPhysicsIslandSystem.SolveDuration -
|
||||
_gameTiming.CurTime.TotalSeconds) / DebugPhysicsIslandSystem.SolveDuration, 0.0f);
|
||||
|
||||
if (ratio <= 0.0f) continue;
|
||||
|
||||
foreach (var body in solve.Bodies)
|
||||
{
|
||||
var worldAABB = body.GetWorldAABB();
|
||||
if (!viewport.Intersects(worldAABB)) continue;
|
||||
|
||||
handle.DrawRect(worldAABB, Color.Green.WithAlpha(ratio * 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
{
|
||||
@@ -126,14 +124,6 @@ namespace Robust.Client.Placement
|
||||
if (value != null)
|
||||
{
|
||||
PlacementOffset = value.PlacementOffset;
|
||||
|
||||
if (value.Components.ContainsKey("BoundingBox") && value.Components.ContainsKey("Physics"))
|
||||
{
|
||||
var map = value.Components["BoundingBox"];
|
||||
var serializer = YamlObjectSerializer.NewReader(map);
|
||||
serializer.DataField(ref _colliderAABB, "aabb", new Box2(0f, 0f, 0f, 0f));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_colliderAABB = new Box2(0f, 0f, 0f, 0f);
|
||||
|
||||
@@ -4,8 +4,10 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
@@ -230,7 +232,7 @@ namespace Robust.Client.Placement
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
|
||||
return pManager.PhysicsManager.TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
return EntitySystem.Get<SharedBroadPhaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
}
|
||||
|
||||
protected Vector2 ScreenToWorld(Vector2 point)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace Robust.Client.Player
|
||||
|
||||
void Initialize();
|
||||
void Startup(INetChannel channel);
|
||||
void Update(float frameTime);
|
||||
void Shutdown();
|
||||
|
||||
void ApplyPlayerStates(IEnumerable<PlayerState>? list);
|
||||
|
||||
@@ -95,12 +95,6 @@ namespace Robust.Client.Player
|
||||
_network.ClientSendMessage(msgList);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
// Uh, nothing anymore I guess.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Shutdown()
|
||||
{
|
||||
@@ -207,7 +201,7 @@ namespace Robust.Client.Player
|
||||
if (state.UserId == LocalPlayer!.UserId)
|
||||
{
|
||||
LocalPlayer.InternalSession = newSession;
|
||||
|
||||
newSession.ConnectedClient = _network.ServerChannel!;
|
||||
// We just connected to the server, hurray!
|
||||
LocalPlayer.SwitchState(SessionStatus.Connecting, newSession.Status);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace Robust.Client.Player
|
||||
/// <inheritdoc />
|
||||
internal short Ping { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public INetChannel ConnectedClient { get; internal set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
short ICommonSession.Ping
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
|
||||
[assembly: InternalsVisibleTo("Robust.Lite")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
#if NET5_0
|
||||
[module: SkipLocalsInit]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,6 @@ namespace Robust.Client.ResourceManagement
|
||||
void RsiLoaded(RsiLoadedEventArgs eventArgs);
|
||||
|
||||
void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null);
|
||||
void PreloadTextures();
|
||||
}
|
||||
}
|
||||
|
||||
196
Robust.Client/ResourceManagement/ResourceCache.Preload.cs
Normal file
196
Robust.Client/ResourceManagement/ResourceCache.Preload.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement.ResourceTypes
|
||||
namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the **source code** of a shader.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Client.Serialization
|
||||
{
|
||||
[TypeSerializer]
|
||||
public class AppearanceVisualizerSerializer : ITypeSerializer<AppearanceVisualizer, MappingDataNode>
|
||||
{
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (!node.TryGetNode("type", out var typeNode))
|
||||
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
|
||||
|
||||
if (typeNode is not ValueDataNode typeValueDataNode)
|
||||
throw new InvalidMappingException("Type node not a value node for AppearanceVisualizer!");
|
||||
|
||||
var type = IoCManager.Resolve<IReflectionManager>()
|
||||
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
|
||||
if (type == null)
|
||||
throw new InvalidMappingException(
|
||||
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
|
||||
|
||||
var newNode = (MappingDataNode)node.Copy();
|
||||
newNode.RemoveNode("type");
|
||||
return serializationManager.Read(type, newNode, context, skipHook);
|
||||
}
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
if (!node.TryGetNode("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
|
||||
{
|
||||
return new ErrorNode(node, "Missing/Invalid type", true);
|
||||
}
|
||||
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
var type = reflectionManager.YamlTypeTagLookup(typeof(AppearanceVisualizer), valueNode.Value);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
return new ErrorNode(node, $"Failed to resolve type: {valueNode.Value}", true);
|
||||
}
|
||||
|
||||
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().RemoveNode("type"));
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, AppearanceVisualizer value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var mapping = serializationManager.WriteValueAs<MappingDataNode>(value.GetType(), value, alwaysWrite, context);
|
||||
mapping.AddNode("type", new ValueDataNode(value.GetType().Name));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public AppearanceVisualizer Copy(ISerializationManager serializationManager, AppearanceVisualizer source,
|
||||
AppearanceVisualizer target, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
return serializationManager.Copy(source, target, context)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@ namespace Robust.Client.State
|
||||
|
||||
State CurrentState { get; }
|
||||
void RequestStateChange<T>() where T : State, new();
|
||||
void Update(FrameEventArgs e);
|
||||
void FrameUpdate(FrameEventArgs e);
|
||||
void FormResize();
|
||||
void RequestStateChange(Type type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.State
|
||||
{
|
||||
@@ -14,16 +14,6 @@ namespace Robust.Client.State
|
||||
/// </summary>
|
||||
public abstract void Shutdown();
|
||||
|
||||
/// <summary>
|
||||
/// Update the contents of this screen.
|
||||
/// </summary>
|
||||
public virtual void Update(FrameEventArgs e) { }
|
||||
|
||||
public virtual void FrameUpdate(FrameEventArgs e) { }
|
||||
|
||||
/// <summary>
|
||||
/// The screen has changed size, usually from resizing window. This is called automatically right after Startup.
|
||||
/// </summary>
|
||||
public virtual void FormResize() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Log;
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -17,21 +17,11 @@ namespace Robust.Client.State
|
||||
CurrentState = new DefaultState();
|
||||
}
|
||||
|
||||
public void Update(FrameEventArgs e)
|
||||
{
|
||||
CurrentState?.Update(e);
|
||||
}
|
||||
|
||||
public void FrameUpdate(FrameEventArgs e)
|
||||
{
|
||||
CurrentState?.FrameUpdate(e);
|
||||
}
|
||||
|
||||
public void FormResize()
|
||||
{
|
||||
CurrentState?.FormResize();
|
||||
}
|
||||
|
||||
public void RequestStateChange<T>() where T : State, new()
|
||||
{
|
||||
RequestStateChange(typeof(T));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -817,25 +817,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <summary>
|
||||
/// Called when the size of the control changes.
|
||||
/// </summary>
|
||||
protected virtual void Resized()
|
||||
{
|
||||
}
|
||||
|
||||
internal void DoUpdate(FrameEventArgs args)
|
||||
{
|
||||
Update(args);
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.DoUpdate(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called every process frame.
|
||||
/// </summary>
|
||||
protected virtual void Update(FrameEventArgs args)
|
||||
{
|
||||
}
|
||||
protected virtual void Resized() { }
|
||||
|
||||
internal void DoFrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user