mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbb45f1c13 | ||
|
|
6284e16b64 | ||
|
|
f6c55085fe | ||
|
|
30eafd26e7 | ||
|
|
63423d96b4 | ||
|
|
474334aff2 | ||
|
|
be102f86bf | ||
|
|
d7962c7190 | ||
|
|
7fe9385c3b | ||
|
|
a9d9d1348a | ||
|
|
4eaf624555 | ||
|
|
e37c131fb4 | ||
|
|
9df4606492 | ||
|
|
3be8070274 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,31 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 148.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add recursive PVS overrides and remove IsOverride()
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -77,11 +77,21 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// </summary>
|
||||
private readonly HashSet<TIndex> _globalOverrides = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent along with all of their children.
|
||||
/// </summary>
|
||||
private readonly HashSet<TIndex> _globalRecursiveOverrides = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent.
|
||||
/// </summary>
|
||||
public HashSet<TIndex>.Enumerator GlobalOverridesEnumerator => _globalOverrides.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent along with all of their children.
|
||||
/// </summary>
|
||||
public HashSet<TIndex>.Enumerator GlobalRecursiveOverridesEnumerator => _globalRecursiveOverrides.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="TIndex"/> that should always get sent to a certain <see cref="ICommonSession"/>.
|
||||
/// </summary>
|
||||
@@ -203,8 +213,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
case GlobalOverride _:
|
||||
_globalOverrides.Add(index);
|
||||
case GlobalOverride global:
|
||||
if (global.Recursive)
|
||||
_globalRecursiveOverrides.Add(index);
|
||||
else
|
||||
_globalOverrides.Add(index);
|
||||
break;
|
||||
case GridChunkLocation gridChunkLocation:
|
||||
// might be gone due to grid-deletions
|
||||
@@ -239,8 +252,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
// since we can find the index, we can assume the dicts will be there too & dont need to do any checks. gaming.
|
||||
switch (location)
|
||||
{
|
||||
case GlobalOverride _:
|
||||
_globalOverrides.Remove(index);
|
||||
case GlobalOverride global:
|
||||
if (global.Recursive)
|
||||
_globalRecursiveOverrides.Remove(index);
|
||||
else
|
||||
_globalOverrides.Remove(index);
|
||||
break;
|
||||
case GridChunkLocation gridChunkLocation:
|
||||
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Remove(index);
|
||||
@@ -376,15 +392,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
|
||||
#region UpdateIndex
|
||||
|
||||
private bool IsOverride(TIndex index)
|
||||
private bool TryGetLocation(TIndex index, out IIndexLocation? location)
|
||||
{
|
||||
if (_locationChangeBuffer.TryGetValue(index, out var change) &&
|
||||
change is GlobalOverride or LocalOverride) return true;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var indexLoc) &&
|
||||
indexLoc is GlobalOverride or LocalOverride) return true;
|
||||
|
||||
return false;
|
||||
return _locationChangeBuffer.TryGetValue(index, out location)
|
||||
|| _indexLocations.TryGetValue(index, out location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -392,15 +403,25 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// </summary>
|
||||
/// <param name="index">The <see cref="TIndex"/> to update.</param>
|
||||
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
|
||||
public void UpdateIndex(TIndex index, bool removeFromOverride = false)
|
||||
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
|
||||
public void AddGlobalOverride(TIndex index, bool removeFromOverride, bool recursive)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
if (!TryGetLocation(index, out var oldLocation))
|
||||
{
|
||||
RegisterUpdate(index, new GlobalOverride(recursive));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeFromOverride && oldLocation is LocalOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is GlobalOverride) return;
|
||||
if (oldLocation is GlobalOverride global &&
|
||||
(!removeFromOverride || global.Recursive == recursive))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterUpdate(index, new GlobalOverride());
|
||||
RegisterUpdate(index, new GlobalOverride(recursive));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -411,12 +432,20 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
|
||||
public void UpdateIndex(TIndex index, ICommonSession session, bool removeFromOverride = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
if (!TryGetLocation(index, out var oldLocation))
|
||||
{
|
||||
RegisterUpdate(index, new LocalOverride(session));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeFromOverride || oldLocation is GlobalOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is LocalOverride local &&
|
||||
local.Session == session) return;
|
||||
if (oldLocation is LocalOverride local &&
|
||||
(!removeFromOverride || local.Session == session))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterUpdate(index, new LocalOverride(session));
|
||||
}
|
||||
@@ -429,34 +458,42 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
|
||||
public void UpdateIndex(TIndex index, EntityCoordinates coordinates, bool removeFromOverride = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
if (!removeFromOverride
|
||||
&& TryGetLocation(index, out var oldLocation)
|
||||
&& oldLocation is GlobalOverride or LocalOverride)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entityManager.TryGetComponent(coordinates.EntityId, out TransformComponent? xform))
|
||||
return;
|
||||
|
||||
var gridIdOpt = coordinates.GetGridUid(_entityManager);
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
if (xform.GridUid is { } gridId && gridId.IsValid())
|
||||
{
|
||||
var gridIndices = GetChunkIndices(coordinates.Position);
|
||||
UpdateIndex(index, gridId, gridIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
return;
|
||||
}
|
||||
|
||||
var mapCoordinates = coordinates.ToMap(_entityManager, _transformSystem);
|
||||
var mapIndices = GetChunkIndices(coordinates.Position);
|
||||
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
var worldPos = _transformSystem.GetWorldMatrix(xform).Transform(coordinates.Position);
|
||||
var mapIndices = GetChunkIndices(worldPos);
|
||||
UpdateIndex(index, xform.MapID, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
}
|
||||
|
||||
public IChunkIndexLocation GetChunkIndex(EntityCoordinates coordinates)
|
||||
{
|
||||
var gridIdOpt = coordinates.GetGridUid(_entityManager);
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
if (!_entityManager.TryGetComponent(coordinates.EntityId, out TransformComponent? xform))
|
||||
return new MapChunkLocation(default, default);
|
||||
|
||||
if (xform.GridUid is { } gridId && gridId.IsValid())
|
||||
{
|
||||
var gridIndices = GetChunkIndices(coordinates.Position);
|
||||
return new GridChunkLocation(gridId, gridIndices);
|
||||
}
|
||||
|
||||
var mapCoordinates = coordinates.ToMap(_entityManager, _transformSystem);
|
||||
var mapIndices = GetChunkIndices(coordinates.Position);
|
||||
return new MapChunkLocation(mapCoordinates.MapId, mapIndices);
|
||||
var worldPos = _transformSystem.GetWorldMatrix(xform).Transform(coordinates.Position);
|
||||
var mapIndices = GetChunkIndices(worldPos);
|
||||
return new MapChunkLocation(xform.MapID, mapIndices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -469,11 +506,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="forceDirty">If true, this will mark the previous chunk as dirty even if the entity did not move from that chunk.</param>
|
||||
public void UpdateIndex(TIndex index, EntityUid gridId, Vector2i chunkIndices, bool removeFromOverride = false, bool forceDirty = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
_locationChangeBuffer.TryGetValue(index, out var bufferedLocation);
|
||||
_indexLocations.TryGetValue(index, out var oldLocation);
|
||||
|
||||
//removeFromOverride is false 99% of the time.
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is GridChunkLocation oldGrid &&
|
||||
if (oldLocation is GridChunkLocation oldGrid &&
|
||||
oldGrid.ChunkIndices == chunkIndices &&
|
||||
oldGrid.GridId == gridId)
|
||||
{
|
||||
@@ -497,22 +537,26 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
/// <param name="forceDirty">If true, this will mark the previous chunk as dirty even if the entity did not move from that chunk.</param>
|
||||
public void UpdateIndex(TIndex index, MapId mapId, Vector2i chunkIndices, bool removeFromOverride = false, bool forceDirty = false)
|
||||
{
|
||||
if(!removeFromOverride && IsOverride(index))
|
||||
_locationChangeBuffer.TryGetValue(index, out var bufferedLocation);
|
||||
_indexLocations.TryGetValue(index, out var oldLocation);
|
||||
|
||||
//removeFromOverride is false 99% of the time.
|
||||
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
|
||||
return;
|
||||
|
||||
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
|
||||
oldLocation is MapChunkLocation oldMap &&
|
||||
// Is this entity just returning to its old location?
|
||||
if (oldLocation is MapChunkLocation oldMap &&
|
||||
oldMap.ChunkIndices == chunkIndices &&
|
||||
oldMap.MapId == mapId)
|
||||
{
|
||||
_locationChangeBuffer.Remove(index);
|
||||
if (bufferedLocation != null)
|
||||
_locationChangeBuffer.Remove(index);
|
||||
|
||||
if (forceDirty)
|
||||
_dirtyChunks.Add(oldMap);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
|
||||
}
|
||||
|
||||
@@ -584,7 +628,18 @@ public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatabl
|
||||
}
|
||||
}
|
||||
|
||||
public struct GlobalOverride : IIndexLocation { }
|
||||
public struct GlobalOverride : IIndexLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, this will also send all children of the override.
|
||||
/// </summary>
|
||||
public readonly bool Recursive;
|
||||
|
||||
public GlobalOverride(bool recursive)
|
||||
{
|
||||
Recursive = recursive;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalOverride : IIndexLocation
|
||||
{
|
||||
|
||||
@@ -12,20 +12,23 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
[Shared.IoC.Dependency] private readonly PvsSystem _pvs = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to every client. Overrides any client-specific overrides.
|
||||
/// Used to ensure that an entity is always sent to every client. By default this overrides any client-specific overrides.
|
||||
/// </summary>
|
||||
public void AddGlobalOverride(EntityUid uid)
|
||||
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
|
||||
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
|
||||
public void AddGlobalOverride(EntityUid uid, bool removeExistingOverride = true, bool recursive = false)
|
||||
{
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, true);
|
||||
_pvs.EntityPVSCollection.AddGlobalOverride(uid, removeExistingOverride, recursive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
|
||||
/// client-specific overrides.
|
||||
/// </summary>
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session,bool removeExistingOverride = true)
|
||||
{
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, session, true);
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, session, removeExistingOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.Configuration;
|
||||
@@ -13,11 +12,9 @@ using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -107,6 +104,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
private EntityQuery<EyeComponent> _eyeQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -114,6 +112,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
_eyeQuery = GetEntityQuery<EyeComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
|
||||
|
||||
@@ -387,7 +386,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
pvsCollection.AddGrid(gridId);
|
||||
}
|
||||
|
||||
_entityPvsCollection.UpdateIndex(gridId);
|
||||
_entityPvsCollection.AddGlobalOverride(gridId, true, false);
|
||||
}
|
||||
|
||||
private void OnMapDestroyed(MapChangedEvent e)
|
||||
@@ -407,7 +406,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
if(e.Map == MapId.Nullspace) return;
|
||||
var uid = _mapManager.GetMapEntityId(e.Map);
|
||||
_entityPvsCollection.UpdateIndex(uid);
|
||||
_entityPvsCollection.AddGlobalOverride(uid, true, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -749,6 +748,16 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
globalEnumerator.Dispose();
|
||||
|
||||
|
||||
var globalRecursiveEnumerator = _entityPvsCollection.GlobalRecursiveOverridesEnumerator;
|
||||
while (globalRecursiveEnumerator.MoveNext())
|
||||
{
|
||||
var uid = globalRecursiveEnumerator.Current;
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
globalRecursiveEnumerator.Dispose();
|
||||
|
||||
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
|
||||
while (localEnumerator.MoveNext())
|
||||
{
|
||||
@@ -764,7 +773,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
var expandEvent = new ExpandPvsEvent(session, new List<EntityUid>());
|
||||
var expandEvent = new ExpandPvsEvent(session);
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
{
|
||||
@@ -772,6 +781,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
foreach (var entityUid in expandEvent.RecursiveEntities)
|
||||
{
|
||||
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
|
||||
var entityStates = new List<EntityState>(entStateCount);
|
||||
|
||||
foreach (var (uid, visiblity) in visibleEnts)
|
||||
@@ -898,8 +913,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
public bool RecursivelyAddOverride(
|
||||
in EntityUid uid,
|
||||
public bool RecursivelyAddOverride(in EntityUid uid,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<EntityUid, PvsEntityVisibility> toSend,
|
||||
@@ -911,34 +925,74 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref int enteredEntityCount,
|
||||
ref int entStateCount,
|
||||
in int newEntityBudget,
|
||||
in int enteredEntityBudget)
|
||||
in int enteredEntityBudget,
|
||||
bool addChildren = false)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid()) return false;
|
||||
if (!uid.IsValid())
|
||||
return false;
|
||||
|
||||
var parent = transQuery.GetComponent(uid).ParentUid;
|
||||
var xform = transQuery.GetComponent(uid);
|
||||
var parent = xform.ParentUid;
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in metaQuery, in transQuery, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget))
|
||||
return false;
|
||||
|
||||
//did we already get added?
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
// Note that we check this AFTER adding parents. This is because while this entity may already have been added
|
||||
// to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported
|
||||
// to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also
|
||||
// send the new parents, which may otherwise be delayed because of the PVS budget..
|
||||
if (!toSend.ContainsKey(uid))
|
||||
{
|
||||
// TODO PERFORMANCE.
|
||||
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
|
||||
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
|
||||
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
|
||||
var (entered, _) = ProcessEntry(in uid, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
// TODO PERFORMANCE.
|
||||
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
|
||||
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
|
||||
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
|
||||
var (entered, _) = ProcessEntry(in uid, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
if (addChildren)
|
||||
{
|
||||
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(TransformComponent xform,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<EntityUid, PvsEntityVisibility> toSend,
|
||||
Dictionary<EntityUid, GameTick> lastSeen,
|
||||
in GameTick fromTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
ref int entStateCount,
|
||||
in int newEntityBudget,
|
||||
in int enteredEntityBudget)
|
||||
{
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(child, out var childXform))
|
||||
continue;
|
||||
|
||||
if (!toSend.ContainsKey(child))
|
||||
{
|
||||
var (entered, _) = ProcessEntry(in child, lastAcked, lastSent, lastSeen, ref newEntityCount,
|
||||
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
AddToSendSet(in child, _metaQuery.GetComponent(child), toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
|
||||
private (bool Entered, bool ShouldAdd) ProcessEntry(in EntityUid uid,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
|
||||
@@ -1034,26 +1088,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 +1125,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 +1152,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)
|
||||
@@ -1330,11 +1374,20 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
public readonly struct ExpandPvsEvent
|
||||
{
|
||||
public readonly IPlayerSession Session;
|
||||
public readonly List<EntityUid> Entities;
|
||||
|
||||
public ExpandPvsEvent(IPlayerSession session, List<EntityUid> entities)
|
||||
/// <summary>
|
||||
/// List of entities that will get added to this session's PVS set.
|
||||
/// </summary>
|
||||
public readonly List<EntityUid> Entities = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of entities that will get added to this session's PVS set. Unlike <see cref="Entities"/> this will also
|
||||
/// recursively add all children of the given entity.
|
||||
/// </summary>
|
||||
public readonly List<EntityUid> RecursiveEntities = new();
|
||||
|
||||
public ExpandPvsEvent(IPlayerSession session)
|
||||
{
|
||||
Session = session;
|
||||
Entities = entities;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -751,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
|
||||
{
|
||||
@@ -764,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user