mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15eded3da8 | ||
|
|
b4165e8661 | ||
|
|
ad339b5bfd | ||
|
|
e1197af8ce | ||
|
|
102cadf3a6 | ||
|
|
e7723b61bc | ||
|
|
a9d17337a3 | ||
|
|
74622bac83 | ||
|
|
a3047b1687 | ||
|
|
3a55118143 | ||
|
|
3c5fbc648a | ||
|
|
f9c39bce0b | ||
|
|
0e8c803c0f | ||
|
|
6bb7b88c69 | ||
|
|
9e0fc7017c | ||
|
|
76317b7ab3 | ||
|
|
d5f4d4bf2f | ||
|
|
156d1a6b14 | ||
|
|
3fa456fd44 | ||
|
|
bf9bb46154 | ||
|
|
fb3da0b53c | ||
|
|
5c635c09b4 | ||
|
|
fad539212d | ||
|
|
7275302639 | ||
|
|
5057ff97a3 | ||
|
|
754d5a1fbb | ||
|
|
7cee5b67a7 | ||
|
|
21729e7e48 | ||
|
|
394d1e6cc2 | ||
|
|
f73d7f7285 | ||
|
|
e505cfffd8 | ||
|
|
d0fe3591ef | ||
|
|
1868f32457 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,80 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 210.0.2
|
||||
|
||||
|
||||
## 210.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert changes to `TextureButton` that broke style property handling.
|
||||
|
||||
|
||||
## 210.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Controls can now hook before, after, and during rendering of their children.
|
||||
* IRenderHandle is now a public API, with the caveat that it's properties and methods are unstable.
|
||||
* ButtonGroup now exposes what buttons it contains, alongside which is currently pressed.
|
||||
* OptionButton has additional styleclasses, and has a hook for modifying it's internal buttons.
|
||||
* PanelContainer.GetStyleBox() is now protected rather than private.
|
||||
* TextureButton now uses a TextureRect instead of custom drawing code.
|
||||
* TextureRect has additional style properties exposed.
|
||||
* A new property, TextureSizeTarget, was added, which allows specifying a size in virtual pixels that the control should attempt to draw at.
|
||||
* Stretch mode is now a style property.
|
||||
* Scale is now a style property.
|
||||
* Avalonia.Metadata.XmlnsDefinitionAttribute is now permitted by the sandbox.
|
||||
* Add MaxDimension property to Box2 to return the higher of the Width or Height.
|
||||
* Add GetLocalPosition to convert ScreenCoordinates to coordinates relative to the control. Ignores window.
|
||||
* Add GlobalRect and GlobalPixelRect for controls to get their UIBox2i in screen terms.
|
||||
* Add dotted line drawing to DrawingHandleScreen.
|
||||
* You can use `Subs.CVar()` from an entity systems to subscribe to CVar changes. This is more convenient than `IConfigurationManager.OnValueChanged` as it automatically unsubscribes on system shutdown.
|
||||
* There is now a built-in type serializer for `DateTime`, so you can put `DateTime`s in your data fields.
|
||||
* `System.Text.Unicode.UnicodeRange` and `UnicodeRanges` are now available in the sandbox.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* UI drawing now properly accounts for a control's draw routine potentially mangling the current matrix.
|
||||
* UI roots now properly update when the global stylesheet is changed. They previously only did so if they had a dedicated stylesheet (which is the one case where they would be unaffected by a global sheet update.
|
||||
|
||||
|
||||
## 209.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix missed import from 209.0.0.
|
||||
|
||||
|
||||
## 209.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `replay.max_compressed_size` and `replay.max_uncompressed_size` CVars are now `long`.
|
||||
* Remove obsolete CoordinatesExtension for ToEntityCoordinates from GridUid / Vector2i.
|
||||
|
||||
### New features
|
||||
|
||||
* Add GetEntitiesOnMap / GetChildEntities to EntityLookupSystem to return components on the specified map and components with the specified parent respectively.
|
||||
* Add MaxDimension property to Box2 to return the higher of the Width or Height.
|
||||
* Add GetLocalPosition to convert ScreenCoordinates to coordinates relative to the control. Ignores window.
|
||||
* Add GlobalRect and GlobalPixelRect for controls to get their UIBox2i in screen terms.
|
||||
* Add dotted line drawing to DrawingHandleScreen.
|
||||
* `IConfigurationManager.LoadDefaultsFromTomlStream` properly does type conversions. This fixes scenarios like loading of `long` CVars.
|
||||
* Add helper methods for TileRef / Vector2i to SharedMapSystem for ToCenterCoordinates (tile center EntityCoordinates) and ToCoordinates (tile origin to EntityCoordinates).
|
||||
* Copy some of the coordinates extensions to SharedTransformSystem.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed integer overflows in replay max size calculation.
|
||||
* Explicitly capped `replay.replay_tick_batchSize` internally to avoid high values causing allocation failures.
|
||||
|
||||
### Other
|
||||
|
||||
* Important MIDI performance improvements.
|
||||
|
||||
|
||||
## 208.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
|
||||
if (args.ViewportControl == null || localPlayer == null)
|
||||
return;
|
||||
|
||||
@@ -106,8 +106,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
|
||||
|
||||
CfgManager.OnValueChanged(CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -133,13 +133,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CfgManager.UnsubValueChanged(CVars.AudioAttenuation, OnAudioAttenuation);
|
||||
CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged);
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void OnAudioPaused(EntityUid uid, AudioComponent component, ref EntityPausedEvent args)
|
||||
{
|
||||
component.Pause();
|
||||
|
||||
@@ -192,7 +192,12 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_settings["synth.midi-bank-select"].StringValue = "gm";
|
||||
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
|
||||
|
||||
_parallel.AddAndInvokeParallelCountChanged(UpdateParallelCount);
|
||||
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -201,7 +206,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread = new Thread(ThreadUpdate)
|
||||
{
|
||||
Name = "RobustToolbox MIDI Thread"
|
||||
};
|
||||
_midiThread.Start();
|
||||
|
||||
_updateJob = new MidiUpdateJob()
|
||||
@@ -219,18 +227,6 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
private void UpdateParallelCount()
|
||||
{
|
||||
if (_settings == null)
|
||||
return;
|
||||
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(_parallel.ParallelProcessCount) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(_parallel.ParallelProcessCount, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
|
||||
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
|
||||
{
|
||||
var rLevel = level switch
|
||||
|
||||
@@ -38,6 +38,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
private readonly Synth _synth;
|
||||
private readonly Sequencer _sequencer;
|
||||
private NFluidsynth.Player? _player;
|
||||
private int _playerTotalTicks;
|
||||
private MidiDriver? _driver;
|
||||
private byte _midiProgram = 1;
|
||||
private byte _midiBank = 1;
|
||||
@@ -144,7 +145,21 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
public int PlayerTotalTick
|
||||
{
|
||||
get
|
||||
{
|
||||
// GetTotalTicks is really expensive (has to iterate the entire file, not cached).
|
||||
// Slight problem with caching it ourselves: the value only becomes available when the player loads the MIDI file.
|
||||
// And that only happens after playback really starts, with the timer and synth and all that stuff.
|
||||
// So we cache it "as soon as it's available", i.e. not 0.
|
||||
// We don't care about playlists and such, so it shouldn't change anymore after.
|
||||
if (_playerTotalTicks != 0)
|
||||
return _playerTotalTicks;
|
||||
|
||||
return _playerTotalTicks = _player?.GetTotalTicks ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTick
|
||||
@@ -339,6 +354,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
return false;
|
||||
}
|
||||
|
||||
_playerTotalTicks = 0;
|
||||
_player?.Dispose();
|
||||
_player = new NFluidsynth.Player(_synth);
|
||||
_player.SetPlaybackCallback(MidiPlayerEventHandler);
|
||||
@@ -377,6 +393,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_player?.Join();
|
||||
_player?.Dispose();
|
||||
_player = null;
|
||||
_playerTotalTicks = 0;
|
||||
}
|
||||
|
||||
StopAllNotes();
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace Robust.Client
|
||||
|
||||
// Don't invoke PlayerLeaveServer if PlayerJoinedServer & GameStartedSetup hasn't been called yet.
|
||||
if (RunLevel > ClientRunLevel.Connecting)
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalSession));
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
GameStoppedReset();
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
|
||||
args.RemoveAt(0);
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalPlayer?.Session, session == null);
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
@@ -200,8 +200,7 @@ namespace Robust.Client.Console
|
||||
// When not connected to a server, you can run all local commands.
|
||||
// When connected to a server, you can only run commands according to the con group controller.
|
||||
|
||||
return _player.LocalPlayer == null
|
||||
|| _player.LocalPlayer.Session.Status <= SessionStatus.Connecting
|
||||
return _player.LocalSession is not { Status: > SessionStatus.Connecting }
|
||||
|| _conGroup.CanCommand(cmdName);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
if (_playerManager.LocalEntity is not { } controlled)
|
||||
{
|
||||
shell.WriteLine("You don't have an attached entity.");
|
||||
return;
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace Robust.Client.Debugging
|
||||
if (mapPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var playerXform) ||
|
||||
playerXform.MapID != args.MapId)
|
||||
|
||||
@@ -42,6 +42,8 @@ public sealed partial class ClientEntityManager
|
||||
var pending = PendingNetEntityStates.GetOrNew(nEntity);
|
||||
pending.Add((typeof(T), callerEntity));
|
||||
|
||||
|
||||
|
||||
return entity.Item1;
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace Robust.Client.GameObjects
|
||||
public void DispatchReceivedNetworkMsg(EntityEventArgs msg)
|
||||
{
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalSession!), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
}
|
||||
|
||||
@@ -105,12 +105,10 @@ namespace Robust.Client.GameObjects
|
||||
/// <param name="inputCmd">Input command to handle as predicted.</param>
|
||||
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
|
||||
{
|
||||
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
|
||||
|
||||
var keyFunc = _inputManager.NetworkBindMap.KeyFunctionName(inputCmd.InputFunctionId);
|
||||
|
||||
Predicted = true;
|
||||
var session = _playerManager.LocalPlayer!.Session;
|
||||
var session = _playerManager.LocalSession;
|
||||
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
|
||||
{
|
||||
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
|
||||
@@ -145,27 +143,22 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if(localPlayer is null)
|
||||
return;
|
||||
|
||||
var pent = localPlayer.ControlledEntity;
|
||||
if(pent is null)
|
||||
if (_playerManager.LocalEntity is not { } pent)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
|
||||
var pxform = Transform(pent.Value);
|
||||
var pxform = Transform(pent);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
|
||||
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
|
||||
|
||||
HandleInputCommand(localPlayer.Session, keyFunction, message);
|
||||
HandleInputCommand(_playerManager.LocalSession, keyFunction, message);
|
||||
}
|
||||
|
||||
private void OnAttachedEntityChanged(LocalPlayerAttachedEvent message)
|
||||
@@ -208,11 +201,8 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public void SetEntityContextActive()
|
||||
{
|
||||
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } controlled)
|
||||
return;
|
||||
}
|
||||
|
||||
SetEntityContextActive(_inputManager, controlled);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentInit>(OnInit);
|
||||
|
||||
_cfg.OnValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged, true);
|
||||
Subs.CVar(_cfg, CVars.RenderSpriteDirectionBias, OnBiasChanged, true);
|
||||
_sawmill = _logManager.GetSawmill("sprite");
|
||||
}
|
||||
|
||||
@@ -72,12 +72,6 @@ namespace Robust.Client.GameObjects
|
||||
QueueUpdateInert(uid, component);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_cfg.UnsubValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged);
|
||||
}
|
||||
|
||||
private void OnBiasChanged(double value)
|
||||
{
|
||||
SpriteComponent.DirectionBias = value;
|
||||
|
||||
@@ -29,10 +29,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var uiKey = ev.UiKey;
|
||||
var message = ev.Message;
|
||||
// This should probably not happen at this point, but better make extra sure!
|
||||
if (_playerManager.LocalPlayer != null)
|
||||
message.Session = _playerManager.LocalPlayer.Session;
|
||||
|
||||
message.Session = _playerManager.LocalSession!;
|
||||
message.Entity = GetNetEntity(uid);
|
||||
message.UiKey = uiKey;
|
||||
|
||||
@@ -75,8 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
boundInterface.Open();
|
||||
uiComp.OpenInterfaces[uiKey] = boundInterface;
|
||||
|
||||
var playerSession = _playerManager.LocalPlayer?.Session;
|
||||
if (playerSession != null)
|
||||
if (_playerManager.LocalSession is { } playerSession)
|
||||
{
|
||||
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
|
||||
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
|
||||
@@ -232,9 +232,9 @@ namespace Robust.Client.GameStates
|
||||
return default;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_players.LocalPlayer);
|
||||
DebugTools.Assert(_players.LocalSession != null);
|
||||
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalPlayer!.Session);
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalSession);
|
||||
_pendingSystemMessages.Enqueue((_nextInputCmdSeq, _timing.CurTick, message,
|
||||
new EntitySessionMessage<T>(evArgs, message)));
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
entity = _playerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
}
|
||||
else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity))
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -14,6 +15,79 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simialr to DrawLine but has dashes interspersed.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset from the start of the line.</param>
|
||||
/// <param name="dashSize">How long a dash is.</param>
|
||||
/// <param name="gapSize">How long the gap between dashes is.</param>
|
||||
public void DrawDottedLine(Vector2 from, Vector2 to, Color color, float offset = 0f, float dashSize = 8f, float gapSize = 2f)
|
||||
{
|
||||
var lineVector = to - from;
|
||||
|
||||
// No drawing for you.
|
||||
if (lineVector.LengthSquared() < 10f * float.Epsilon)
|
||||
return;
|
||||
|
||||
var lineAndGap = gapSize + dashSize;
|
||||
var lines = new ValueList<Vector2>();
|
||||
|
||||
// Minimum distance.
|
||||
if (lineVector.Length() < lineAndGap)
|
||||
{
|
||||
lines.Add(from);
|
||||
lines.Add(to);
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxLength = lineVector.Length();
|
||||
var normalizedLine = lineVector.Normalized();
|
||||
var dashVector = normalizedLine * dashSize;
|
||||
var gapVector = normalizedLine * gapSize;
|
||||
|
||||
var position = from;
|
||||
offset %= (dashSize + gapSize);
|
||||
var length = offset;
|
||||
var dashLength = dashSize;
|
||||
|
||||
// If offset is less than gap size then start with a gap
|
||||
// otherwise start with a partial line
|
||||
if (offset > 0f)
|
||||
{
|
||||
if (offset < gapSize)
|
||||
{
|
||||
position += normalizedLine * offset;
|
||||
length += offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
dashLength = (offset - gapSize);
|
||||
}
|
||||
}
|
||||
|
||||
while (length < maxLength)
|
||||
{
|
||||
lines.Add(position);
|
||||
|
||||
position += normalizedLine * dashLength;
|
||||
var lengthFromStart = (position - from).Length();
|
||||
|
||||
// if over length then cap the thing.
|
||||
if (lengthFromStart > maxLength)
|
||||
{
|
||||
position = to;
|
||||
}
|
||||
|
||||
lines.Add(position);
|
||||
dashLength = dashVector.Length();
|
||||
position += gapVector;
|
||||
length = (position - from).Length();
|
||||
}
|
||||
}
|
||||
|
||||
DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, color);
|
||||
}
|
||||
|
||||
public abstract void DrawRect(UIBox2 rect, Color color, bool filled = true);
|
||||
|
||||
public abstract void DrawTextureRectRegion(Texture texture, UIBox2 rect, UIBox2? subRegion = null, Color? modulate = null);
|
||||
|
||||
@@ -6,7 +6,10 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
internal interface IRenderHandle
|
||||
/// <remarks>
|
||||
/// Unstable API. Likely to break hard during renderer rewrite if you rely on it.
|
||||
/// </remarks>
|
||||
public interface IRenderHandle
|
||||
{
|
||||
DrawingHandleScreen DrawingHandleScreen { get; }
|
||||
DrawingHandleWorld DrawingHandleWorld { get; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -69,7 +69,7 @@ namespace Robust.Client.Graphics
|
||||
ShaderBlendMode? blend = null;
|
||||
if (_rawBlendMode != null)
|
||||
{
|
||||
if (!Enum.TryParse<ShaderBlendMode>(_rawBlendMode.ToUpper(), out var parsed))
|
||||
if (!Enum.TryParse<ShaderBlendMode>(_rawBlendMode, true, out var parsed))
|
||||
Logger.Error($"invalid mode: {_rawBlendMode}");
|
||||
else
|
||||
blend = parsed;
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadKeyFile(path, true);
|
||||
LoadKeyFile(path, false, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -136,7 +136,7 @@ namespace Robust.Client.Input
|
||||
|
||||
if (_resourceMan.ContentFileExists(path))
|
||||
{
|
||||
LoadKeyFile(path, false);
|
||||
LoadKeyFile(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +496,13 @@ namespace Robust.Client.Input
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LoadKeyFile(ResPath file, bool userData)
|
||||
/// <summary>
|
||||
/// Loads a keybind file, configuring keybinds.
|
||||
/// </summary>
|
||||
/// <param name="file">File to load from the content package</param>
|
||||
/// <param name="defaultRegistration">Whether or not this is a "default" keybind set. If it is, then it won't override the current configuration, only the defaults.</param>
|
||||
/// <param name="userData">Whether or not to load from the user data directory instead of the content package.</param>
|
||||
public void LoadKeyFile(ResPath file, bool defaultRegistration, bool userData = false)
|
||||
{
|
||||
TextReader reader;
|
||||
if (userData)
|
||||
@@ -526,7 +532,7 @@ namespace Robust.Client.Input
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!userData)
|
||||
if (defaultRegistration)
|
||||
{
|
||||
_defaultRegistrations.Add(reg);
|
||||
|
||||
@@ -538,11 +544,11 @@ namespace Robust.Client.Input
|
||||
}
|
||||
}
|
||||
|
||||
RegisterBinding(reg, markModified: userData);
|
||||
RegisterBinding(reg, markModified: defaultRegistration);
|
||||
}
|
||||
}
|
||||
|
||||
if (userData && mapping.TryGet("leaveEmpty", out var node))
|
||||
if (!defaultRegistration && mapping.TryGet("leaveEmpty", out var node))
|
||||
{
|
||||
var leaveEmpty = _serialization.Read<BoundKeyFunction[]>(node, notNullableOverride: true);
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ public sealed class TileEdgeOverlay : GridOverlay
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
var bounds = args.WorldBounds;
|
||||
bounds = new Box2Rotated(bounds.Box.Enlarged(1), bounds.Rotation, bounds.Origin);
|
||||
var localAABB = invMatrix.TransformBox(bounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
|
||||
@@ -495,7 +495,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
// Try to get current map.
|
||||
var map = MapId.Nullspace;
|
||||
if (EntityManager.TryGetComponent(PlayerManager.LocalPlayer?.ControlledEntity, out TransformComponent? xform))
|
||||
if (EntityManager.TryGetComponent(PlayerManager.LocalEntity, out TransformComponent? xform))
|
||||
{
|
||||
map = xform.MapID;
|
||||
}
|
||||
@@ -512,7 +512,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
private bool CurrentEraserMouseCoordinates(out EntityCoordinates coordinates)
|
||||
{
|
||||
var ent = PlayerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
var ent = PlayerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
if (ent == EntityUid.Invalid)
|
||||
{
|
||||
coordinates = new EntityCoordinates();
|
||||
@@ -640,7 +640,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
if (CurrentPermission is not {Range: > 0} ||
|
||||
!CurrentMode.RangeRequired ||
|
||||
PlayerManager.LocalPlayer?.ControlledEntity is not {Valid: true} controlled)
|
||||
PlayerManager.LocalEntity is not {Valid: true} controlled)
|
||||
return;
|
||||
|
||||
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
if (!RangeRequired)
|
||||
return true;
|
||||
var controlled = pManager.PlayerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
var controlled = pManager.PlayerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -10,14 +10,13 @@ namespace Robust.Client.Player
|
||||
|
||||
public override Filter FromEntities(Filter filter, params EntityUid[] entities)
|
||||
{
|
||||
if (_playerManager.LocalPlayer is not { } localPlayer
|
||||
|| localPlayer.Session.AttachedEntity is not {Valid: true} attachedUid)
|
||||
if (_playerManager.LocalEntity is not {Valid: true} attachedUid)
|
||||
return filter;
|
||||
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (uid == attachedUid)
|
||||
filter.AddPlayer(localPlayer.Session);
|
||||
filter.AddPlayer(_playerManager.LocalSession!);
|
||||
}
|
||||
|
||||
return filter;
|
||||
|
||||
@@ -36,12 +36,12 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
|
||||
|
||||
private void OnRecordingStarted(MappingDataNode metadata, List<object> messages)
|
||||
{
|
||||
if (_player.LocalPlayer == null)
|
||||
if (_player.LocalSession == null)
|
||||
return;
|
||||
|
||||
// Add information about the user doing the recording. This is used to set the default replay observer position
|
||||
// when playing back the replay.
|
||||
var guid = _player.LocalPlayer.UserId.UserId.ToString();
|
||||
var guid = _player.LocalUser.ToString();
|
||||
metadata[ReplayConstants.MetaKeyRecordedBy] = new ValueDataNode(guid);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,6 +226,10 @@ namespace Robust.Client.UserInterface
|
||||
/// <seealso cref="Rect"/>
|
||||
public UIBox2i PixelRect => UIBox2i.FromDimensions(PixelPosition, PixelSize);
|
||||
|
||||
public UIBox2 GlobalRect => UIBox2.FromDimensions(GlobalPosition, _size);
|
||||
|
||||
public UIBox2i GlobalPixelRect => UIBox2i.FromDimensions(GlobalPixelPosition, PixelSize);
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal alignment mode.
|
||||
/// This determines how the control should be laid out horizontally
|
||||
@@ -464,6 +468,14 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates position relative to the control.
|
||||
/// </summary>
|
||||
public Vector2 GetLocalPosition(ScreenCoordinates coordinates)
|
||||
{
|
||||
return coordinates.Position - GlobalPixelPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the layout system that this control's <see cref="Measure"/> result may have changed
|
||||
/// and must be recalculated.
|
||||
|
||||
@@ -545,6 +545,36 @@ namespace Robust.Client.UserInterface
|
||||
Draw(renderHandle.DrawingHandleScreen);
|
||||
}
|
||||
|
||||
protected internal virtual void PreRenderChildren(ref ControlRenderArguments args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected internal virtual void PostRenderChildren(ref ControlRenderArguments args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected internal virtual void RenderChildOverride(ref ControlRenderArguments args, int childIndex, Vector2i position)
|
||||
{
|
||||
RenderControl(ref args, childIndex, position);
|
||||
}
|
||||
|
||||
public ref struct ControlRenderArguments
|
||||
{
|
||||
public IRenderHandle Handle;
|
||||
public ref int Total;
|
||||
public Vector2i Position;
|
||||
public Color Modulate;
|
||||
public UIBox2i? ScissorBox;
|
||||
public ref Matrix3 CoordinateTransform;
|
||||
}
|
||||
|
||||
protected void RenderControl(ref ControlRenderArguments args, int childIndex, Vector2i position)
|
||||
{
|
||||
UserInterfaceManagerInternal.RenderControl(args.Handle, ref args.Total, GetChild(childIndex), position, args.Modulate, args.ScissorBox, args.CoordinateTransform);
|
||||
}
|
||||
|
||||
public void UpdateDraw()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -37,8 +38,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
get => _group;
|
||||
set
|
||||
{
|
||||
if (value?.InternalButtons.Contains(this) ?? false)
|
||||
return; // No work to do.
|
||||
// Remove from old group.
|
||||
_group?.Buttons.Remove(this);
|
||||
_group?.InternalButtons.Remove(this);
|
||||
|
||||
_group = value;
|
||||
|
||||
@@ -47,11 +50,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
value.Buttons.Add(this);
|
||||
value.InternalButtons.Add(this);
|
||||
ToggleMode = true;
|
||||
|
||||
// Set us to pressed if we're the first button.
|
||||
Pressed = value.Buttons.Count == 0;
|
||||
// Set us to pressed if we're the first button. Doesn't go through the setter to avoid setting off our own error check.
|
||||
_pressed = value.InternalButtons.Count == 1;
|
||||
DrawModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +330,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var button in _group.Buttons)
|
||||
foreach (var button in _group.InternalButtons)
|
||||
{
|
||||
if (button != this && button.Pressed)
|
||||
{
|
||||
@@ -440,6 +444,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </remarks>
|
||||
public sealed class ButtonGroup
|
||||
{
|
||||
internal readonly List<BaseButton> Buttons = new();
|
||||
internal readonly List<BaseButton> InternalButtons = new();
|
||||
public IReadOnlyList<BaseButton> Buttons => InternalButtons;
|
||||
|
||||
public BaseButton? Pressed => InternalButtons.FirstOrDefault(x => x.Pressed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
@@ -9,6 +9,9 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a scrollable list of items in a user interface.
|
||||
/// </summary>
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
[Virtual]
|
||||
@@ -29,6 +32,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePropertySelectedItemBackground = "selected-item-background";
|
||||
public const string StylePropertyDisabledItemBackground = "disabled-item-background";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ItemSeparation of individual list items
|
||||
/// </summary>
|
||||
public int ItemSeparation { get; set; } = 0; // Default value is 0px
|
||||
public int Count => _itemList.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
@@ -68,8 +75,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
itemHeight += ActualItemBackground.MinimumSize.Y * UIScale;
|
||||
|
||||
_totalContentHeight += (int)Math.Ceiling(itemHeight);
|
||||
_totalContentHeight += ItemSeparation;
|
||||
}
|
||||
|
||||
//Remove unneeded ItemSeparation on last item.
|
||||
_totalContentHeight -= ItemSeparation;
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
_updateScrollbarVisibility();
|
||||
}
|
||||
@@ -390,6 +399,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
offset += itemHeight;
|
||||
|
||||
// Add a ItemSeparation at the bottom of each item.
|
||||
offset += ItemSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +411,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var color = ActualFontColor;
|
||||
var offsetY = (int) (box.Height - font.GetHeight(UIScale)) / 2;
|
||||
var baseLine = new Vector2i(0, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
|
||||
var baseLine = new Vector2i(5, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
|
||||
|
||||
foreach (var rune in text.EnumerateRunes())
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public class OptionButton : ContainerButton
|
||||
{
|
||||
public const string StyleClassOptionButton = "optionButton";
|
||||
public const string StyleClassPopup = "optionButtonPopup";
|
||||
public const string StyleClassOptionTriangle = "optionTriangle";
|
||||
public readonly ScrollContainer OptionsScroll;
|
||||
|
||||
@@ -74,7 +75,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
_popup = new Popup()
|
||||
{
|
||||
Children = { OptionsScroll }
|
||||
Children = { new PanelContainer(), OptionsScroll },
|
||||
StyleClasses = { StyleClassPopup }
|
||||
};
|
||||
_popup.OnPopupHide += OnPopupHide;
|
||||
|
||||
@@ -99,6 +101,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
AddItem(label, id);
|
||||
}
|
||||
|
||||
public virtual void ButtonOverride(Button button)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void AddItem(string label, int? id = null)
|
||||
{
|
||||
if (id == null)
|
||||
@@ -132,6 +139,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
Select(0);
|
||||
}
|
||||
|
||||
ButtonOverride(button);
|
||||
}
|
||||
|
||||
private void TogglePopup(bool show)
|
||||
@@ -139,6 +148,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (show)
|
||||
{
|
||||
var globalPos = GlobalPosition;
|
||||
globalPos.Y += Size.Y + 1; // Place it below us, with a safety margin.
|
||||
globalPos.Y -= Margin.SumVertical;
|
||||
OptionsScroll.Measure(Window?.Size ?? Vector2Helpers.Infinity);
|
||||
var (minX, minY) = OptionsScroll.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, new Vector2(Math.Max(minX, Width), minY));
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
var style = _getStyleBox();
|
||||
var style = GetStyleBox();
|
||||
style?.Draw(handle, PixelSizeBox, UIScale);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var styleSize = _getStyleBox()?.MinimumSize ?? Vector2.Zero;
|
||||
var styleSize = GetStyleBox()?.MinimumSize ?? Vector2.Zero;
|
||||
var measureSize = Vector2.Max(availableSize - styleSize, Vector2.Zero);
|
||||
var childSize = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var ourSize = UIBox2.FromDimensions(Vector2.Zero, finalSize);
|
||||
var contentBox = _getStyleBox()?.GetContentBox(ourSize, 1) ?? ourSize;
|
||||
var contentBox = GetStyleBox()?.GetContentBox(ourSize, 1) ?? ourSize;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
private StyleBox? _getStyleBox()
|
||||
protected StyleBox? GetStyleBox()
|
||||
{
|
||||
if (PanelOverride != null)
|
||||
{
|
||||
|
||||
@@ -61,6 +61,9 @@ namespace Robust.Client.UserInterface
|
||||
Vector2? CalcRelativeMousePositionFor(Control control, ScreenCoordinates mousePos);
|
||||
|
||||
Color GetMainClearColor();
|
||||
|
||||
void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox, Matrix3 coordinateTransform);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,9 +132,10 @@ internal sealed partial class UserInterfaceManager
|
||||
try
|
||||
{
|
||||
var total = 0;
|
||||
_render(renderHandle, ref total, root, Vector2i.Zero, Color.White, null);
|
||||
var drawingHandle = renderHandle.DrawingHandleScreen;
|
||||
drawingHandle.SetTransform(Vector2.Zero, Angle.Zero, Vector2.One);
|
||||
drawingHandle.SetTransform(Matrix3.Identity);
|
||||
RenderControl(renderHandle, ref total, root, Vector2i.Zero, Color.White, null, Matrix3.Identity);
|
||||
drawingHandle.SetTransform(Matrix3.Identity);
|
||||
OnPostDrawUIRoot?.Invoke(new PostDrawUIRootEventArgs(root, drawingHandle));
|
||||
|
||||
_prof.WriteValue("Controls rendered", ProfData.Int32(total));
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
foreach (var root in _roots)
|
||||
{
|
||||
if (root.Stylesheet != null)
|
||||
if (root.Stylesheet == null)
|
||||
{
|
||||
root.StylesheetUpdateRecursive();
|
||||
}
|
||||
@@ -329,8 +329,8 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private void _render(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox)
|
||||
public void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox, Matrix3 coordinateTransform)
|
||||
{
|
||||
if (!control.Visible)
|
||||
{
|
||||
@@ -377,7 +377,10 @@ namespace Robust.Client.UserInterface
|
||||
total += 1;
|
||||
|
||||
var handle = renderHandle.DrawingHandleScreen;
|
||||
handle.SetTransform(position, Angle.Zero, Vector2.One);
|
||||
var oldXform = handle.GetTransform();
|
||||
var xform = oldXform;
|
||||
xform.Multiply(Matrix3.CreateTransform(position, Angle.Zero, Vector2.One));
|
||||
handle.SetTransform(xform);
|
||||
modulate *= control.Modulate;
|
||||
|
||||
if (_rendering || control.AlwaysRender)
|
||||
@@ -389,16 +392,32 @@ namespace Robust.Client.UserInterface
|
||||
handle.Modulate = oldMod;
|
||||
handle.UseShader(null);
|
||||
}
|
||||
handle.SetTransform(oldXform);
|
||||
var args = new Control.ControlRenderArguments()
|
||||
{
|
||||
Handle = renderHandle,
|
||||
Total = ref total,
|
||||
Modulate = modulate,
|
||||
ScissorBox = scissorRegion,
|
||||
CoordinateTransform = ref coordinateTransform
|
||||
};
|
||||
|
||||
control.PreRenderChildren(ref args);
|
||||
|
||||
foreach (var child in control.Children)
|
||||
{
|
||||
_render(renderHandle, ref total, child, position + child.PixelPosition, modulate, scissorRegion);
|
||||
var pos = position + (Vector2i) coordinateTransform.Transform(child.PixelPosition);
|
||||
control.RenderChildOverride(ref args, child.GetPositionInParent(), pos);
|
||||
}
|
||||
|
||||
control.PostRenderChildren(ref args);
|
||||
|
||||
if (clip)
|
||||
{
|
||||
renderHandle.SetScissor(scissorBox);
|
||||
}
|
||||
|
||||
handle.SetTransform(oldXform);
|
||||
}
|
||||
|
||||
public Color GetMainClearColor() => RootControl.ActualBgColor;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Robust.Server.GameObjects
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MapGridComponent, EmptyGridEvent>(HandleGridEmpty);
|
||||
|
||||
_cfg.OnValueChanged(CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
|
||||
Subs.CVar(_cfg, CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
@@ -64,13 +64,6 @@ namespace Robust.Server.GameObjects
|
||||
return !(grid.GetAllTiles().Any());
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_cfg.UnsubValueChanged(CVars.GameDeleteEmptyGrids, SetGridDeletion);
|
||||
}
|
||||
|
||||
private void HandleGridEmpty(EntityUid uid, MapGridComponent component, EmptyGridEvent args)
|
||||
{
|
||||
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
|
||||
|
||||
@@ -20,7 +20,8 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
base.Initialize();
|
||||
LoadMetricCVar();
|
||||
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
|
||||
|
||||
Subs.CVar(_configurationManager, CVars.MetricsEnabled, _ => LoadMetricCVar());
|
||||
}
|
||||
|
||||
private void LoadMetricCVar()
|
||||
|
||||
@@ -126,11 +126,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_transform.OnGlobalMoveEvent += OnEntityMove;
|
||||
|
||||
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
|
||||
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetLowLodRange, OnLodChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetPvsAsync, OnAsyncChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
|
||||
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetLowLodRange, OnLodChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetForceAckThreshold, OnForceAckChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsAsync, OnAsyncChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsCompressLevel, ResetParallelism, true);
|
||||
|
||||
_serverGameStateManager.ClientAck += OnClientAck;
|
||||
_serverGameStateManager.ClientRequestFull += OnClientRequestFull;
|
||||
@@ -138,7 +139,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
InitializeDirty();
|
||||
|
||||
_parallelMgr.ParallelCountChanged += ResetParallelism;
|
||||
_configManager.OnValueChanged(CVars.NetPvsCompressLevel, ResetParallelism, true);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -148,10 +148,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_transform.OnGlobalMoveEvent -= OnEntityMove;
|
||||
|
||||
_configManager.UnsubValueChanged(CVars.NetPVS, SetPvs);
|
||||
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged);
|
||||
_configManager.UnsubValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged);
|
||||
_configManager.UnsubValueChanged(CVars.NetPvsCompressLevel, ResetParallelism);
|
||||
_parallelMgr.ParallelCountChanged -= ResetParallelism;
|
||||
|
||||
_serverGameStateManager.ClientAck -= OnClientAck;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Robust.Server.Physics
|
||||
SubscribeNetworkEvent<RequestGridNodesMessage>(OnDebugRequest);
|
||||
SubscribeNetworkEvent<StopGridNodesMessage>(OnDebugStopRequest);
|
||||
|
||||
_cfg.OnValueChanged(CVars.GridSplitting, SetSplitAllowed, true);
|
||||
Subs.CVar(_cfg, CVars.GridSplitting, SetSplitAllowed, true);
|
||||
}
|
||||
|
||||
private void SetSplitAllowed(bool value) => SplitAllowed = value;
|
||||
@@ -73,7 +73,6 @@ namespace Robust.Server.Physics
|
||||
{
|
||||
base.Shutdown();
|
||||
_subscribedSessions.Clear();
|
||||
_cfg.UnsubValueChanged(CVars.GridSplitting, SetSplitAllowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -75,6 +75,15 @@ namespace Robust.Shared.Maths
|
||||
get => new(Width, Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the highest of width or height.
|
||||
/// </summary>
|
||||
public readonly float MaxDimension
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => MathF.Max(Height, Width);
|
||||
}
|
||||
|
||||
public readonly Vector2 Center
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -177,13 +186,13 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 Intersect(in Box2 other)
|
||||
{
|
||||
var ourLeftBottom = new System.Numerics.Vector2(Left, Bottom);
|
||||
var ourRightTop = new System.Numerics.Vector2(Right, Top);
|
||||
var otherLeftBottom = new System.Numerics.Vector2(other.Left, other.Bottom);
|
||||
var otherRightTop = new System.Numerics.Vector2(other.Right, other.Top);
|
||||
var ourLeftBottom = new Vector2(Left, Bottom);
|
||||
var ourRightTop = new Vector2(Right, Top);
|
||||
var otherLeftBottom = new Vector2(other.Left, other.Bottom);
|
||||
var otherRightTop = new Vector2(other.Right, other.Top);
|
||||
|
||||
var max = System.Numerics.Vector2.Max(ourLeftBottom, otherLeftBottom);
|
||||
var min = System.Numerics.Vector2.Min(ourRightTop, otherRightTop);
|
||||
var max = Vector2.Max(ourLeftBottom, otherLeftBottom);
|
||||
var min = Vector2.Min(ourRightTop, otherRightTop);
|
||||
|
||||
if (max.X <= min.X && max.Y <= min.Y)
|
||||
return new Box2(max.X, max.Y, min.X, min.Y);
|
||||
@@ -210,13 +219,13 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 Union(in Box2 other)
|
||||
{
|
||||
var ourLeftBottom = new System.Numerics.Vector2(Left, Bottom);
|
||||
var otherLeftBottom = new System.Numerics.Vector2(other.Left, other.Bottom);
|
||||
var ourRightTop = new System.Numerics.Vector2(Right, Top);
|
||||
var otherRightTop = new System.Numerics.Vector2(other.Right, other.Top);
|
||||
var ourLeftBottom = new Vector2(Left, Bottom);
|
||||
var otherLeftBottom = new Vector2(other.Left, other.Bottom);
|
||||
var ourRightTop = new Vector2(Right, Top);
|
||||
var otherRightTop = new Vector2(other.Right, other.Top);
|
||||
|
||||
var leftBottom = System.Numerics.Vector2.Min(ourLeftBottom, otherLeftBottom);
|
||||
var rightTop = System.Numerics.Vector2.Max(ourRightTop, otherRightTop);
|
||||
var leftBottom = Vector2.Min(ourLeftBottom, otherLeftBottom);
|
||||
var rightTop = Vector2.Max(ourRightTop, otherRightTop);
|
||||
|
||||
if (leftBottom.X <= rightTop.X && leftBottom.Y <= rightTop.Y)
|
||||
return new Box2(leftBottom.X, leftBottom.Y, rightTop.X, rightTop.Y);
|
||||
@@ -396,11 +405,11 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public static Box2 Union(in Vector2 a, in Vector2 b)
|
||||
{
|
||||
var vecA = new System.Numerics.Vector2(a.X, a.Y);
|
||||
var vecB = new System.Numerics.Vector2(b.X, b.Y);
|
||||
var vecA = new Vector2(a.X, a.Y);
|
||||
var vecB = new Vector2(b.X, b.Y);
|
||||
|
||||
var min = System.Numerics.Vector2.Min(vecA, vecB);
|
||||
var max = System.Numerics.Vector2.Max(vecA, vecB);
|
||||
var min = Vector2.Min(vecA, vecB);
|
||||
var max = Vector2.Max(vecA, vecB);
|
||||
|
||||
return new Box2(min.X, min.Y, max.X, max.Y);
|
||||
}
|
||||
@@ -412,12 +421,12 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 ExtendToContain(Vector2 vec)
|
||||
{
|
||||
var leftBottom = new System.Numerics.Vector2(Left, Bottom);
|
||||
var rightTop = new System.Numerics.Vector2(Right, Top);
|
||||
var vector = new System.Numerics.Vector2(vec.X, vec.Y);
|
||||
var leftBottom = new Vector2(Left, Bottom);
|
||||
var rightTop = new Vector2(Right, Top);
|
||||
var vector = new Vector2(vec.X, vec.Y);
|
||||
|
||||
var min = System.Numerics.Vector2.Min(vector, leftBottom);
|
||||
var max = System.Numerics.Vector2.Max(vector, rightTop);
|
||||
var min = Vector2.Min(vector, leftBottom);
|
||||
var max = Vector2.Max(vector, rightTop);
|
||||
|
||||
return new Box2(min.X, min.Y, max.X, max.Y);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,229 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.Maths;
|
||||
|
||||
// Reference: https://easings.net/
|
||||
|
||||
internal static class Easings
|
||||
/// <summary>
|
||||
/// A static class for computing easings for animations.
|
||||
/// The parameter "p" is the absolute progress of the animation between 0 and 1.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class Easings
|
||||
{
|
||||
#region Trig
|
||||
|
||||
public static float InSine(float p)
|
||||
{
|
||||
return 1.0f - MathF.Cos(p * MathF.PI / 2.0f);
|
||||
}
|
||||
|
||||
public static float OutSine(float p)
|
||||
{
|
||||
return MathF.Sin(p * MathF.PI / 2);
|
||||
}
|
||||
|
||||
public static float InOutSine(float p)
|
||||
{
|
||||
return -(MathF.Cos(MathF.PI * p) - 1.0f) / 2.0f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Polynomial
|
||||
|
||||
public static float InQuad(float p)
|
||||
{
|
||||
return p * p;
|
||||
}
|
||||
|
||||
public static float OutQuad(float p)
|
||||
{
|
||||
return 1 - (1 - p) * (1 - p);
|
||||
}
|
||||
|
||||
public static float InOutQuad(float p)
|
||||
{
|
||||
return p < 0.5 ? 2 * p * p : 1 - MathF.Pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
public static float InCubic(float p)
|
||||
{
|
||||
return p * p * p;
|
||||
}
|
||||
|
||||
public static float OutCubic(float p)
|
||||
{
|
||||
return 1 - MathF.Pow(1 - p, 3);
|
||||
}
|
||||
|
||||
public static float InOutCubic(float p)
|
||||
{
|
||||
return p < 0.5 ? 4 * p * p * p : 1 - MathF.Pow(-2 * p + 2, 3) / 2;
|
||||
}
|
||||
|
||||
public static float InQuart(float p)
|
||||
{
|
||||
return p * p * p * p;
|
||||
}
|
||||
|
||||
public static float OutQuart(float p)
|
||||
{
|
||||
return 1 - MathF.Pow(1 - p, 4);
|
||||
}
|
||||
|
||||
public static float InOutQuart(float p)
|
||||
{
|
||||
return p < 0.5 ? 8 * p * p * p * p : 1 - MathF.Pow(-2 * p + 2, 4) / 2;
|
||||
}
|
||||
|
||||
public static float InQuint(float p)
|
||||
{
|
||||
return p * p * p * p * p;
|
||||
}
|
||||
|
||||
public static float OutQuint(float p)
|
||||
{
|
||||
return 1 - MathF.Pow(1 - p, 5);
|
||||
}
|
||||
|
||||
public static float InOutQuint(float p)
|
||||
{
|
||||
return p < 0.5f ? (16 * p * p * p * p * p) : 1 - MathF.Pow(-2 * p + 2, 5) / 2;
|
||||
return p < 0.5f ? 16 * p * p * p * p * p : 1 - MathF.Pow(-2 * p + 2, 5) / 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Other
|
||||
|
||||
public static float InExpo(float p)
|
||||
{
|
||||
return p == 0 ? 0 : MathF.Pow(2, 10 * p - 10);
|
||||
}
|
||||
|
||||
public static float OutExpo(float p)
|
||||
{
|
||||
return Math.Abs(p - 1) < 0.0001f ? 1 : 1 - MathF.Pow(2, -10 * p);
|
||||
}
|
||||
|
||||
public static float InOutExpo(float p)
|
||||
{
|
||||
return p == 0.0f
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: p < 0.5f
|
||||
? MathF.Pow(2, 20 * p - 10) / 2
|
||||
: (2 - MathF.Pow(2, -20 * p + 10)) / 2;
|
||||
}
|
||||
|
||||
public static float InCirc(float p)
|
||||
{
|
||||
return 1 - MathF.Sqrt(1 - MathF.Pow(p, 2));
|
||||
}
|
||||
|
||||
public static float OutCirc(float p)
|
||||
{
|
||||
return MathF.Sqrt(1 - MathF.Pow(p - 1, 2));
|
||||
}
|
||||
|
||||
public static float InOutCirc(float p)
|
||||
{
|
||||
return p < 0.5
|
||||
? (1 - MathF.Sqrt(1 - MathF.Pow(2 * p, 2))) / 2
|
||||
: (MathF.Sqrt(1 - MathF.Pow(-2 * p + 2, 2)) + 1) / 2;
|
||||
}
|
||||
|
||||
public static float InBack(float p)
|
||||
{
|
||||
var c1 = 1.70158f;
|
||||
var c3 = c1 + 1;
|
||||
|
||||
return c3 * p * p * p - c1 * p * p;
|
||||
}
|
||||
|
||||
public static float OutBack(float p)
|
||||
{
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1;
|
||||
|
||||
return 1 + c3 * MathF.Pow(p - 1, 3) + c1 * MathF.Pow(p - 1, 2);
|
||||
}
|
||||
|
||||
public static float InOutBack(float p)
|
||||
{
|
||||
const float c1 = 1.70158f;
|
||||
const float c2 = c1 * 1.525f;
|
||||
|
||||
return p < 0.5
|
||||
? MathF.Pow(2 * p, 2) * ((c2 + 1) * 2 * p - c2) / 2
|
||||
: (MathF.Pow(2 * p - 2, 2) * ((c2 + 1) * (p * 2 - 2) + c2) + 2) / 2;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// elastic in, not "inelastic"
|
||||
/// </remarks>
|
||||
public static float InElastic(float p)
|
||||
{
|
||||
const float c4 = 2 * MathF.PI / 3;
|
||||
|
||||
return p == 0
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: -MathF.Pow(2, 10 * p - 10) * MathF.Sin((p * 10 - 10.75f) * c4);
|
||||
}
|
||||
|
||||
public static float OutElastic(float p)
|
||||
{
|
||||
const float c4 = 2.0f * MathF.PI / 3.0f;
|
||||
|
||||
return p == 0
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: MathF.Pow(2, -10 * p) * MathF.Sin((p * 10.0f - 0.75f) * c4) + 1.0f;
|
||||
}
|
||||
|
||||
public static float InOutElastic(float p)
|
||||
{
|
||||
const float c5 = 2.0f * MathF.PI / 4.5f;
|
||||
|
||||
return p == 0
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: p < 0.5
|
||||
? -(MathF.Pow(2, 20 * p - 10) * MathF.Sin((20.0f * p - 11.125f) * c5)) / 2.0f
|
||||
: MathF.Pow(2, -20.0f * p + 10.0f) * MathF.Sin((20.0f * p - 11.125f) * c5) / 2.0f + 1.0f;
|
||||
}
|
||||
|
||||
public static float InBounce(float p)
|
||||
{
|
||||
return 1 - OutBounce(1 - p);
|
||||
}
|
||||
|
||||
public static float OutBounce(float p)
|
||||
{
|
||||
const float n1 = 7.5625f;
|
||||
const float d1 = 2.75f;
|
||||
|
||||
if (p < 1 / d1) return n1 * p * p;
|
||||
|
||||
if (p < 2 / d1) return n1 * (p -= 1.5f / d1) * p + 0.75f;
|
||||
|
||||
if (p < 2.5 / d1) return n1 * (p -= 2.25f / d1) * p + 0.9375f;
|
||||
|
||||
return n1 * (p -= 2.625f / d1) * p + 0.984375f;
|
||||
}
|
||||
|
||||
public static float InOutBounce(float p)
|
||||
{
|
||||
return p < 0.5
|
||||
? (1 - OutBounce(1 - 2 * p)) / 2
|
||||
: (1 + OutBounce(2 * p - 1)) / 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -50,17 +50,11 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
base.Initialize();
|
||||
InitializeEffect();
|
||||
ZOffset = CfgManager.GetCVar(CVars.AudioZOffset);
|
||||
CfgManager.OnValueChanged(CVars.AudioZOffset, SetZOffset);
|
||||
Subs.CVar(CfgManager, CVars.AudioZOffset, SetZOffset);
|
||||
SubscribeLocalEvent<AudioComponent, ComponentGetStateAttemptEvent>(OnAudioGetStateAttempt);
|
||||
SubscribeLocalEvent<AudioComponent, EntityUnpausedEvent>(OnAudioUnpaused);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
CfgManager.UnsubValueChanged(CVars.AudioZOffset, SetZOffset);
|
||||
}
|
||||
|
||||
protected void SetZOffset(float value)
|
||||
{
|
||||
ZOffset = value;
|
||||
|
||||
@@ -1239,7 +1239,7 @@ namespace Robust.Shared
|
||||
/// Enable Discord rich presence integration.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DiscordEnabled =
|
||||
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
|
||||
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<string> DiscordRichPresenceMainIconId =
|
||||
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.SERVER | CVar.REPLICATED);
|
||||
@@ -1323,6 +1323,16 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MidiVolume =
|
||||
CVarDef.Create("midi.volume", 0.50f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Controls amount of CPU cores and (by extension) polyphony for Fluidsynth.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You probably don't want to set this to be multithreaded, the way Fluidsynth's multithreading works is
|
||||
/// probably worse-than-nothing for Robust's usage.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> MidiParallelism =
|
||||
CVarDef.Create("midi.parallelism", 1, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* HUB
|
||||
* CVars related to public master server hub
|
||||
@@ -1487,14 +1497,14 @@ namespace Robust.Shared
|
||||
/// <summary>
|
||||
/// Maximum compressed size of a replay recording (in kilobytes) before recording automatically stops.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ReplayMaxCompressedSize = CVarDef.Create("replay.max_compressed_size",
|
||||
1024 * 256, CVar.ARCHIVE);
|
||||
public static readonly CVarDef<long> ReplayMaxCompressedSize = CVarDef.Create("replay.max_compressed_size",
|
||||
1024L * 256, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum uncompressed size of a replay recording (in kilobytes) before recording automatically stops.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size",
|
||||
1024 * 1024, CVar.ARCHIVE);
|
||||
public static readonly CVarDef<long> ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size",
|
||||
1024L * 1024, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed size of individual files created by the replay (in kilobytes), where each file contains data
|
||||
|
||||
@@ -113,10 +113,46 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
var loaded = new HashSet<string>();
|
||||
|
||||
foreach (var (cvar, value) in ParseCVarValuesFromToml(stream))
|
||||
var callbackEvents = new ValueList<ValueChangedInvoke>();
|
||||
|
||||
// Ensure callbacks are raised OUTSIDE the write lock.
|
||||
using (Lock.WriteGuard())
|
||||
{
|
||||
loaded.Add(cvar);
|
||||
OverrideDefault(cvar, value);
|
||||
foreach (var (cVarName, value) in ParseCVarValuesFromToml(stream))
|
||||
{
|
||||
if (!_configVars.TryGetValue(cVarName, out var cVar) || !cVar.Registered)
|
||||
{
|
||||
_sawmill.Error($"Trying to set unregistered variable '{cVarName}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
var convertedValue = value;
|
||||
if (!cVar.DefaultValue.GetType().IsEnum && cVar.DefaultValue.GetType() != value.GetType())
|
||||
{
|
||||
try
|
||||
{
|
||||
convertedValue = ConvertToCVarType(value, cVar.DefaultValue.GetType());
|
||||
}
|
||||
catch
|
||||
{
|
||||
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.DefaultValue.GetType()}. Toml type: {value.GetType()}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cVar.DefaultValue = value;
|
||||
|
||||
if (cVar.OverrideValue == null && cVar.Value == null)
|
||||
{
|
||||
if (SetupInvokeValueChanged(cVar, convertedValue) is { } invoke)
|
||||
callbackEvents.Add(invoke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var callback in callbackEvents)
|
||||
{
|
||||
InvokeValueChanged(callback);
|
||||
}
|
||||
|
||||
return loaded;
|
||||
@@ -303,8 +339,7 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
try
|
||||
{
|
||||
// try convert thing like int to float.
|
||||
cVar.Value = Convert.ChangeType(cVar.Value, type);
|
||||
cVar.Value = ConvertToCVarType(cVar.Value, type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -711,6 +746,26 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to convert a compatible value to the actual registration type of a CVar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When CVars are parsed from TOML, their in-code type is not known.
|
||||
/// This function does the necessary conversions from e.g. int to long.
|
||||
/// </remarks>
|
||||
/// <param name="value">
|
||||
/// The value to convert.
|
||||
/// This must be a simple type like strings or integers.
|
||||
/// </param>
|
||||
/// <param name="cVar">
|
||||
/// The registration type of the CVar.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private static object ConvertToCVarType(object value, Type cVar)
|
||||
{
|
||||
return Convert.ChangeType(value, cVar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the data for a single configuration variable.
|
||||
/// </summary>
|
||||
|
||||
@@ -73,6 +73,9 @@ WhitelistedNamespaces:
|
||||
# * The API is not *relevant* to content. e.g. System.Type.IsAnsiClass.
|
||||
# * I am lazy these API lists are huge dude.
|
||||
Types:
|
||||
Avalonia.Metadata:
|
||||
XmlnsDefinitionAttribute:
|
||||
All: True
|
||||
NetSerializer:
|
||||
NetListAsArray`1:
|
||||
Fields:
|
||||
@@ -626,6 +629,7 @@ Types:
|
||||
- "System.Text.Encoding get_UTF7()"
|
||||
- "System.Text.Encoding get_UTF8()"
|
||||
- "System.Text.Encoding get_UTF32()"
|
||||
- "System.Text.Encoding GetEncoding(string)"
|
||||
NormalizationForm: { } # Enum
|
||||
Rune: { All: True }
|
||||
StringBuilder:
|
||||
@@ -716,6 +720,9 @@ Types:
|
||||
ChunkEnumerator: { All: True }
|
||||
AppendInterpolatedStringHandler: { All: True }
|
||||
StringRuneEnumerator: { All: True }
|
||||
System.Text.Unicode:
|
||||
UnicodeRange: { All: True }
|
||||
UnicodeRanges: { All: True }
|
||||
System.Threading.Tasks:
|
||||
Task: { All: True }
|
||||
Task`1: { All: True }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract partial class EntitySystem
|
||||
{
|
||||
private List<SubBase>? _subscriptions;
|
||||
private ValueList<SubBase> _subscriptions;
|
||||
|
||||
/// <summary>
|
||||
/// A handle to allow subscription on this entity system's behalf.
|
||||
@@ -84,7 +84,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(src));
|
||||
}
|
||||
|
||||
@@ -96,7 +95,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(src));
|
||||
}
|
||||
|
||||
@@ -108,7 +106,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
|
||||
}
|
||||
|
||||
@@ -122,7 +119,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
@@ -134,7 +130,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
@@ -146,21 +141,17 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
private void ShutdownSubscriptions()
|
||||
{
|
||||
if (_subscriptions == null)
|
||||
return;
|
||||
|
||||
foreach (var sub in _subscriptions)
|
||||
{
|
||||
sub.Unsubscribe(this, EntityManager.EventBus);
|
||||
}
|
||||
|
||||
_subscriptions = null;
|
||||
_subscriptions = default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -232,6 +223,19 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an action to be ran when this entity system is shut down.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used by extension methods for <see cref="Subscriptions"/>
|
||||
/// to unsubscribe from from external sources such as CVars.
|
||||
/// </remarks>
|
||||
/// <param name="action">An action to be ran when the entity system is shut down.</param>
|
||||
public void RegisterUnsubscription(Action action)
|
||||
{
|
||||
System._subscriptions.Add(new SubAction(action));
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class SubBase
|
||||
@@ -261,5 +265,13 @@ namespace Robust.Shared.GameObjects
|
||||
bus.UnsubscribeLocalEvent<TComp, TBase>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SubAction(Action action) : SubBase
|
||||
{
|
||||
public override void Unsubscribe(EntitySystem sys, IEventBus bus)
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -41,8 +42,16 @@ namespace Robust.Shared.GameObjects
|
||||
name = name.Substring(0, name.Length - "System".Length);
|
||||
|
||||
// Convert CamelCase to snake_case
|
||||
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
|
||||
name = name.Trim('_');
|
||||
// Ignore if all uppercase, assume acronym (e.g. NPC or HTN)
|
||||
if (name.All(char.IsUpper))
|
||||
{
|
||||
name = name.ToLower(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
|
||||
name = name.Trim('_');
|
||||
}
|
||||
|
||||
return $"system.{name}";
|
||||
}
|
||||
|
||||
130
Robust.Shared/GameObjects/EntitySystemSubscriptionExt.cs
Normal file
130
Robust.Shared/GameObjects/EntitySystemSubscriptionExt.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Extra subscription helpers for <see cref="EntitySystem"/> that are not part of the core entity system behavior.
|
||||
/// </summary>
|
||||
public static class EntitySystemSubscriptionExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="name">The name of the CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately inw this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
string name,
|
||||
Action<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(name, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(name, onValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="cVar">The CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<T> cVar,
|
||||
Action<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(cVar, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(cVar, onValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="name">The name of the CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
string name,
|
||||
CVarChanged<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(name, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(name, onValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="cVar">The CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<T> cVar,
|
||||
CVarChanged<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(cVar, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(cVar, onValueChanged));
|
||||
}
|
||||
}
|
||||
@@ -703,16 +703,75 @@ public sealed partial class EntityLookupSystem
|
||||
GetEntitiesIntersecting(mapId, shape, transform, entities, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified map.
|
||||
/// </summary>
|
||||
public void GetEntitiesOnMap<TComp1>(MapId mapId, HashSet<Entity<TComp1>> entities) where TComp1 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
public void GetEntitiesOnMap<TComp1, TComp2>(MapId mapId, HashSet<Entity<TComp1, TComp2>> entities)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TComp2, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var comp2, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp, comp2));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly record struct GridQueryCompState<T>(
|
||||
HashSet<Entity<IComponent>> Intersecting,
|
||||
IPhysShape Shape,
|
||||
Transform Transform,
|
||||
EntityLookupSystem Lookup,
|
||||
LookupFlags Flags,
|
||||
EntityQuery<T> Query
|
||||
) where T : IComponent;
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
public void GetChildEntities<TComp1>(EntityUid parentUid, HashSet<Entity<TComp1>> entities) where TComp1 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
if (xform.ParentUid != parentUid)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
public void GetChildEntities<TComp1, TComp2>(EntityUid parentUid, HashSet<Entity<TComp1, TComp2>> entities)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TComp2, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var comp2, out var xform))
|
||||
{
|
||||
if (xform.ParentUid != parentUid)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp, comp2));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct GridQueryState<T>(
|
||||
HashSet<Entity<T>> Intersecting,
|
||||
|
||||
@@ -36,8 +36,8 @@ namespace Robust.Shared.GameObjects
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
|
||||
|
||||
_cfg.OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
|
||||
_cfg.OnValueChanged(CVars.GridFixtureEnlargement, SetEnlargement, true);
|
||||
Subs.CVar(_cfg, CVars.GenerateGridFixtures, SetEnabled, true);
|
||||
Subs.CVar(_cfg, CVars.GridFixtureEnlargement, SetEnlargement, true);
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
|
||||
SubscribeLocalEvent<RegenerateGridBoundsEvent>(OnGridBoundsRegenerate);
|
||||
@@ -58,14 +58,6 @@ namespace Robust.Shared.GameObjects
|
||||
_map.RegenerateCollision(ev.EntityUid, grid, _map.GetMapChunks(ev.EntityUid, grid).Values.ToHashSet());
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_cfg.UnsubValueChanged(CVars.GenerateGridFixtures, SetEnabled);
|
||||
_cfg.UnsubValueChanged(CVars.GridFixtureEnlargement, SetEnlargement);
|
||||
}
|
||||
|
||||
private void SetEnabled(bool value) => _enabled = value;
|
||||
|
||||
private void SetEnlargement(float value) => _fixtureEnlargement = value;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedMapSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// If the supplied coordinates intersects a grid will align with the tile center, otherwise returns the coordinates.
|
||||
/// </summary>
|
||||
/// <param name="coordinates"></param>
|
||||
/// <returns></returns>
|
||||
[Pure]
|
||||
public EntityCoordinates AlignToGrid(EntityCoordinates coordinates)
|
||||
{
|
||||
// Check if the parent is already a grid.
|
||||
if (_gridQuery.TryGetComponent(coordinates.EntityId, out var gridComponent))
|
||||
{
|
||||
var tile = CoordinatesToTile(coordinates.EntityId, gridComponent, coordinates);
|
||||
return ToCenterCoordinates(coordinates.EntityId, tile, gridComponent);
|
||||
}
|
||||
|
||||
// Check if mappos intersects a grid.
|
||||
var mapPos = coordinates.ToMap(EntityManager, _transform);
|
||||
|
||||
if (_mapInternal.TryFindGridAt(mapPos, out var gridUid, out gridComponent))
|
||||
{
|
||||
var tile = CoordinatesToTile(gridUid, gridComponent, coordinates);
|
||||
return ToCenterCoordinates(gridUid, tile, gridComponent);
|
||||
}
|
||||
|
||||
// No grid so just return it.
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCoordinates(TileRef tileRef, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
return ToCoordinates(tileRef.GridUid, tileRef.GridIndices, gridComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCoordinates(EntityUid gridUid, Vector2i tile, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
if (!_gridQuery.Resolve(gridUid, ref gridComponent))
|
||||
{
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
return new EntityCoordinates(gridUid, tile * gridComponent.TileSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates for the center of the tile.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCenterCoordinates(TileRef tileRef, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
return ToCenterCoordinates(tileRef.GridUid, tileRef.GridIndices, gridComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates for the center of the tile.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCenterCoordinates(EntityUid gridUid, Vector2i tile, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
if (!_gridQuery.Resolve(gridUid, ref gridComponent))
|
||||
{
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
return new EntityCoordinates(gridUid, tile * gridComponent.TileSize + gridComponent.TileSizeHalfVector);
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ public abstract partial class SharedMapSystem
|
||||
if (xform.MapUid == null && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} was moved to nullspace! AAAAAAAAAAAAAAAAAAAAAAAAA! {Environment.StackTrace}");
|
||||
|
||||
DebugTools.Assert(!HasComp<MapComponent>(uid));
|
||||
DebugTools.Assert(!_mapQuery.HasComponent(uid));
|
||||
|
||||
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
|
||||
{
|
||||
@@ -454,7 +454,7 @@ public abstract partial class SharedMapSystem
|
||||
if (xform.MapUid != null && xform.MapUid != uid)
|
||||
_transform.SetParent(uid, xform, xform.MapUid.Value);
|
||||
|
||||
if (!HasComp<MapComponent>(uid))
|
||||
if (!_mapQuery.HasComponent(uid))
|
||||
{
|
||||
var aabb = GetWorldAABB(uid, component);
|
||||
|
||||
@@ -506,7 +506,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
private void AddGrid(EntityUid uid, MapGridComponent grid)
|
||||
{
|
||||
DebugTools.Assert(!EntityManager.HasComponent<MapComponent>(uid));
|
||||
DebugTools.Assert(!_mapQuery.HasComponent(uid));
|
||||
var aabb = GetWorldAABB(uid, grid);
|
||||
|
||||
if (!_xformQuery.TryGetComponent(uid, out var xform))
|
||||
@@ -1254,7 +1254,7 @@ public abstract partial class SharedMapSystem
|
||||
$"Grid {uid} is on map {mapId}, but coords are on map {posWorld.MapId}.",
|
||||
nameof(posWorld));
|
||||
|
||||
if (!TryComp<MapGridComponent>(uid, out var grid))
|
||||
if (!_gridQuery.TryGetComponent(uid, out var grid))
|
||||
{
|
||||
return new EntityCoordinates(MapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
InitializeMap();
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedTransformSystem
|
||||
{
|
||||
/*
|
||||
* Helper methods for working with EntityCoordinates / MapCoordinates.
|
||||
* For grid methods see SharedMapSystem.Coordinates
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that this set of coordinates can be currently resolved to a location.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if this set of coordinates can be currently resolved to a location, otherwise <see langword="false" />.</returns>
|
||||
public bool IsValid(EntityCoordinates coordinates)
|
||||
{
|
||||
var entity = coordinates.EntityId;
|
||||
|
||||
if (!entity.IsValid() || !Exists(entity))
|
||||
return false;
|
||||
|
||||
if (!float.IsFinite(coordinates.Position.X) || !float.IsFinite(coordinates.Position.Y))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new set of EntityCoordinates local to a new entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity that the new coordinates will be local to</param>
|
||||
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
|
||||
public EntityCoordinates WithEntityId(EntityCoordinates coordinates, EntityUid entity)
|
||||
{
|
||||
var mapPos = ToMapCoordinates(coordinates);
|
||||
|
||||
// You'd think this would throw like ToCoordinates does but TODO check that.
|
||||
if (mapPos.MapId == MapId.Nullspace)
|
||||
{
|
||||
return new EntityCoordinates(entity, Vector2.Zero);
|
||||
}
|
||||
|
||||
var xform = XformQuery.GetComponent(entity);
|
||||
|
||||
if (xform.MapID != mapPos.MapId)
|
||||
{
|
||||
return new EntityCoordinates(entity, Vector2.Zero);
|
||||
}
|
||||
|
||||
var localPos = GetInvWorldMatrix(xform).Transform(mapPos.Position);
|
||||
return new EntityCoordinates(entity, localPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts entity-local coordinates into map terms.
|
||||
/// </summary>
|
||||
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates)
|
||||
{
|
||||
if (!IsValid(coordinates))
|
||||
return MapCoordinates.Nullspace;
|
||||
|
||||
var xform = XformQuery.GetComponent(coordinates.EntityId);
|
||||
var worldPos = GetWorldMatrix(xform).Transform(coordinates.Position);
|
||||
return new MapCoordinates(worldPos, xform.MapID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
|
||||
public EntityCoordinates ToCoordinates(EntityUid entity, MapCoordinates coordinates)
|
||||
{
|
||||
var xform = XformQuery.GetComponent(entity);
|
||||
|
||||
if (xform.MapID != coordinates.MapId)
|
||||
throw new InvalidOperationException("Entity is not on the same map!");
|
||||
|
||||
var localPos = GetInvWorldMatrix(xform).Transform(coordinates.Position);
|
||||
return new EntityCoordinates(entity, localPos);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -10,16 +8,6 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
public static class CoordinatesExtensions
|
||||
{
|
||||
public static EntityCoordinates ToEntityCoordinates(this Vector2i vector, EntityUid gridId, IMapManager? mapManager = null)
|
||||
{
|
||||
IoCManager.Resolve(ref mapManager);
|
||||
|
||||
var grid = mapManager.GetGrid(gridId);
|
||||
var tile = grid.TileSize;
|
||||
|
||||
return new EntityCoordinates(gridId, new Vector2(vector.X * tile, vector.Y * tile));
|
||||
}
|
||||
|
||||
public static EntityCoordinates AlignWithClosestGridTile(this EntityCoordinates coords, float searchBoxSize = 1.5f, IEntityManager? entityManager = null, IMapManager? mapManager = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entityManager, ref mapManager);
|
||||
|
||||
@@ -185,6 +185,21 @@ public abstract partial class Joint : IEquatable<Joint>
|
||||
// serializer.DataField(this, x => x.BodyA, "bodyA", EntityUid.Invalid);
|
||||
// serializer.DataField(this, x => x.BodyB, "bodyB", Ent);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the other entity on this joint or throws if it's not related.
|
||||
/// </summary>
|
||||
public EntityUid GetOther(EntityUid uid)
|
||||
{
|
||||
if (BodyAUid == uid)
|
||||
return BodyBUid;
|
||||
|
||||
if (BodyBUid == uid)
|
||||
return BodyAUid;
|
||||
|
||||
// Should return EntityUid.Invalid but larger joints refactor first so we can actually log it properly here.
|
||||
throw new ArgumentOutOfRangeException($"EntityUid {uid} unrelated to joint");
|
||||
}
|
||||
|
||||
protected internal void Dirty(IEntityManager? entMan = null)
|
||||
{
|
||||
// TODO: move dirty & setter functions to a system.
|
||||
|
||||
@@ -67,14 +67,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
UpdatesOutsidePrediction = true;
|
||||
UpdatesAfter.Add(typeof(SharedTransformSystem));
|
||||
|
||||
_cfg.OnValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand, true);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_cfg.UnsubValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand);
|
||||
Subs.CVar(_cfg, CVars.BroadphaseExpand, SetBroadphaseExpand, true);
|
||||
}
|
||||
|
||||
private void SetBroadphaseExpand(float value) => _broadphaseExpand = value;
|
||||
|
||||
@@ -70,6 +70,18 @@ public abstract partial class SharedJointSystem
|
||||
if (_container.TryGetOuterContainer(uid, Transform(uid), out var container))
|
||||
{
|
||||
relay = container.Owner;
|
||||
|
||||
// Validate that the relay target is not being set to our own container.
|
||||
foreach (var joint in component.Joints.Values)
|
||||
{
|
||||
var other = joint.GetOther(uid);
|
||||
|
||||
if (other == relay)
|
||||
{
|
||||
SetRelay(uid, null, component);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetRelay(uid, relay, component);
|
||||
|
||||
@@ -199,38 +199,20 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
private void InitializeIsland()
|
||||
{
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, SetTickRate, true);
|
||||
_configManager.OnValueChanged(CVars.WarmStarting, SetWarmStarting, true);
|
||||
_configManager.OnValueChanged(CVars.MaxLinearCorrection, SetMaxLinearCorrection, true);
|
||||
_configManager.OnValueChanged(CVars.MaxAngularCorrection, SetMaxAngularCorrection, true);
|
||||
_configManager.OnValueChanged(CVars.VelocityIterations, SetVelocityIterations, true);
|
||||
_configManager.OnValueChanged(CVars.PositionIterations, SetPositionIterations, true);
|
||||
_configManager.OnValueChanged(CVars.MaxLinVelocity, SetMaxLinearVelocity, true);
|
||||
_configManager.OnValueChanged(CVars.MaxAngVelocity, SetMaxAngularVelocity, true);
|
||||
_configManager.OnValueChanged(CVars.SleepAllowed, SetSleepAllowed, true);
|
||||
_configManager.OnValueChanged(CVars.AngularSleepTolerance, SetAngularToleranceSqr, true);
|
||||
_configManager.OnValueChanged(CVars.LinearSleepTolerance, SetLinearToleranceSqr, true);
|
||||
_configManager.OnValueChanged(CVars.TimeToSleep, SetTimeToSleep, true);
|
||||
_configManager.OnValueChanged(CVars.VelocityThreshold, SetVelocityThreshold, true);
|
||||
_configManager.OnValueChanged(CVars.Baumgarte, SetBaumgarte, true);
|
||||
}
|
||||
|
||||
private void ShutdownIsland()
|
||||
{
|
||||
_configManager.UnsubValueChanged(CVars.NetTickrate, SetTickRate);
|
||||
_configManager.UnsubValueChanged(CVars.WarmStarting, SetWarmStarting);
|
||||
_configManager.UnsubValueChanged(CVars.MaxLinearCorrection, SetMaxLinearCorrection);
|
||||
_configManager.UnsubValueChanged(CVars.MaxAngularCorrection, SetMaxAngularCorrection);
|
||||
_configManager.UnsubValueChanged(CVars.VelocityIterations, SetVelocityIterations);
|
||||
_configManager.UnsubValueChanged(CVars.PositionIterations, SetPositionIterations);
|
||||
_configManager.UnsubValueChanged(CVars.MaxLinVelocity, SetMaxLinearVelocity);
|
||||
_configManager.UnsubValueChanged(CVars.MaxAngVelocity, SetMaxAngularVelocity);
|
||||
_configManager.UnsubValueChanged(CVars.SleepAllowed, SetSleepAllowed);
|
||||
_configManager.UnsubValueChanged(CVars.AngularSleepTolerance, SetAngularToleranceSqr);
|
||||
_configManager.UnsubValueChanged(CVars.LinearSleepTolerance, SetLinearToleranceSqr);
|
||||
_configManager.UnsubValueChanged(CVars.TimeToSleep, SetTimeToSleep);
|
||||
_configManager.UnsubValueChanged(CVars.VelocityThreshold, SetVelocityThreshold);
|
||||
_configManager.UnsubValueChanged(CVars.Baumgarte, SetBaumgarte);
|
||||
Subs.CVar(_configManager, CVars.NetTickrate, SetTickRate, true);
|
||||
Subs.CVar(_configManager, CVars.WarmStarting, SetWarmStarting, true);
|
||||
Subs.CVar(_configManager, CVars.MaxLinearCorrection, SetMaxLinearCorrection, true);
|
||||
Subs.CVar(_configManager, CVars.MaxAngularCorrection, SetMaxAngularCorrection, true);
|
||||
Subs.CVar(_configManager, CVars.VelocityIterations, SetVelocityIterations, true);
|
||||
Subs.CVar(_configManager, CVars.PositionIterations, SetPositionIterations, true);
|
||||
Subs.CVar(_configManager, CVars.MaxLinVelocity, SetMaxLinearVelocity, true);
|
||||
Subs.CVar(_configManager, CVars.MaxAngVelocity, SetMaxAngularVelocity, true);
|
||||
Subs.CVar(_configManager, CVars.SleepAllowed, SetSleepAllowed, true);
|
||||
Subs.CVar(_configManager, CVars.AngularSleepTolerance, SetAngularToleranceSqr, true);
|
||||
Subs.CVar(_configManager, CVars.LinearSleepTolerance, SetLinearToleranceSqr, true);
|
||||
Subs.CVar(_configManager, CVars.TimeToSleep, SetTimeToSleep, true);
|
||||
Subs.CVar(_configManager, CVars.VelocityThreshold, SetVelocityThreshold, true);
|
||||
Subs.CVar(_configManager, CVars.Baumgarte, SetBaumgarte, true);
|
||||
}
|
||||
|
||||
private void SetWarmStarting(bool value) => _warmStarting = value;
|
||||
|
||||
@@ -92,9 +92,9 @@ namespace Robust.Shared.Physics.Systems
|
||||
InitializeIsland();
|
||||
InitializeContacts();
|
||||
|
||||
_configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, UpdateSubsteps, true);
|
||||
_configManager.OnValueChanged(CVars.TargetMinimumTickrate, UpdateSubsteps, true);
|
||||
Subs.CVar(_configManager, CVars.AutoClearForces, OnAutoClearChange);
|
||||
Subs.CVar(_configManager, CVars.NetTickrate, UpdateSubsteps, true);
|
||||
Subs.CVar(_configManager, CVars.TargetMinimumTickrate, UpdateSubsteps, true);
|
||||
}
|
||||
|
||||
private void OnPhysicsShutdown(EntityUid uid, PhysicsComponent component, ComponentShutdown args)
|
||||
@@ -250,8 +250,6 @@ namespace Robust.Shared.Physics.Systems
|
||||
base.Shutdown();
|
||||
|
||||
ShutdownContacts();
|
||||
ShutdownIsland();
|
||||
_configManager.UnsubValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
}
|
||||
|
||||
private void UpdateMapAwakeState(EntityUid uid, PhysicsComponent body)
|
||||
|
||||
@@ -34,6 +34,11 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
// date format for default replay names. Like the sortable template, but without colons.
|
||||
public const string DefaultReplayNameFormat = "yyyy-MM-dd_HH-mm-ss";
|
||||
|
||||
// Kinda arbitrary but (after multiplying by 1024 cuz it's kB)
|
||||
// needs to be less than (max array size) / 2.
|
||||
// I don't think anybody's gonna write 256 MB of chunk at once yeah?
|
||||
private const int MaxTickBatchSize = 256 * 1024;
|
||||
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] protected readonly INetConfigurationManager NetConf = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
@@ -50,8 +55,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
private List<object> _queuedMessages = new();
|
||||
|
||||
// Config variables.
|
||||
private int _maxCompressedSize;
|
||||
private int _maxUncompressedSize;
|
||||
private long _maxCompressedSize;
|
||||
private long _maxUncompressedSize;
|
||||
private int _tickBatchSize;
|
||||
private bool _enabled;
|
||||
|
||||
@@ -63,9 +68,9 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("replay");
|
||||
|
||||
NetConf.OnValueChanged(CVars.ReplayMaxCompressedSize, (v) => _maxCompressedSize = v * 1024, true);
|
||||
NetConf.OnValueChanged(CVars.ReplayMaxUncompressedSize, (v) => _maxUncompressedSize = v * 1024, true);
|
||||
NetConf.OnValueChanged(CVars.ReplayTickBatchSize, (v) => _tickBatchSize = v * 1024, true);
|
||||
NetConf.OnValueChanged(CVars.ReplayMaxCompressedSize, (v) => _maxCompressedSize = SaturatingMultiplyKb(v), true);
|
||||
NetConf.OnValueChanged(CVars.ReplayMaxUncompressedSize, (v) => _maxUncompressedSize = SaturatingMultiplyKb(v), true);
|
||||
NetConf.OnValueChanged(CVars.ReplayTickBatchSize, (v) => _tickBatchSize = Math.Min(v, MaxTickBatchSize) * 1024, true);
|
||||
NetConf.OnValueChanged(CVars.NetPvsCompressLevel, OnCompressionChanged);
|
||||
}
|
||||
|
||||
@@ -450,6 +455,18 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
return new ReplayRecordingStats(time, tick, size, altSize);
|
||||
}
|
||||
|
||||
private static long SaturatingMultiplyKb(long kb)
|
||||
{
|
||||
var result = kb * 1024;
|
||||
if (result < kb)
|
||||
{
|
||||
// Overflow
|
||||
return long.MaxValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all state related to an active recording.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Implements serialization for <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Serialization is implemented with <see cref="DateTimeStyles.RoundtripKind"/> and the "o" format specifier.
|
||||
/// </remarks>
|
||||
[TypeSerializer]
|
||||
public sealed class DateTimeSerializer : ITypeSerializer<DateTime, ValueDataNode>, ITypeCopyCreator<DateTime>
|
||||
{
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return DateTime.TryParse(node.Value, null, DateTimeStyles.RoundtripKind, out _)
|
||||
? new ValidatedValueNode(node)
|
||||
: new ErrorNode(node, "Failed parsing DateTime");
|
||||
}
|
||||
|
||||
public DateTime Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<DateTime>? instanceProvider = null)
|
||||
{
|
||||
return DateTime.Parse(node.Value, null, DateTimeStyles.RoundtripKind);
|
||||
}
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
DateTime value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new ValueDataNode(value.ToString("o"));
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
public DateTime CreateCopy(
|
||||
ISerializationManager serializationManager,
|
||||
DateTime source,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -759,8 +759,9 @@ namespace Robust.UnitTesting
|
||||
|
||||
public sealed class ClientIntegrationInstance : IntegrationInstance
|
||||
{
|
||||
[Obsolete("Use Session instead")]
|
||||
public LocalPlayer? Player => ((IPlayerManager) PlayerMan).LocalPlayer;
|
||||
public ICommonSession? Session => Player?.Session;
|
||||
public ICommonSession? Session => ((IPlayerManager) PlayerMan).LocalSession;
|
||||
public NetUserId? User => Session?.UserId;
|
||||
public EntityUid? AttachedEntity => Session?.AttachedEntity;
|
||||
|
||||
|
||||
115
Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs
Normal file
115
Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Robust.UnitTesting.Shared.GameState.ExampleAutogeneratedComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameState;
|
||||
|
||||
/// <summary>
|
||||
/// This is a test of test engine <see cref="RobustIntegrationTest"/>. Not a test of game engine.
|
||||
/// </summary>
|
||||
public sealed partial class NoSharedReferencesTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// The test performs a basic check to ensure that there is no issue with server's object references leaking to client.
|
||||
/// It accomplishments this by testing two things: 1) That the reference object on both sides is not the same; and
|
||||
/// 2) That the client-side copy of server's component state (used for prediction resetting) is not the same.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ReferencesAreNotShared()
|
||||
{
|
||||
var serverOpts = new ServerIntegrationOptions { Pool = false };
|
||||
var clientOpts = new ClientIntegrationOptions { Pool = false };
|
||||
var server = StartServer(serverOpts);
|
||||
var client = StartClient(clientOpts);
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
var netMan = client.ResolveDependency<IClientNetManager>();
|
||||
var clientGameStateManager = client.ResolveDependency<IClientGameStateManager>();
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
||||
|
||||
// Set up map.
|
||||
EntityUid map = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapId = server.MapMan.CreateMap();
|
||||
map = server.MapMan.GetMapEntityId(mapId);
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
|
||||
EntityUid sPlayer = default;
|
||||
EntityUid cPlayer = default;
|
||||
ExampleObject serverObject = default!;
|
||||
var sEntMan = server.EntMan;
|
||||
|
||||
// Set up test entity (player).
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
sPlayer = sEntMan.Spawn();
|
||||
serverObject = new ExampleObject(5);
|
||||
|
||||
var comp = new ExampleAutogeneratedComponent { ReferenceObject = serverObject };
|
||||
sEntMan.AddComponent(sPlayer, comp);
|
||||
|
||||
var session = server.PlayerMan.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(session, sPlayer);
|
||||
server.PlayerMan.JoinGame(session);
|
||||
});
|
||||
|
||||
// Let Client sync state with Server
|
||||
await RunTicks();
|
||||
|
||||
// Assert that Client's object and client-side server state objects are different to Server's object
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Player attached assertions
|
||||
var cEntMan = client.EntMan;
|
||||
cPlayer = cEntMan.GetEntity(server.EntMan.GetNetEntity(sPlayer));
|
||||
Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer));
|
||||
Assert.That(cEntMan.EntityExists(cPlayer));
|
||||
|
||||
// Assert client and server have different objects of same values
|
||||
Assert.That(cEntMan.TryGetComponent(cPlayer, out ExampleAutogeneratedComponent? comp));
|
||||
var clientObject = comp?.ReferenceObject;
|
||||
Assert.That(clientObject, Is.EqualTo(serverObject));
|
||||
Assert.That(ReferenceEquals(clientObject, serverObject), Is.False);
|
||||
|
||||
// Assert that client-side dictionary of server component state also isn't contaminated by server references
|
||||
var componentStates = clientGameStateManager.GetFullRep()[cEntMan.GetNetEntity(cPlayer)];
|
||||
var clientLastTickStateObject = ((ExampleAutogeneratedComponent_AutoState)componentStates.First(x => x.Value is ExampleAutogeneratedComponent_AutoState).Value).ReferenceObject;
|
||||
Assert.That(clientLastTickStateObject, Is.Not.Null);
|
||||
Assert.That(ReferenceEquals(clientLastTickStateObject, serverObject), Is.False);
|
||||
});
|
||||
|
||||
// wait for errors.
|
||||
await RunTicks();
|
||||
|
||||
async Task RunTicks()
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ExampleAutogeneratedComponent : Component
|
||||
{
|
||||
[AutoNetworkedField]
|
||||
public ExampleObject ReferenceObject;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record ExampleObject(int Value);
|
||||
}
|
||||
@@ -73,8 +73,8 @@ public sealed class DisconnectTest : RobustIntegrationTest
|
||||
|
||||
session = sPlayerMan.Sessions.Single();
|
||||
Assert.That(session.Status, Is.EqualTo(SessionStatus.Connected));
|
||||
Assert.That(session.UserId, Is.EqualTo(cPlayerMan!.LocalPlayer?.UserId));
|
||||
Assert.That(cPlayerMan.LocalPlayer, Is.Not.Null);
|
||||
Assert.That(session.UserId, Is.EqualTo(cPlayerMan.LocalUser));
|
||||
Assert.That(cPlayerMan.LocalSession, Is.Not.Null);
|
||||
}
|
||||
|
||||
void AssertDisconnected()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DateTimeSerializer))]
|
||||
internal sealed class DateTimeSerializerTest : SerializationTest
|
||||
{
|
||||
[Test]
|
||||
public void WriteTest()
|
||||
{
|
||||
var dateTime = DateTime.UtcNow;
|
||||
var result = Serialization.WriteValueAs<ValueDataNode>(dateTime);
|
||||
|
||||
var parsed = DateTime.Parse(result.Value, null, DateTimeStyles.RoundtripKind);
|
||||
Assert.That(parsed, Is.EqualTo(dateTime));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadTest()
|
||||
{
|
||||
var result = Serialization.Read<DateTime>(new ValueDataNode("2020-07-10 15:00:00.000"));
|
||||
|
||||
Assert.That(result, Is.EqualTo(new DateTime(2020, 07, 10, 15, 0, 0)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user