diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 98785c46ea1..baf384dd395 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,9 @@ END TEMPLATE--> ### New features -*None yet* +* `RequiredMemberAttribute` and `SetsRequiredMembersAttribute` have been added to the sandbox whitelist. I.e., you can now use the `required` keyword in client/shared code. +* Added `LineEdit.SelectAllOnFocus`. +* ``Gametitle``,``WindowIconSet`` and ``SplashLogo`` are exposed in IGameController. These will return said information set game options or whatever is set in manifest.yml. ### Bugfixes diff --git a/Robust.Client/GameController/GameController.cs b/Robust.Client/GameController/GameController.cs index bd162b6538c..4116fb52c93 100644 --- a/Robust.Client/GameController/GameController.cs +++ b/Robust.Client/GameController/GameController.cs @@ -112,14 +112,28 @@ public void SetCommandLineArgs(CommandLineArgs args) _commandLineArgs = args; } + public string GameTitle() + { + return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox"; + } + + public string WindowIconSet() + { + return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? ""; + } + + public string SplashLogo() + { + return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? ""; + } + internal bool StartupContinue(DisplayMode displayMode) { DebugTools.AssertNotNull(_resourceManifest); _clyde.InitializePostWindowing(); _audio.InitializePostWindowing(); - _clyde.SetWindowTitle( - Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox"); + _clyde.SetWindowTitle(GameTitle()); _taskManager.Initialize(); _parallelMgr.Initialize(); @@ -399,10 +413,8 @@ internal bool StartupSystemSplash( // Handle GameControllerOptions implicit CVar overrides. _configurationManager.OverrideConVars(new[] { - (CVars.DisplayWindowIconSet.Name, - options.WindowIconSet?.ToString() ?? _resourceManifest.WindowIconSet ?? ""), - (CVars.DisplaySplashLogo.Name, - options.SplashLogo?.ToString() ?? _resourceManifest.SplashLogo ?? "") + (CVars.DisplayWindowIconSet.Name, WindowIconSet()), + (CVars.DisplaySplashLogo.Name, SplashLogo()) }); } diff --git a/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs b/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs index 8d3323d6f4a..ce1f0c93f20 100644 --- a/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs @@ -143,6 +143,14 @@ public void Play(Entity ent, Animation animation, stri } #endif + foreach (var track in animation.AnimationTracks) + { + if (track is not AnimationTrackSpriteFlick) + continue; + + track.AdvancePlayback(ent.Owner, 0, 0, 0f); + } + ent.Comp.PlayingAnimations.Add(key, playback); } diff --git a/Robust.Client/IGameController.cs b/Robust.Client/IGameController.cs index ee3190622c4..1212e26a67f 100644 --- a/Robust.Client/IGameController.cs +++ b/Robust.Client/IGameController.cs @@ -26,5 +26,20 @@ public interface IGameController /// This exists to give content module more control over tick updating. /// event Action? TickUpdateOverride; + + /// + /// Get the games Title, if Options.DefaultWindowTitle or if defaultWindowTitle is not set in the manifest.yml, it will default to RobustToolbox. + /// + string GameTitle(); + + /// + /// Get the games Window Icon set, if Options.WindowIconSet or if windowIconSet is not set in the manifest.yml, it will default to an empty string. + /// + string WindowIconSet(); + + /// + /// Get the games Splash Logo, if Options.SplashLogo or if splashLogo is not set in the manifest.yml, it will default to an empty string. + /// + string SplashLogo(); } diff --git a/Robust.Client/UserInterface/Controls/LineEdit.cs b/Robust.Client/UserInterface/Controls/LineEdit.cs index 5e3c54e35fa..2022926b85e 100644 --- a/Robust.Client/UserInterface/Controls/LineEdit.cs +++ b/Robust.Client/UserInterface/Controls/LineEdit.cs @@ -53,6 +53,15 @@ public class LineEdit : Control private TimeSpan? _lastClickTime; private Vector2? _lastClickPosition; + // Keep track of the frame on which we got focus, so we can implement SelectAllOnFocus properly. + // Otherwise, there's no way to keep track of whether the KeyDown is the one that focused the text box, + // to avoid text selection stomping on the behavior. + // This isn't a great way to do it. + // A better fix would be to annotate all input events with some unique sequence ID, + // and expose the input event that focused the control in KeyboardFocusEntered. + // But that sounds like a refactor I'm not doing today. + private uint _focusedOnFrame; + private bool IsPlaceHolderVisible => !(HidePlaceHolderOnFocus && HasKeyboardFocus()) && string.IsNullOrEmpty(_text) && _placeHolder != null; public event Action? OnTextChanged; @@ -190,6 +199,11 @@ public int SelectionStart public bool IgnoreNext { get; set; } + /// + /// If true, all the text in the LineEdit will be automatically selected whenever it is focused. + /// + public bool SelectAllOnFocus { get; set; } + private (int start, int length)? _imeData; @@ -709,7 +723,7 @@ async void DoPaste() args.Handle(); } - else + else if (!(SelectAllOnFocus && _focusedOnFrame == _timing.CurFrame)) { _lastClickTime = _timing.RealTime; _lastClickPosition = args.PointerLocation.Position; @@ -868,6 +882,13 @@ protected internal override void KeyboardFocusEntered() { _clyde.TextInputStart(); } + + _focusedOnFrame = _timing.CurFrame; + if (SelectAllOnFocus) + { + CursorPosition = _text.Length; + SelectionStart = 0; + } } protected internal override void KeyboardFocusExited() diff --git a/Robust.Server/Console/ServerConsoleHost.cs b/Robust.Server/Console/ServerConsoleHost.cs index a9325163595..fa950250013 100644 --- a/Robust.Server/Console/ServerConsoleHost.cs +++ b/Robust.Server/Console/ServerConsoleHost.cs @@ -162,7 +162,6 @@ private void HandleRegistrationRequest(INetChannel senderConnection) { var message = new MsgConCmdReg(); - var counter = 0; var toolshedCommands = _toolshed.DefaultEnvironment.AllCommands().ToArray(); message.Commands = new List(AvailableCommands.Count + toolshedCommands.Length); var commands = new HashSet(); diff --git a/Robust.Server/Physics/GridFixtureSystem.cs b/Robust.Server/Physics/GridFixtureSystem.cs index 28622aa8b68..30d956c7683 100644 --- a/Robust.Server/Physics/GridFixtureSystem.cs +++ b/Robust.Server/Physics/GridFixtureSystem.cs @@ -285,7 +285,7 @@ private void CheckSplits(EntityUid uid, HashSet dirtyNodes) foreach (var index in node.Indices) { var tilePos = offset + index; - tileData.Add((tilePos, oldGrid.GetTileRef(tilePos).Tile)); + tileData.Add((tilePos, _maps.GetTileRef(oldGridUid, oldGrid, tilePos).Tile)); } } @@ -355,7 +355,7 @@ private void CheckSplits(EntityUid uid, HashSet dirtyNodes) } // Set tiles on old grid - oldGrid.SetTiles(tileData); + _maps.SetTiles(oldGridUid, oldGrid, tileData); GenerateSplitNodes(newGridUid, newGrid); SendNodeDebug(newGridUid); } @@ -388,7 +388,7 @@ private void CheckSplits(EntityUid uid, HashSet dirtyNodes) private void GenerateSplitNodes(EntityUid gridUid, MapGridComponent grid) { - foreach (var chunk in grid.GetMapChunks().Values) + foreach (var chunk in _maps.GetMapChunks(gridUid, grid).Values) { var group = CreateNodes(gridUid, grid, chunk); _nodes[gridUid].Add(chunk.Indices, group); @@ -479,7 +479,7 @@ private ChunkNodeGroup CreateNodes(EntityUid gridEuid, MapGridComponent grid, Ma if (index.X == 0) { // Check West - if (grid.TryGetChunk(new Vector2i(chunk.Indices.X - 1, chunk.Indices.Y), out neighborChunk) && + if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X - 1, chunk.Indices.Y), out neighborChunk) && TryGetNode(gridEuid, neighborChunk, new Vector2i(chunk.ChunkSize - 1, index.Y), out neighborNode)) { chunkNode.Neighbors.Add(neighborNode); @@ -490,7 +490,7 @@ private ChunkNodeGroup CreateNodes(EntityUid gridEuid, MapGridComponent grid, Ma if (index.Y == 0) { // Check South - if (grid.TryGetChunk(new Vector2i(chunk.Indices.X, chunk.Indices.Y - 1), out neighborChunk) && + if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X, chunk.Indices.Y - 1), out neighborChunk) && TryGetNode(gridEuid, neighborChunk, new Vector2i(index.X, chunk.ChunkSize - 1), out neighborNode)) { chunkNode.Neighbors.Add(neighborNode); @@ -501,7 +501,7 @@ private ChunkNodeGroup CreateNodes(EntityUid gridEuid, MapGridComponent grid, Ma if (index.X == chunk.ChunkSize - 1) { // Check East - if (grid.TryGetChunk(new Vector2i(chunk.Indices.X + 1, chunk.Indices.Y), out neighborChunk) && + if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X + 1, chunk.Indices.Y), out neighborChunk) && TryGetNode(gridEuid, neighborChunk, new Vector2i(0, index.Y), out neighborNode)) { chunkNode.Neighbors.Add(neighborNode); @@ -512,7 +512,7 @@ private ChunkNodeGroup CreateNodes(EntityUid gridEuid, MapGridComponent grid, Ma if (index.Y == chunk.ChunkSize - 1) { // Check North - if (grid.TryGetChunk(new Vector2i(chunk.Indices.X, chunk.Indices.Y + 1), out neighborChunk) && + if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X, chunk.Indices.Y + 1), out neighborChunk) && TryGetNode(gridEuid, neighborChunk, new Vector2i(index.X, 0), out neighborNode)) { chunkNode.Neighbors.Add(neighborNode); diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index 37770f25bd5..8916b2db6ab 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -480,6 +480,7 @@ Types: NotNullAttribute: { All: True } NotNullIfNotNullAttribute: { All: True } NotNullWhenAttribute: { All: True } + SetsRequiredMembersAttribute: { All : True} SuppressMessageAttribute: { All: True } System.Diagnostics: DebuggableAttribute: { All: True } @@ -588,7 +589,6 @@ Types: Vector2: { All: True } Vector3: { All: True } Vector4: { All: True } - Matrix3x2: { All: True } System.Reflection: Assembly: Methods: @@ -653,6 +653,7 @@ Types: NullableContextAttribute: { All: True } PreserveBaseOverridesAttribute: { All: True } RefSafetyRulesAttribute: { All: True } + RequiredMemberAttribute: { All: True } RuntimeCompatibilityAttribute: { All: True } RuntimeHelpers: { All: True } TaskAwaiter: { All: True } diff --git a/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs b/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs index 65f6411755b..09a22a96109 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs @@ -69,7 +69,6 @@ public Dictionary> ValidateDirectory(ResPath path, var errors = new List(); foreach (var (type, instances) in prototypes) { - var defaultErrorOccurred = false; foreach (var (id, data) in instances) { errors.Clear(); diff --git a/Robust.UnitTesting/GameControllerDummy.cs b/Robust.UnitTesting/GameControllerDummy.cs index a29ac8ae1a0..67c945e1ab2 100644 --- a/Robust.UnitTesting/GameControllerDummy.cs +++ b/Robust.UnitTesting/GameControllerDummy.cs @@ -66,5 +66,20 @@ public void MouseWheel(MouseWheelEventArgs mouseWheelEventArgs) public void OverrideMainLoop(IGameLoop gameLoop) { } + + public string GameTitle() + { + return "RobustToolbox"; + } + + public string WindowIconSet() + { + return ""; + } + + public string SplashLogo() + { + return ""; + } } } diff --git a/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs b/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs index ed8fb7dc796..f90fa326d00 100644 --- a/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs @@ -25,6 +25,8 @@ public async Task TestLocalWorldConversions() var entMan = server.ResolveDependency(); var mapMan = server.ResolveDependency(); + var mapSystem = entMan.System(); + var transformSystem = entMan.System(); await server.WaitAssertion(() => { @@ -34,22 +36,23 @@ await server.WaitAssertion(() => var coordinates = new EntityCoordinates(gridEnt, new Vector2(10, 0)); // if no rotation and 0,0 position should just be the same coordinate. - Assert.That(entMan.GetComponent(gridEnt).WorldRotation, Is.EqualTo(Angle.Zero)); - Assert.That(grid.Comp.WorldToLocal(coordinates.Position), Is.EqualTo(coordinates.Position)); + Assert.That(transformSystem.GetWorldRotation(gridEnt), Is.EqualTo(Angle.Zero)); + + Assert.That(mapSystem.WorldToLocal(grid.Owner, grid.Comp, coordinates.Position), Is.EqualTo(coordinates.Position)); // Rotate 180 degrees should show -10, 0 for the position in map-terms and 10, 0 for the position in entity terms (i.e. no change). entMan.GetComponent(gridEnt).WorldRotation += new Angle(MathF.PI); - Assert.That(entMan.GetComponent(gridEnt).WorldRotation, Is.EqualTo(new Angle(MathF.PI))); + Assert.That(transformSystem.GetWorldRotation(gridEnt), Is.EqualTo(new Angle(MathF.PI))); // Check the map coordinate rotates correctly - Assert.That(grid.Comp.WorldToLocal(new Vector2(10, 0)).EqualsApprox(new Vector2(-10, 0), 0.01f)); - Assert.That(grid.Comp.LocalToWorld(coordinates.Position).EqualsApprox(new Vector2(-10, 0), 0.01f)); + Assert.That(mapSystem.WorldToLocal(grid.Owner, grid.Comp, new Vector2(10, 0)).EqualsApprox(new Vector2(-10, 0), 0.01f)); + Assert.That(mapSystem.LocalToWorld(grid.Owner, grid.Comp, coordinates.Position).EqualsApprox(new Vector2(-10, 0), 0.01f)); // Now we'll do the same for 180 degrees. entMan.GetComponent(gridEnt).WorldRotation += MathF.PI / 2f; // If grid facing down then worldpos of 10, 0 gets rotated 90 degrees CCW and hence should be 0, 10 - Assert.That(grid.Comp.WorldToLocal(new Vector2(10, 0)).EqualsApprox(new Vector2(0, 10), 0.01f)); + Assert.That(mapSystem.WorldToLocal(grid.Owner, grid.Comp, new Vector2(10, 0)).EqualsApprox(new Vector2(0, 10), 0.01f)); // If grid facing down then local 10,0 pos should just return 0, -10 given it's aligned with the rotation. - Assert.That(grid.Comp.LocalToWorld(coordinates.Position).EqualsApprox(new Vector2(0, -10), 0.01f)); + Assert.That(mapSystem.LocalToWorld(grid.Owner, grid.Comp, coordinates.Position).EqualsApprox(new Vector2(0, -10), 0.01f)); }); } @@ -78,11 +81,11 @@ await server.WaitAssertion(() => { for (var y = 0; y < 10; y++) { - grid.Comp.SetTile(new Vector2i(x, y), tile); + mapSystem.SetTile(grid, new Vector2i(x, y), tile); } } - var chunks = grid.Comp.GetMapChunks().Select(c => c.Value).ToList(); + var chunks = mapSystem.GetMapChunks(grid.Owner, grid.Comp).Select(c => c.Value).ToList(); Assert.That(chunks.Count, Is.EqualTo(1)); var chunk = chunks[0];