Compare commits

...

27 Commits

Author SHA1 Message Date
DrSmugleaf
f6c55085fe Version: 148.3.0 2023-08-21 19:01:15 -07:00
DrSmugleaf
30eafd26e7 Fix test checking that Robust's and .NET's colors are equal (#4287) 2023-08-21 16:26:06 -07:00
Pieter-Jan Briers
63423d96b4 Fixes for new color PR (#4278)
Undo change to violet color

add to named color list
2023-08-21 23:06:20 +02:00
Morb
474334aff2 Make DiscordRichPresence icon CVars server-side with replication (#4272) 2023-08-21 10:44:40 +02:00
Leon Friedrich
be102f86bf Add IntegrationInstance fields for common dependencies (#4283) 2023-08-21 14:35:27 +10:00
Tom Leys
d7962c7190 Add implementation of Random.Pick(ValueList<T> ..) (#4257) 2023-08-21 13:56:18 +10:00
Leon Friedrich
7fe9385c3b Change default value of EntityLastModifiedTick from zero to one. (#4282)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2023-08-21 13:55:41 +10:00
Kara
a9d9d1348a Tile texture reload command (#4268) 2023-08-21 13:46:58 +10:00
Leon Friedrich
4eaf624555 Allow pre-startup components to be shut down. (#4281) 2023-08-21 13:45:57 +10:00
Leon Friedrich
e37c131fb4 Prevent invalid prototypes from being spawned (#4279) 2023-08-21 12:29:02 +10:00
PrPleGoo
9df4606492 Added colors (#4278) 2023-08-20 18:48:00 +02:00
Pieter-Jan Briers
3be8070274 Happy eyeballs delay can be configured. 2023-08-20 17:45:43 +02:00
metalgearsloth
8a440d705f Version: 148.2.0 2023-08-20 15:53:35 +10:00
metalgearsloth
5849474022 Add IsPaused to EntityManager (#4277) 2023-08-20 15:50:11 +10:00
c4llv07e
b7d67c0ece Fix UserInterface.SetActiveTheme didn't update theme (#4263) 2023-08-20 03:36:38 +10:00
Pieter-Jan Briers
b04cf71bc0 Expose SpinBox.LineEditControl 2023-08-19 16:32:05 +02:00
Leon Friedrich
ea87df649a Add readonly VV attributes to various fields. (#4265) 2023-08-20 00:13:18 +10:00
metalgearsloth
dab7a9112f Version: 148.1.0 2023-08-16 19:19:46 +10:00
DrSmugleaf
d3dc89832a Add component that lets entities ignore BUI range checks (#4264) 2023-08-16 15:41:01 +10:00
Leon Friedrich
3ade9ca447 Add support for f16-f24 keys (#4261) 2023-08-13 22:15:18 +10:00
Leon Friedrich
149e9a2613 Fix a gamestate bug. (#4260) 2023-08-13 11:22:15 +10:00
ElectroJr
d164148ce2 Version: 148.0.0 2023-08-12 19:40:55 -04:00
/ʊniɹɑː/
d898b52449 more richtext (#4187) 2023-08-13 09:34:44 +10:00
TemporalOroboros
dcf7a1e580 PixelToMap (#4188) 2023-08-13 09:34:33 +10:00
Pieter-Jan Briers
65c6bb74eb Mark a bunch of NuGet dependencies as private compile assets (#4258) 2023-08-13 07:22:14 +10:00
Leon Friedrich
d6d88bea91 Fix replay handling of bad prototype uploads (#4259) 2023-08-13 07:07:47 +10:00
Pieter-Jan Briers
d6467f768a Fix Logger calls in ComponentRegistrySerializer 2023-08-11 23:50:54 +02:00
71 changed files with 724 additions and 201 deletions

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,6 +54,70 @@ END TEMPLATE-->
*None yet*
## 148.3.0
### New features
* Happy eyeballs delay can be configured.
* Added more colors.
* Allow pre-startup components to be shut down.
* Added tile texture reload command.
* Add implementation of Random.Pick(ValueList<T> ..).
* Add IntegrationInstance fields for common dependencies.
### Bugfixes
* Prevent invalid prototypes from being spawned.
* Change default value of EntityLastModifiedTick from zero to one.
* Make DiscordRichPresence icon CVars server-side with replication.
## 148.2.0
### New features
* `SpinBox.LineEditControl` exposes the underlying `LineEdit`.
* Add VV attributes to various fields across overlay and sessions.
* Add IsPaused to EntityManager to check if an entity is paused.
### Bugfixes
* Fix SetActiveTheme not updating the theme.
## 148.1.0
### New features
* Added IgnoreUIChecksComponent that lets entities ignore bound user interface range checks which would normally close the UI.
* Add support for F16-F24 keybinds.
### Bugfixes
* Fix gamestate bug where PVS is disabled.
### Other
* EntityQuery.HasComponent override for nullable entity uids.
## 148.0.0
### Breaking changes
* Several NuGet dependencies are now private assets.
* Added `IViewportControl.PixelToMap()` and `PixelToMapEvent`. These are variants of the existing screen-to-map functions that should account for distortion effects.
### New features
* Added several new rich-text tags, including italic and bold-italic.
### Bugfixes
* Fixed log messages for unknown components not working due to threaded IoC issues.
* Replay recordings no longer record invalid prototype uploads.
## 147.0.0
### Breaking changes
@@ -204,7 +268,7 @@ END TEMPLATE-->
* `IHttpClientHolder` holds a shared `HttpClient` for use by content. It has Happy Eyeballs fixed and an appropriate `User-Agent`.
* Added `DataNode.ToString()`. Makes it easier to save yaml files and debug code.
* Added some cvars to modify discord rich presence icons.
* .ogg files now read the `Artist` and `Title` tags and make them available via new fields in `AudioStream`.
* .ogg files now read the `Artist` and `Title` tags and make them available via new fields in `AudioStream`.
* The default fragment shaders now have access to the local light level (`lowp vec3 lightSample`).
* Added `IPrototypeManager.ValidateAllPrototypesSerializable()`, which can be used to check that all currently loaded prototypes can be serialised & deserialised.
@@ -213,7 +277,7 @@ END TEMPLATE-->
* Fix certain debug commands and tools crashing on non-SS14 RobustToolbox games due to a missing font.
* Discord rich presence strings are now truncated if they are too long.
* Fixed a couple of broadphase/entity-lookup update bugs that were affecting containers and entities attached to other (non-grid/map) entities.
* Fixed `INetChannel.Disconnect()` not properly disconnecting clients in integration tests.
* Fixed `INetChannel.Disconnect()` not properly disconnecting clients in integration tests.
### Other

View File

@@ -558,3 +558,6 @@ cmd-vfs_ls-help = Usage: vfs_list <path>
cmd-vfs_ls-err-args = Need exactly 1 argument.
cmd-vfs_ls-hint-path = <path>
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
cmd-reloadtiletextures-help = Usage: reloadtiletextures

View File

@@ -18,6 +18,15 @@ input-key-F12 = F12
input-key-F13 = F13
input-key-F14 = F14
input-key-F15 = F15
input-key-F16 = F16
input-key-F17 = F17
input-key-F18 = F18
input-key-F19 = F19
input-key-F20 = F20
input-key-F21 = F21
input-key-F22 = F22
input-key-F23 = F23
input-key-F24 = F24
input-key-Pause = Pause
input-key-Left = Left
input-key-Up = Up

View File

@@ -68,6 +68,7 @@ namespace Robust.Client
deps.Register<IComponentFactory, ComponentFactory>();
deps.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
deps.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
deps.Register<ClydeTileDefinitionManager, ClydeTileDefinitionManager>();
deps.Register<GameController, GameController>();
deps.Register<IGameController, GameController>();
deps.Register<IGameControllerInternal, GameController>();

View File

@@ -630,7 +630,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
var mousePos = _eye.PixelToMap(_input.MouseScreenPosition);
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
{

View File

@@ -61,7 +61,7 @@ namespace Robust.Client.Debugging
}
var mouseSpot = _inputManager.MouseScreenPosition;
var spot = _eyeManager.ScreenToMap(mouseSpot);
var spot = _eyeManager.PixelToMap(mouseSpot);
if (!_mapManager.TryFindGridAt(spot, out var gridUid, out var grid))
{

View File

@@ -371,7 +371,7 @@ namespace Robust.Client.Debugging
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
{
var hoverBodies = new List<PhysicsComponent>();
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
var bounds = Box2.UnitCentered.Translated(_eyeManager.PixelToMap(mousePos.Position).Position);
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
{
@@ -404,7 +404,7 @@ namespace Robust.Client.Debugging
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Distance) != 0x0)
{
var mapPos = _eyeManager.ScreenToMap(mousePos);
var mapPos = _eyeManager.PixelToMap(mousePos);
if (mapPos.MapId != args.MapId)
return;

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.Graphics
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
private ISawmill _logMill = default!;
// We default to this when we get set to a null eye.
private readonly FixedEye _defaultEye = new();
@@ -53,6 +54,7 @@ namespace Robust.Client.Graphics
void IEyeManager.Initialize()
{
MainViewport = _uiManager.MainViewport;
_logMill = IoCManager.Resolve<ILogManager>().RootSawmill;
}
/// <inheritdoc />
@@ -129,14 +131,14 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public ScreenCoordinates CoordinatesToScreen(EntityCoordinates point)
{
return MapToScreen(point.ToMap(_entityManager));
return MapToScreen(point.ToMap(_entityManager, _entityManager.System<SharedTransformSystem>()));
}
public ScreenCoordinates MapToScreen(MapCoordinates point)
{
if (CurrentEye.Position.MapId != point.MapId)
{
Logger.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
_logMill.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
return new(default, WindowId.Invalid);
}
@@ -146,12 +148,10 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public MapCoordinates ScreenToMap(ScreenCoordinates point)
{
var (pos, window) = point;
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
return default;
return viewport.ScreenToMap(pos);
return viewport.ScreenToMap(point.Position);
}
/// <inheritdoc />
@@ -159,6 +159,21 @@ namespace Robust.Client.Graphics
{
return MainViewport.ScreenToMap(point);
}
/// <inheritdoc />
public MapCoordinates PixelToMap(ScreenCoordinates point)
{
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
return default;
return viewport.PixelToMap(point.Position);
}
/// <inheritdoc />
public MapCoordinates PixelToMap(Vector2 point)
{
return MainViewport.PixelToMap(point);
}
}
public sealed class CurrentEyeChangedEvent : EntityEventArgs

View File

@@ -79,6 +79,16 @@ namespace Robust.Client.Graphics
/// <returns>Corresponding point in the world.</returns>
MapCoordinates ScreenToMap(Vector2 point);
/// <summary>
/// Similar to <see cref="ScreenToMap(ScreenCoordinates)"/>, except it should compensate for the effects of shaders on viewports.
/// </summary>
MapCoordinates PixelToMap(ScreenCoordinates point);
/// <summary>
/// Similar to <see cref="ScreenToMap(Vector2)"/>, except it should compensate for the effects of shaders on viewports.
/// </summary>
MapCoordinates PixelToMap(Vector2 point);
void ClearCurrentEye();
void Initialize();
}

View File

@@ -207,6 +207,15 @@ namespace Robust.Client.Graphics.Clyde
{GlfwKey.F13, Key.F13},
{GlfwKey.F14, Key.F14},
{GlfwKey.F15, Key.F15},
{GlfwKey.F16, Key.F16},
{GlfwKey.F17, Key.F17},
{GlfwKey.F18, Key.F18},
{GlfwKey.F19, Key.F19},
{GlfwKey.F20, Key.F20},
{GlfwKey.F21, Key.F21},
{GlfwKey.F22, Key.F22},
{GlfwKey.F23, Key.F23},
{GlfwKey.F24, Key.F24},
{GlfwKey.Pause, Key.Pause},
{GlfwKey.World1, Key.World1},
};

View File

@@ -191,6 +191,15 @@ internal partial class Clyde
MapKey(SDL_SCANCODE_F13, Key.F13);
MapKey(SDL_SCANCODE_F14, Key.F14);
MapKey(SDL_SCANCODE_F15, Key.F15);
MapKey(SDL_SCANCODE_F16, Key.F16);
MapKey(SDL_SCANCODE_F17, Key.F17);
MapKey(SDL_SCANCODE_F18, Key.F18);
MapKey(SDL_SCANCODE_F19, Key.F19);
MapKey(SDL_SCANCODE_F20, Key.F20);
MapKey(SDL_SCANCODE_F21, Key.F21);
MapKey(SDL_SCANCODE_F22, Key.F22);
MapKey(SDL_SCANCODE_F23, Key.F23);
MapKey(SDL_SCANCODE_F24, Key.F24);
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
KeyMapReverse = new Dictionary<Key, SDL_Scancode>();

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Graphics
{
@@ -11,6 +12,7 @@ namespace Robust.Client.Graphics
{
[Dependency] private readonly ILogManager _logMan = default!;
[ViewVariables]
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
private ISawmill _logger = default!;

View File

@@ -161,6 +161,15 @@ namespace Robust.Client.Input
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
Pause,
World1,
}

View File

@@ -3,13 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -52,8 +55,11 @@ namespace Robust.Client.Map
_genTextureAtlas();
}
private void _genTextureAtlas()
internal void _genTextureAtlas()
{
_tileRegions.Clear();
_tileTextureAtlas = null;
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
// If there are no tile definitions, we do nothing.
@@ -144,4 +150,17 @@ namespace Robust.Client.Map
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
}
}
public sealed class ReloadTileTexturesCommand : LocalizedCommands
{
[Dependency] private readonly ClydeTileDefinitionManager _tile = default!;
public override string Command => "reloadtiletextures";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_tile._genTextureAtlas();
}
}
}

View File

@@ -532,7 +532,7 @@ namespace Robust.Client.Placement
return false;
}
coordinates = EntityCoordinates.FromMap(MapManager,
EyeManager.ScreenToMap(InputManager.MouseScreenPosition));
EyeManager.PixelToMap(InputManager.MouseScreenPosition));
return true;
}
}

View File

@@ -134,7 +134,7 @@ namespace Robust.Client.Placement
public IEnumerable<EntityCoordinates> LineCoordinates()
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.ScreenToMap(mouseScreen);
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
if (mousePos.MapId == MapId.Nullspace)
yield break;
@@ -165,7 +165,7 @@ namespace Robust.Client.Placement
public IEnumerable<EntityCoordinates> GridCoordinates()
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.ScreenToMap(mouseScreen);
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
if (mousePos.MapId == MapId.Nullspace)
yield break;
@@ -254,7 +254,7 @@ namespace Robust.Client.Placement
protected EntityCoordinates ScreenToCursorGrid(ScreenCoordinates coords)
{
var mapCoords = pManager.EyeManager.ScreenToMap(coords.Position);
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
{
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);

View File

@@ -3,14 +3,18 @@ using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
{
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
{
new IEnumerable<ICommonSession> Sessions { get; }
[ViewVariables]
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
[ViewVariables]
LocalPlayer? LocalPlayer { get; }
/// <summary>

View File

@@ -2,6 +2,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
{
@@ -17,11 +18,14 @@ namespace Robust.Client.Player
}
/// <inheritdoc />
[ViewVariables]
public EntityUid? AttachedEntity { get; set; }
/// <inheritdoc />
[ViewVariables]
public NetUserId UserId { get; }
[ViewVariables]
internal string Name { get; set; } = "<Unknown>";
/// <inheritdoc />
@@ -31,9 +35,11 @@ namespace Robust.Client.Player
set => this.Name = value;
}
[ViewVariables]
internal short Ping { get; set; }
/// <inheritdoc />
[ViewVariables]
public INetChannel ConnectedClient { get; internal set; } = null!;
/// <inheritdoc />

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Threading.Tasks;
using Robust.Client.Upload.Commands;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Replays;
@@ -250,36 +251,56 @@ public sealed partial class ReplayLoadManager
continue;
message.Messages.RemoveSwap(i);
var changed = new Dictionary<Type, HashSet<string>>();
_protoMan.LoadString(protoUpload.PrototypeData, true, changed);
foreach (var (kind, ids) in changed)
try
{
var protos = prototypes[kind];
var count = protos.Count;
protos.UnionWith(ids);
if (!ignoreDuplicates && ids.Count + count != protos.Count)
{
// An existing prototype was overwritten. Much like for resource uploading, supporting this
// requires tracking the last-modified time of prototypes and either resetting or applying
// prototype changes when jumping around in time. This also requires reworking how the initial
// implicit state data is generated, because we can't simply cache it anymore.
// Also, does reloading prototypes in release mode modify existing entities?
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
_sawmill.Error(msg);
else
throw new NotSupportedException(msg);
}
LoadPrototype(protoUpload.PrototypeData, prototypes, ignoreDuplicates);
}
catch (Exception e)
{
if (e is NotSupportedException || !_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw;
_protoMan.ResolveResults();
_protoMan.ReloadPrototypes(changed);
_locMan.ReloadLocalizations();
var msg = $"Caught exception while parsing uploaded prototypes in a replay. Exception: {e}";
_sawmill.Error(msg);
}
}
}
private void LoadPrototype(
string data,
Dictionary<Type, HashSet<string>> prototypes,
bool ignoreDuplicates)
{
var changed = new Dictionary<Type, HashSet<string>>();
_protoMan.LoadString(data, true, changed);
foreach (var (kind, ids) in changed)
{
var protos = prototypes[kind];
var count = protos.Count;
protos.UnionWith(ids);
if (!ignoreDuplicates && ids.Count + count != protos.Count)
{
// An existing prototype was overwritten. Much like for resource uploading, supporting this
// requires tracking the last-modified time of prototypes and either resetting or applying
// prototype changes when jumping around in time. This also requires reworking how the initial
// implicit state data is generated, because we can't simply cache it anymore.
// Also, does reloading prototypes in release mode modify existing entities?
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
_sawmill.Error(msg);
else
throw new NotSupportedException(msg);
}
}
_protoMan.ResolveResults();
_protoMan.ReloadPrototypes(changed);
_locMan.ReloadLocalizations();
}
private void UpdateDeletions(NetListAsArray<EntityUid> entityDeletions,
Dictionary<EntityUid, EntityState> entStates, HashSet<EntityUid> detached)
{

View File

@@ -10,25 +10,26 @@
<RobustILLink>true</RobustILLink>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" PrivateAssets="compile" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" />
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" PrivateAssets="compile" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
<PackageReference Include="Robust.Natives" Version="0.1.1" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" PrivateAssets="compile" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" PrivateAssets="compile" />
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
</ItemGroup>

View File

@@ -89,7 +89,7 @@ namespace Robust.Client.UserInterface.Controls
public void AddMessage(FormattedMessage message)
{
var entry = new RichTextEntry(message, this, _tagManager);
var entry = new RichTextEntry(message, this, _tagManager, null);
entry.Update(_getFont(), _getContentBox().Width, UIScale);

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using System;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.RichText;
@@ -21,18 +22,18 @@ namespace Robust.Client.UserInterface.Controls
IoCManager.InjectDependencies(this);
}
public void SetMessage(FormattedMessage message)
public void SetMessage(FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
{
_message = message;
_entry = new RichTextEntry(_message, this, _tagManager);
_entry = new RichTextEntry(_message, this, _tagManager, tagsAllowed, defaultColor);
InvalidateMeasure();
}
public void SetMessage(string message)
public void SetMessage(string message, Type[]? tagsAllowed = null, Color? defaultColor = null)
{
var msg = new FormattedMessage();
msg.AddText(message);
SetMessage(msg);
SetMessage(msg, tagsAllowed, defaultColor);
}
public string? GetMessage() => _message?.ToMarkup();

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
public const string LeftButtonStyle = "spinbox-left";
public const string RightButtonStyle = "spinbox-right";
public const string MiddleButtonStyle = "spinbox-middle";
private LineEdit _lineEdit;
public LineEdit LineEditControl { get; }
private List<Button> _leftButtons = new();
private List<Button> _rightButtons = new();
private int _stepSize = 1;
@@ -35,7 +35,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
_value = value;
_lineEdit.Text = value.ToString();
LineEditControl.Text = value.ToString();
ValueChanged?.Invoke(new ValueChangedEventArgs(value));
}
}
@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
_value = value;
_lineEdit.Text = value.ToString();
LineEditControl.Text = value.ToString();
}
public event Action<ValueChangedEventArgs>? ValueChanged;
@@ -62,17 +62,17 @@ namespace Robust.Client.UserInterface.Controls
Orientation = LayoutOrientation.Horizontal;
MouseFilter = MouseFilterMode.Pass;
_lineEdit = new LineEdit
LineEditControl = new LineEdit
{
MinSize = new Vector2(40, 0),
HorizontalExpand = true
};
AddChild(_lineEdit);
AddChild(LineEditControl);
Value = 0;
_lineEdit.IsValid = (str) => int.TryParse(str, out var i) && (IsValid == null || IsValid(i));
_lineEdit.OnTextChanged += (args) =>
LineEditControl.IsValid = (str) => int.TryParse(str, out var i) && (IsValid == null || IsValid(i));
LineEditControl.OnTextChanged += (args) =>
{
if (int.TryParse(args.Text, out int i))
Value = i;
@@ -143,8 +143,8 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public bool LineEditDisabled
{
get => !_lineEdit.Editable;
set => _lineEdit.Editable = !value;
get => !LineEditControl.Editable;
set => LineEditControl.Editable = !value;
}
/// <summary>
@@ -185,7 +185,7 @@ namespace Robust.Client.UserInterface.Controls
{
base.MouseWheel(args);
if (!_lineEdit.HasKeyboardFocus())
if (!LineEditControl.HasKeyboardFocus())
{
return;
}

View File

@@ -65,7 +65,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
EntityCoordinates mouseGridPos;
TileRef tile;
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
if (mouseWorldMap == MapCoordinates.Nullspace)
return;

View File

@@ -24,6 +24,11 @@ namespace Robust.Client.UserInterface.CustomControls
/// </param>
MapCoordinates ScreenToMap(Vector2 coords);
/// <summary>
/// Similar to <see cref="ScreenToMap(Vector2)"/>, except it should compensate for the effects of shaders on viewports.
/// </summary>
MapCoordinates PixelToMap(Vector2 point);
/// <summary>
/// Converts a point on the map to screen coordinates.
/// </summary>

View File

@@ -0,0 +1,34 @@
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using System.Numerics;
namespace Robust.Client.UserInterface.CustomControls;
/// <summary>
/// An event used to reverse distortion effects applied by shaders.
/// Used to find the map position that visible pixels originate from so that severe distortion shaders do not make interaction nigh-impossible.
/// </summary>
[ByRefEvent]
public record struct PixelToMapEvent(Vector2 LocalPosition, IViewportControl Control, IClydeViewport Viewport)
{
/// <summary>
/// The local position of the pixel within the <see cref="Control"/> that we are trying to convert to a map position.
/// </summary>
public readonly Vector2 LocalPosition = LocalPosition;
/// <summary>
/// The original (or WIP) location of the pixel within the <see cref="Control"/> that we are trying to convert to a map position.
/// Used as the output of the event.
/// </summary>
public Vector2 VisiblePosition = LocalPosition;
/// <summary>
/// The control the pixel we are considering is located within.
/// </summary>
public readonly IViewportControl Control = Control;
/// <summary>
/// The viewport being displayed by the control we are considering.
/// </summary>
public readonly IClydeViewport Viewport = Viewport;
}

View File

@@ -1,6 +1,7 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -13,8 +14,9 @@ namespace Robust.Client.UserInterface.CustomControls
[Virtual]
public class ViewportContainer : Control, IViewportControl
{
private readonly IClyde _displayManager;
private readonly IInputManager _inputManager;
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
public IClydeViewport? Viewport { get; set; }
@@ -36,8 +38,7 @@ namespace Robust.Client.UserInterface.CustomControls
public ViewportContainer()
{
_displayManager = IoCManager.Resolve<IClyde>();
_inputManager = IoCManager.Resolve<IInputManager>();
IoCManager.InjectDependencies(this);
MouseFilter = MouseFilterMode.Stop;
Resized();
}
@@ -99,8 +100,7 @@ namespace Robust.Client.UserInterface.CustomControls
// -- Handlers: In --
// -- Utils / S2M-M2S Base --
public MapCoordinates LocalPixelToMap(Vector2 point)
public MapCoordinates LocalCoordsToMap(Vector2 point)
{
if (Viewport == null)
return default;
@@ -111,6 +111,19 @@ namespace Robust.Client.UserInterface.CustomControls
return Viewport.LocalToWorld(point);
}
public MapCoordinates LocalPixelToMap(Vector2 point)
{
if (Viewport == null)
return default;
// pre-scaler
point *= _viewportResolution;
var ev = new PixelToMapEvent(point, this, Viewport);
_entityManager.EventBus.RaiseEvent(EventSource.Local, ref ev);
return Viewport.LocalToWorld(ev.VisiblePosition);
}
public Vector2 WorldToLocalPixel(Vector2 point)
{
if (Viewport?.Eye == null)
@@ -127,6 +140,12 @@ namespace Robust.Client.UserInterface.CustomControls
// -- Utils / S2M-M2S Extended --
public MapCoordinates ScreenToMap(Vector2 point)
{
return LocalCoordsToMap(point - GlobalPixelPosition);
}
/// <inheritdoc/>
public MapCoordinates PixelToMap(Vector2 point)
{
return LocalPixelToMap(point - GlobalPixelPosition);
}

View File

@@ -0,0 +1,29 @@
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
public sealed class BoldItalicTag : IMarkupTag
{
public const string BoldItalicFont = "DefaultBoldItalic";
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string Name => "bolditalic";
/// <inheritdoc/>
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
{
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldItalicFont);
context.Font.Push(font);
}
/// <inheritdoc/>
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
{
context.Font.Pop();
}
}

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
@@ -14,12 +15,18 @@ public sealed class BoldTag : IMarkupTag
public string Name => "bold";
/// <inheritdoc/>
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
{
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldFont);
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager,
context.Tags.Any(static x => x is ItalicTag)
? BoldItalicTag.BoldItalicFont
: BoldFont
);
context.Font.Push(font);
}
/// <inheritdoc/>
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
{
context.Font.Pop();

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
public sealed class BulletTag : IMarkupTag
{
public string Name => "bullet";
/// <inheritdoc/>
public string TextBefore(MarkupNode _) => " · ";
}

View File

@@ -8,7 +8,7 @@ namespace Robust.Client.UserInterface.RichText;
/// </summary>
public sealed class ColorTag : IMarkupTag
{
private static readonly Color DefaultColor = new(200, 200, 200);
public static readonly Color DefaultColor = new(200, 200, 200);
public string Name => "color";

View File

@@ -14,12 +14,14 @@ namespace Robust.Client.UserInterface.RichText;
public sealed class FontTag : IMarkupTag
{
public const string DefaultFont = "Default";
public const int DefaultSize = 12;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string Name => "font";
/// <inheritdoc/>
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
{
string fontId = node.Value.StringValue ?? DefaultFont;
@@ -28,6 +30,7 @@ public sealed class FontTag : IMarkupTag
context.Font.Push(font);
}
/// <inheritdoc/>
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
{
context.Font.Pop();
@@ -44,7 +47,7 @@ public sealed class FontTag : IMarkupTag
IPrototypeManager prototypeManager,
string fontId)
{
var size = 12;
var size = DefaultSize;
if (contextFontStack.TryPeek(out var previousFont))
{

View File

@@ -0,0 +1,36 @@
using System;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
public sealed class HeadingTag : IMarkupTag
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string Name => "head";
/// <inheritdoc/>
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
{
if (!node.Value.TryGetLong(out var levelParam))
return;
var level = Math.Min(Math.Max((int)levelParam, 1), 3);
node.Attributes["size"] = new MarkupParameter(
(int)Math.Ceiling(FontTag.DefaultSize * 2 / Math.Sqrt(level))
);
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldTag.BoldFont);
context.Font.Push(font);
}
/// <inheritdoc/>
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
{
context.Font.Pop();
}
}

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
@@ -11,14 +12,21 @@ public sealed class ItalicTag : IMarkupTag
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string Name => "italic";
/// <inheritdoc/>
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
{
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, ItalicFont);
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager,
context.Tags.Any(static x => x is BoldTag)
? BoldItalicTag.BoldItalicFont
: ItalicFont
);
context.Font.Push(font);
}
/// <inheritdoc/>
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
{
context.Font.Pop();

View File

@@ -8,22 +8,26 @@ public sealed class MarkupDrawingContext
{
public readonly Stack<Color> Color;
public readonly Stack<Font> Font;
public readonly List<IMarkupTag> Tags;
public MarkupDrawingContext()
{
Color = new Stack<Color>();
Font = new Stack<Font>();
Tags = new List<IMarkupTag>();
}
public MarkupDrawingContext(int capacity)
{
Color = new Stack<Color>(capacity);
Font = new Stack<Font>(capacity);
Tags = new List<IMarkupTag>();
}
public void Clear()
{
Color.Clear();
Font.Clear();
Tags.Clear();
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
@@ -15,24 +16,29 @@ public sealed class MarkupTagManager
/// <summary>
/// Tags defined in engine need to be instantiated here because of sandboxing
/// </summary>
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new()
{
{"color", new ColorTag()},
{"cmdlink", new CommandLinkTag()},
{"font", new FontTag()},
{"bold", new BoldTag()},
{"italic", new ItalicTag()}
};
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new IMarkupTag[] {
new BoldItalicTag(),
new BoldTag(),
new BulletTag(),
new ColorTag(),
new CommandLinkTag(),
new FontTag(),
new HeadingTag(),
new ItalicTag()
}.ToDictionary(x => x.Name.ToLower(), x => x);
/// <summary>
/// A list of <see cref="IMarkupTag"/> types that shouldn't be instantiated through reflection
/// </summary>
private readonly List<Type> _engineTypes = new()
{
typeof(BoldItalicTag),
typeof(BoldTag),
typeof(BulletTag),
typeof(ColorTag),
typeof(CommandLinkTag),
typeof(FontTag),
typeof(BoldTag),
typeof(HeadingTag),
typeof(ItalicTag)
};
@@ -59,9 +65,11 @@ public sealed class MarkupTagManager
return _markupTagTypes.GetValueOrDefault(name);
}
public bool TryGetMarkupTag(string name, [NotNullWhen(true)] out IMarkupTag? tag)
public bool TryGetMarkupTag(string name, Type[]? tagsAllowed, [NotNullWhen(true)] out IMarkupTag? tag)
{
if (_markupTagTypes.TryGetValue(name, out var markupTag))
if (_markupTagTypes.TryGetValue(name, out var markupTag)
// Using a whitelist prevents new tags from sneaking in.
&& (tagsAllowed == null || Array.IndexOf(tagsAllowed, markupTag.GetType()) != -1))
{
tag = markupTag;
return true;

View File

@@ -16,9 +16,9 @@ namespace Robust.Client.UserInterface
/// </summary>
internal struct RichTextEntry
{
private static readonly Color DefaultColor = new(200, 200, 200);
private readonly Color _defaultColor;
private readonly MarkupTagManager _tagManager;
private readonly Type[]? _tagsAllowed;
public readonly FormattedMessage Message;
@@ -39,13 +39,15 @@ namespace Robust.Client.UserInterface
private readonly Dictionary<int, Control> _tagControls = new();
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager)
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
{
Message = message;
Height = 0;
Width = 0;
LineBreaks = default;
_defaultColor = defaultColor ?? new(200, 200, 200);
_tagManager = tagManager;
_tagsAllowed = tagsAllowed;
var nodeIndex = -1;
foreach (var node in Message.Nodes)
@@ -55,7 +57,7 @@ namespace Robust.Client.UserInterface
if (node.Name == null)
continue;
if (!_tagManager.TryGetMarkupTag(node.Name, out var tag) || !tag.TryGetControl(node, out var control))
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
continue;
parent.Children.Add(control);
@@ -82,7 +84,7 @@ namespace Robust.Client.UserInterface
var wordWrap = new WordWrap(maxSizeX);
var context = new MarkupDrawingContext();
context.Font.Push(defaultFont);
context.Color.Push(DefaultColor);
context.Color.Push(_defaultColor);
// Go over every node.
// Nodes can change the markup drawing context and return additional text.
@@ -171,7 +173,7 @@ namespace Robust.Client.UserInterface
float uiScale)
{
context.Clear();
context.Color.Push(DefaultColor);
context.Color.Push(_defaultColor);
context.Font.Push(defaultFont);
var globalBreakCounter = 0;
@@ -186,7 +188,7 @@ namespace Robust.Client.UserInterface
var text = ProcessNode(node, context);
if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font))
{
color = DefaultColor;
color = _defaultColor;
font = defaultFont;
}
@@ -226,15 +228,17 @@ namespace Robust.Client.UserInterface
return node.Value.StringValue ?? "";
//Skip the node if there is no markup tag for it.
if (!_tagManager.TryGetMarkupTag(node.Name, out var tag))
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
return "";
if (!node.Closing)
{
context.Tags.Add(tag);
tag.PushDrawContext(node, context);
return tag.TextBefore(node);
}
context.Tags.Remove(tag);
tag.PopDrawContext(node, context);
return tag.TextAfter(node);
}

View File

@@ -29,7 +29,7 @@ internal partial class UserInterfaceManager
public void SetActiveTheme(string themeName)
{
if (!_themes.TryGetValue(themeName, out var theme) || (theme == CurrentTheme)) return;
CurrentTheme = theme;
UpdateTheme(theme);
}
public void SetDefaultTheme(string themeId)

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects;
/// <summary>
/// Lets any entities with this component ignore user interface range checks that would normally
/// close the UI automatically.
/// </summary>
[RegisterComponent]
public sealed class IgnoreUIRangeComponent : Component
{
}

View File

@@ -17,6 +17,8 @@ namespace Robust.Server.GameObjects
[Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly TransformSystem _xformSys = default!;
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
private readonly List<IPlayerSession> _sessionCache = new();
private readonly Dictionary<IPlayerSession, List<BoundUserInterface>> _openInterfaces = new();
@@ -30,6 +32,8 @@ namespace Robust.Server.GameObjects
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
}
public override void Shutdown()
@@ -174,6 +178,9 @@ namespace Robust.Server.GameObjects
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
continue;
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
continue;
if (uiMap != xform.MapID)
{
CloseUi(ui, session, activeUis);

View File

@@ -81,30 +81,35 @@ namespace Robust.Server.GameObjects
private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null)
{
var entity = base.CreateEntity(prototypeName, uid, context);
if (prototypeName == null)
return base.CreateEntity(prototypeName, uid, context);
if (!string.IsNullOrWhiteSpace(prototypeName))
{
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
// which indicates "not different from client's own deserialization".
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
foreach (var (netId, component) in GetNetComponents(entity))
{
// Make sure to ONLY get components that are defined in the prototype.
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
// And those aren't guaranteed to exist on the client, so don't clear them.
var compName = ComponentFactory.GetComponentName(component.GetType());
if (prototype.Components.ContainsKey(compName))
component.ClearTicks();
}
}
var entity = base.CreateEntity(prototype, uid, context);
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
// which indicates "not different from client's own deserialization".
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
ClearTicks(entity, prototype);
return entity;
}
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
{
foreach (var (netId, component) in GetNetComponents(entity))
{
// Make sure to ONLY get components that are defined in the prototype.
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
// And those aren't guaranteed to exist on the client, so don't clear them.
var compName = ComponentFactory.GetComponentName(netId);
if (prototype.Components.ContainsKey(compName))
component.ClearTicks();
}
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
{
TryGetComponent(uid, out ActorComponent? actor);

View File

@@ -1034,26 +1034,20 @@ internal sealed partial class PvsSystem : EntitySystem
while (query.MoveNext(out var uid, out var md))
{
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
if (md.EntityLastModifiedTick <= fromTick)
continue;
var state = GetEntityState(player, uid, fromTick, md);
// Temporary debugging code.
// TODO REMOVE TEMPORARY CODE
if (state.Empty)
{
var msg = $"{nameof(GetEntityState)} returned an empty state while enumerating all. Entity {ToPrettyString(uid)}. Net component Data:";
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
{
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
$"Enabled: {cmp.NetSyncEnabled}, " +
$"Lifestage: {cmp.LifeStage}, " +
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
$"SessionSpecific: {cmp.SessionSpecific}, " +
$"LastModified: {cmp.LastModifiedTick}";
}
Log.Error(msg);
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
Tick: {fromTick}--{_gameTiming.CurTick}
Entity: {ToPrettyString(uid)}
Last modified: {md.EntityLastModifiedTick}
Metadata last modified: {md.LastModifiedTick}
Transform last modified: {Transform(uid).LastModifiedTick}");
}
stateEntities.Add(state);
@@ -1077,26 +1071,21 @@ internal sealed partial class PvsSystem : EntitySystem
continue;
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
var state = GetEntityState(player, uid, fromTick, md);
// Temporary debugging code.
// TODO REMOVE TEMPORARY CODE
if (state.Empty)
{
var msg = $"{nameof(GetEntityState)} returned an empty state for new entity {ToPrettyString(uid)}. Net component Data:";
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
{
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
$"Enabled: {cmp.NetSyncEnabled}, " +
$"Lifestage: {cmp.LifeStage}, " +
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
$"SessionSpecific: {cmp.SessionSpecific}, " +
$"LastModified: {cmp.LastModifiedTick}";
}
Log.Error(msg);
Log.Error($@"{nameof(GetEntityState)} returned an empty state for a new entity.
Tick: {fromTick}--{_gameTiming.CurTick}
Entity: {ToPrettyString(uid)}
Last modified: {md.EntityLastModifiedTick}
Metadata last modified: {md.LastModifiedTick}
Transform last modified: {Transform(uid).LastModifiedTick}");
continue;
}
stateEntities.Add(state);
@@ -1109,8 +1098,9 @@ internal sealed partial class PvsSystem : EntitySystem
continue;
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
var state = GetEntityState(player, uid, fromTick, md);
if (!state.Empty)

View File

@@ -191,7 +191,7 @@ namespace Robust.Server.GameStates
using (_usageHistogram.WithLabels("Clean Dirty").NewTimer())
{
_pvs.CleanupDirty(_playerManager.ServerSessions);
_pvs.CleanupDirty(players);
}
// keep the deletion history buffers clean

View File

@@ -13,15 +13,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="SpaceWizards.HttpListener" Version="0.1.0" />
<!-- -->
<PackageReference Include="SpaceWizards.HttpListener" Version="0.1.0" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' != 'True'" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="6.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.2" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -29,7 +29,6 @@ using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using JetBrains.Annotations;
using Robust.Shared.Utility;
using SysVector3 = System.Numerics.Vector3;
@@ -1638,6 +1637,11 @@ namespace Robust.Shared.Maths
/// </summary>
public static Color RoyalBlue => new(65, 105, 225, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (204, 71, 120, 255).
/// </summary>
public static Color Ruber => new(204, 71, 120, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (139, 69, 19, 255).
/// </summary>
@@ -1653,6 +1657,11 @@ namespace Robust.Shared.Maths
/// </summary>
public static Color SandyBrown => new(244, 164, 96, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (0, 66, 153, 255).
/// </summary>
public static Color SeaBlue => new(0, 66, 153, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (46, 139, 87, 255).
/// </summary>
@@ -1733,6 +1742,16 @@ namespace Robust.Shared.Maths
/// </summary>
public static Color Violet => new(238, 130, 238, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (126, 3, 168, 255).
/// </summary>
public static Color BetterViolet => new(126, 3, 168, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (255, 153, 0, 255).
/// </summary>
public static Color VividGamboge => new(255, 153, 0, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (245, 222, 179, 255).
/// </summary>
@@ -1767,6 +1786,7 @@ namespace Robust.Shared.Maths
["aquamarine"] = Aquamarine,
["azure"] = Azure,
["beige"] = Beige,
["betterviolet"] = BetterViolet,
["bisque"] = Bisque,
["black"] = Black,
["blanchedalmond"] = BlanchedAlmond,
@@ -1877,9 +1897,11 @@ namespace Robust.Shared.Maths
["red"] = Red,
["rosybrown"] = RosyBrown,
["royalblue"] = RoyalBlue,
["ruber"] = Ruber,
["saddlebrown"] = SaddleBrown,
["salmon"] = Salmon,
["sandybrown"] = SandyBrown,
["seablue"] = SeaBlue,
["seagreen"] = SeaGreen,
["seashell"] = SeaShell,
["sienna"] = Sienna,
@@ -1896,6 +1918,7 @@ namespace Robust.Shared.Maths
["tomato"] = Tomato,
["turquoise"] = Turquoise,
["violet"] = Violet,
["vividgamboge"] = VividGamboge,
["wheat"] = Wheat,
["white"] = White,
["whitesmoke"] = WhiteSmoke,

View File

@@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Properties.targets" />

View File

@@ -283,6 +283,12 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<float> NetFakeDuplicates = CVarDef.Create("net.fakeduplicates", 0f, CVar.CHEAT);
/// <summary>
/// When using Happy Eyeballs to try both IPv6 over IPv4, the delay that IPv4 gets to get less priority.
/// </summary>
public static readonly CVarDef<float> NetHappyEyeballsDelay =
CVarDef.Create("net.happy_eyeballs_delay", 0.025f, CVar.CLIENTONLY);
/**
* SUS
*/
@@ -1189,10 +1195,10 @@ namespace Robust.Shared
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
public static readonly CVarDef<string> DiscordRichPresenceMainIconId =
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.CLIENTONLY);
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.SERVER | CVar.REPLICATED);
public static readonly CVarDef<string> DiscordRichPresenceSecondIconId =
CVarDef.Create("discord.rich_second_icon_id", "logo", CVar.CLIENTONLY);
CVarDef.Create("discord.rich_second_icon_id", "logo", CVar.SERVER | CVar.REPLICATED);
/*
* RES

View File

@@ -100,9 +100,15 @@ namespace Robust.Shared.GameObjects
/// </remarks>
internal void LifeShutdown(IEntityManager entManager)
{
// Starting allows a component to remove itself in it's own Startup function.
DebugTools.Assert(LifeStage == ComponentLifeStage.Starting || LifeStage == ComponentLifeStage.Running);
DebugTools.Assert(LifeStage is >= ComponentLifeStage.Initializing and < ComponentLifeStage.Stopping);
if (LifeStage <= ComponentLifeStage.Initialized)
{
// Component was never started, no shutdown logic necessary. Simply mark it as stopped.
LifeStage = ComponentLifeStage.Stopped;
return;
}
LifeStage = ComponentLifeStage.Stopping;
entManager.EventBus.RaiseComponentEvent(this, CompShutdownInstance);
LifeStage = ComponentLifeStage.Stopped;

View File

@@ -328,6 +328,12 @@ namespace Robust.Shared.GameObjects
return GetRegistration(componentType).Name;
}
[Pure]
public string GetComponentName(ushort netID)
{
return GetRegistration(netID).Name;
}
public ComponentRegistration GetRegistration(ushort netID)
{
if (_networkedComponents is null)

View File

@@ -71,7 +71,7 @@ namespace Robust.Shared.GameObjects
// Every entity starts at tick 1, because they are conceptually created in the time between 0->1
[ViewVariables]
public GameTick EntityLastModifiedTick { get; internal set; } = GameTick.Zero;
public GameTick EntityLastModifiedTick { get; internal set; } = GameTick.First;
/// <summary>
/// This is the tick at which the client last applied state data received from the server.

View File

@@ -131,7 +131,7 @@ namespace Robust.Shared.GameObjects
foreach (var comp in comps)
{
if (comp is { LifeStage: < ComponentLifeStage.Initialized })
if (comp is { LifeStage: ComponentLifeStage.Added })
comp.LifeInitialize(this, CompIdx.Index(comp.GetType()));
}
@@ -490,7 +490,7 @@ namespace Robust.Shared.GameObjects
return;
}
if (component.Running)
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
component.LifeShutdown(this);
#if EXCEPTION_TOLERANCE
}
@@ -1409,6 +1409,13 @@ namespace Robust.Shared.GameObjects
return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent(EntityUid? uid)
{
return uid != null && HasComponent(uid.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)

View File

@@ -661,6 +661,15 @@ namespace Robust.Shared.GameObjects
return uid.HasValue && EntityExists(uid.Value);
}
/// <inheritdoc />
public bool IsPaused(EntityUid? uid, MetaDataComponent? metadata = null)
{
if (uid == null)
return false;
return _metaQuery.Resolve(uid.Value, ref metadata) && metadata.EntityPaused;
}
public bool Deleted(EntityUid uid)
{
return !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
@@ -742,8 +751,17 @@ namespace Robust.Shared.GameObjects
if (prototypeName == null)
return AllocEntity(out _, uid);
PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
return CreateEntity(prototype, uid, context);
}
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected EntityUid CreateEntity(EntityPrototype prototype, EntityUid uid = default, IEntityLoadContext? context = null)
{
var entity = AllocEntity(prototype, out var metadata, uid);
try
{
@@ -755,7 +773,7 @@ namespace Robust.Shared.GameObjects
// Exception during entity loading.
// Need to delete the entity to avoid corrupt state causing crashes later.
DeleteEntity(entity);
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototypeName}", e);
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototype.ID}", e);
}
}

View File

@@ -182,6 +182,12 @@ public partial class EntitySystem
#region Entity Metadata
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool IsPaused(EntityUid? uid, MetaDataComponent? metadata = null)
{
return EntityManager.IsPaused(uid, metadata);
}
/// <summary>
/// Marks an entity as dirty.
/// </summary>

View File

@@ -156,6 +156,17 @@ namespace Robust.Shared.GameObjects
/// </exception>
[Pure]
string GetComponentName(Type componentType);
/// <summary>
/// Gets the name of a component, throwing an exception if it does not exist.
/// </summary>
/// <param name="netID">The network ID corresponding to the component.</param>
/// <returns>The registered name of the component</returns>
/// <exception cref="UnknownComponentException">
/// Thrown if no component with id <see cref="netID"/> exists.
/// </exception>
[Pure]
string GetComponentName(ushort netID);
/// <summary>
/// Gets the registration belonging to a component, throwing an exception if it does not exist.

View File

@@ -134,6 +134,11 @@ namespace Robust.Shared.GameObjects
/// </summary>
bool EntityExists([NotNullWhen(true)] EntityUid? uid);
/// <summary>
/// Returns true if entity is valid and paused.
/// </summary>
bool IsPaused([NotNullWhen(true)] EntityUid? uid, MetaDataComponent? metadata = null);
/// <summary>
/// Checks whether an entity with the specified ID has been deleted or is nonexistent.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Net;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Network
{
@@ -28,10 +29,13 @@ namespace Robust.Shared.Network
/// On the server, this is the session ID for this client.
/// On the client, this is the session ID for the client.
/// </summary>
[ViewVariables]
NetUserId UserId { get; }
[ViewVariables]
string UserName { get; }
[ViewVariables]
LoginType AuthType { get; }
/// <summary>
@@ -47,11 +51,13 @@ namespace Robust.Shared.Network
/// <summary>
/// Average round trip time in milliseconds between the remote peer and us.
/// </summary>
[ViewVariables]
short Ping { get; }
/// <summary>
/// Whether or not the channel is currently connected to a remote peer.
/// </summary>
[ViewVariables]
bool IsConnected { get; }
NetUserData UserData { get; }

View File

@@ -324,7 +324,8 @@ namespace Robust.Shared.Network
{
DebugTools.AssertNotNull(second);
// Connecting via second peer is delayed by 25ms to give an advantage to IPv6, if it works.
await Task.Delay(25, cancellationToken);
var delay = TimeSpan.FromSeconds(_config.GetCVar(CVars.NetHappyEyeballsDelay));
await Task.Delay(delay, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
return;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Collections;
using Robust.Shared.Utility;
namespace Robust.Shared.Random
@@ -29,6 +30,12 @@ namespace Robust.Shared.Random
return list[index];
}
public static ref T Pick<T>(this IRobustRandom random, ValueList<T> list)
{
var index = random.Next(list.Count);
return ref list[index];
}
/// <summary>Picks a random element from a collection.</summary>
/// <remarks>
/// This is O(n).

View File

@@ -8,19 +8,19 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.2" />
<PackageReference Include="Microsoft.ILVerification" Version="6.0.0" />
<PackageReference Include="Nett" Version="0.15.0" />
<PackageReference Include="Microsoft.ILVerification" Version="6.0.0" PrivateAssets="compile" />
<PackageReference Include="Nett" Version="0.15.0" PrivateAssets="compile" />
<PackageReference Include="Pidgin" Version="2.5.0" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Robust.Shared.AuthLib" Version="0.1.2" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="YamlDotNet" Version="12.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" PrivateAssets="compile" />
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />

View File

@@ -44,14 +44,20 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
continue;
case ComponentAvailability.Unknown:
Logger.ErrorS(SerializationManager.LogCategory, $"Unknown component '{compType}' in prototype!");
dependencies
.Resolve<ILogManager>()
.GetSawmill(SerializationManager.LogCategory)
.Error($"Unknown component '{compType}' in prototype!");
continue;
}
// Has this type already been added?
if (components.ContainsKey(compType))
{
Logger.ErrorS(SerializationManager.LogCategory, $"Component of type '{compType}' defined twice in prototype!");
dependencies
.Resolve<ILogManager>()
.GetSawmill(SerializationManager.LogCategory)
.Error(SerializationManager.LogCategory, $"Component of type '{compType}' defined twice in prototype!");
continue;
}

View File

@@ -37,18 +37,22 @@ public abstract class SharedPrototypeLoadManager : IGamePrototypeLoadManager
protected virtual void LoadPrototypeData(GamePrototypeLoadMessage message)
{
var data = message.PrototypeData;
LoadedPrototypes.Add(data);
_replay.RecordReplayMessage(new ReplayPrototypeUploadMsg { PrototypeData = data });
// TODO validate yaml before loading?
var changed = new Dictionary<Type, HashSet<string>>();
_prototypeManager.LoadString(data, true, changed);
_prototypeManager.ResolveResults();
_prototypeManager.ReloadPrototypes(changed);
_localizationManager.ReloadLocalizations();
// Add to replay recording after we have loaded the file, in case it contains bad yaml that throws exceptions.
LoadedPrototypes.Add(data);
_replay.RecordReplayMessage(new ReplayPrototypeUploadMsg { PrototypeData = data });
_sawmill.Info("Loaded adminbus prototype data.");
}
private void OnStartReplayRecording(MappingDataNode metadat, List<object> events)
private void OnStartReplayRecording(MappingDataNode metadata, List<object> events)
{
foreach (var prototype in LoadedPrototypes)
{

View File

@@ -3,6 +3,7 @@ using Pidgin;
using Robust.Shared.Maths;
using static Pidgin.Parser;
using static Pidgin.Parser<char>;
namespace Robust.Shared.Utility;
public sealed partial class FormattedMessage
@@ -65,7 +66,7 @@ public sealed partial class FormattedMessage
//This checks for a backslash and one reserved character
private static readonly Parser<char, char> EscapeSequence =
Escape.Then(OneOf(Escape, Begin, End, Slash));
Try(Escape.Then(OneOf(Escape, Begin, End, Slash)));
//Parses text by repeatedly parsing escape sequences or any character except [ and \
//The result is put into a new markup node representing text (it has no name)

View File

@@ -13,6 +13,7 @@ using Moq;
using NUnit.Framework;
using Robust.Client;
using Robust.Client.GameStates;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Client.UserInterface;
using Robust.Server;
@@ -26,7 +27,10 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using ServerProgram = Robust.Server.Program;
@@ -259,6 +263,38 @@ namespace Robust.UnitTesting
public virtual IntegrationOptions? Options { get; internal set; }
public IEntityManager EntMan { get; private set; } = default!;
public IPrototypeManager ProtoMan { get; private set; } = default!;
public IConfigurationManager CfgMan { get; private set; } = default!;
public ISharedPlayerManager PlayerMan { get; private set; } = default!;
public IGameTiming Timing { get; private set; } = default!;
public IMapManager MapMan { get; private set; } = default!;
protected virtual void ResolveIoC(IDependencyCollection deps)
{
EntMan = deps.Resolve<IEntityManager>();
ProtoMan = deps.Resolve<IPrototypeManager>();
CfgMan = deps.Resolve<IConfigurationManager>();
PlayerMan = deps.Resolve<ISharedPlayerManager>();
Timing = deps.Resolve<IGameTiming>();
MapMan = deps.Resolve<IMapManager>();
}
public T System<T>() where T : IEntitySystem
{
return EntMan.System<T>();
}
public TransformComponent Transform(EntityUid uid)
{
return EntMan.GetComponent<TransformComponent>(uid);
}
public MetaDataComponent MetaData(EntityUid uid)
{
return EntMan.GetComponent<MetaDataComponent>(uid);
}
/// <summary>
/// Whether the instance is still alive.
/// "Alive" indicates that it is able to receive and process commands.
@@ -688,6 +724,7 @@ namespace Robust.UnitTesting
server.SetupMainLoop();
GameLoop.RunInit();
ResolveIoC(deps);
return server;
}
@@ -695,6 +732,11 @@ namespace Robust.UnitTesting
public sealed class ClientIntegrationInstance : IntegrationInstance
{
public LocalPlayer? Player => ((IPlayerManager) PlayerMan).LocalPlayer;
public ICommonSession? Session => Player?.Session;
public NetUserId? User => Session?.UserId;
public EntityUid? AttachedEntity => Session?.AttachedEntity;
public ClientIntegrationInstance(ClientIntegrationOptions? options) : base(options)
{
ClientOptions = options;
@@ -859,6 +901,7 @@ namespace Robust.UnitTesting
client.StartupContinue(GameController.DisplayMode.Headless);
GameLoop.RunInit();
ResolveIoC(deps);
return client;
}

View File

@@ -65,12 +65,12 @@ public sealed class PvsSystemTests : RobustIntegrationTest
var mapCoords = new EntityCoordinates(map, new Vector2(2, 2));
await server.WaitPost(() =>
{
player = sEntMan.SpawnEntity("", gridCoords);
other = sEntMan.SpawnEntity("", gridCoords);
player = sEntMan.SpawnEntity(null, gridCoords);
other = sEntMan.SpawnEntity(null, gridCoords);
otherXform = sEntMan.GetComponent<TransformComponent>(other);
// Ensure map PVS chunk is not empty
sEntMan.SpawnEntity("", mapCoords);
sEntMan.SpawnEntity(null, mapCoords);
// Attach player.
var session = (IPlayerSession) sPlayerMan.Sessions.First();

View File

@@ -10,7 +10,6 @@ using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.UnitTesting.Server.Maps
{
@@ -59,18 +58,19 @@ entities:
[OneTimeSetUp]
public void Setup()
{
// For some reason RobustUnitTest doesn't discover PVSSystem but this does here so ?
var syssy = IoCManager.Resolve<IEntitySystemManager>();
syssy.Shutdown();
syssy.Initialize();
var compFactory = IoCManager.Resolve<IComponentFactory>();
compFactory.RegisterClass<MapDeserializeTestComponent>();
compFactory.RegisterClass<VisibilityComponent>();
compFactory.RegisterClass<ActorComponent>();
compFactory.RegisterClass<IgnoreUIRangeComponent>();
compFactory.GenerateNetIds();
IoCManager.Resolve<ISerializationManager>().Initialize();
// For some reason RobustUnitTest doesn't discover PVSSystem but this does here so ?
var syssy = IoCManager.Resolve<IEntitySystemManager>();
syssy.Shutdown();
syssy.Initialize();
var resourceManager = IoCManager.Resolve<IResourceManagerInternal>();
resourceManager.Initialize(null);
resourceManager.MountString("/TestMap.yml", MapData);

View File

@@ -84,7 +84,7 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
await server.WaitPost(() =>
{
var coords = new EntityCoordinates(grid1, new Vector2(0.5f, 0.5f));
player = sEntMan.SpawnEntity("", coords);
player = sEntMan.SpawnEntity(null, coords);
var session = (IPlayerSession) sPlayerMan.Sessions.First();
session.AttachToEntity(player);
session.JoinGame();
@@ -107,10 +107,10 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
var coords = new EntityCoordinates(grid2, new Vector2(0.5f, 0.5f));
await server.WaitPost(() =>
{
entA = sEntMan.SpawnEntity("", coords);
entB = sEntMan.SpawnEntity("", coords);
childA = sEntMan.SpawnEntity("", new EntityCoordinates(entA, default));
childB = sEntMan.SpawnEntity("", new EntityCoordinates(entB, default));
entA = sEntMan.SpawnEntity(null, coords);
entB = sEntMan.SpawnEntity(null, coords);
childA = sEntMan.SpawnEntity(null, new EntityCoordinates(entA, default));
childB = sEntMan.SpawnEntity(null, new EntityCoordinates(entB, default));
});
await RunTicks();
@@ -122,10 +122,10 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
EntityUid clientChildB = default;
await client.WaitPost(() =>
{
entC = cEntMan.SpawnEntity("", coords);
childC = cEntMan.SpawnEntity("", new EntityCoordinates(entC, default));
clientChildA = cEntMan.SpawnEntity("", new EntityCoordinates(entA, default));
clientChildB = cEntMan.SpawnEntity("", new EntityCoordinates(entB, default));
entC = cEntMan.SpawnEntity(null, coords);
childC = cEntMan.SpawnEntity(null, new EntityCoordinates(entC, default));
clientChildA = cEntMan.SpawnEntity(null, new EntityCoordinates(entA, default));
clientChildB = cEntMan.SpawnEntity(null, new EntityCoordinates(entB, default));
});
await RunTicks();

View File

@@ -202,7 +202,9 @@ namespace Robust.UnitTesting.Shared.Maths
Assert.That(sysColor, Is.EqualTo((System.Drawing.Color) color));
}
static IEnumerable<string> DefaultColorNames => Color.GetAllDefaultColors().Select(e => e.Key);
static IEnumerable<string> DefaultColorNames => Color.GetAllDefaultColors()
.Where(e => System.Drawing.Color.FromName(e.Key).IsKnownColor)
.Select(e => e.Key);
[Test]
public void GetAllDefaultColorsFromName([ValueSource(nameof(DefaultColorNames))] string colorName)

View File

@@ -74,7 +74,7 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
await server.WaitPost(() =>
{
var coords = new EntityCoordinates(grid1, new Vector2(0.5f, 0.5f));
player = sEntMan.SpawnEntity("", coords);
player = sEntMan.SpawnEntity(null, coords);
// Enable physics
var physics = sEntMan.AddComponent<PhysicsComponent>(player);

View File

@@ -12,7 +12,6 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
using SharpFont.PostScript;
namespace Robust.UnitTesting.Shared.Physics;

View File

@@ -2,7 +2,6 @@
using NUnit.Framework;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown.Mapping;
using TerraFX.Interop.Windows;
namespace Robust.UnitTesting.Shared.Serialization;