mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77654a1628 | ||
|
|
f3af813b57 | ||
|
|
0623baedcf | ||
|
|
2ade6c04c5 | ||
|
|
a9df9097c1 | ||
|
|
755dac719f |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,23 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 167.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove ComponentExtensions.
|
||||
* Remove ContainerHelpers.
|
||||
* Change some TransformSystem methods to fix clientside lerping.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed PVS bugs from dropped entity states.
|
||||
|
||||
### Other
|
||||
|
||||
* Add more joint debug asserts.
|
||||
|
||||
|
||||
## 166.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -1,51 +1,41 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed partial class TransformSystem
|
||||
{
|
||||
public override void SetLocalPosition(TransformComponent xform, Vector2 value)
|
||||
public override void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
|
||||
{
|
||||
xform.PrevPosition = xform._localPosition;
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
xform.NextPosition = value;
|
||||
xform.LerpParent = xform.ParentUid;
|
||||
base.SetLocalPosition(xform, value);
|
||||
ActivateLerp(xform);
|
||||
ActivateLerp(uid, xform);
|
||||
base.SetLocalPosition(uid, value, xform);
|
||||
}
|
||||
|
||||
public override void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
|
||||
public override void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
|
||||
{
|
||||
xform.NextPosition = null;
|
||||
xform.LerpParent = EntityUid.Invalid;
|
||||
base.SetLocalPositionNoLerp(xform, value);
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
xform.NextRotation = value;
|
||||
ActivateLerp(uid, xform);
|
||||
base.SetLocalRotation(uid, value, xform);
|
||||
}
|
||||
|
||||
public override void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
|
||||
public override void SetLocalPositionRotation(EntityUid uid, Vector2 pos, Angle rot, TransformComponent? xform = null)
|
||||
{
|
||||
xform.NextRotation = null;
|
||||
xform.LerpParent = EntityUid.Invalid;
|
||||
base.SetLocalRotationNoLerp(xform, angle);
|
||||
}
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
public override void SetLocalRotation(TransformComponent xform, Angle angle)
|
||||
{
|
||||
xform.PrevRotation = xform._localRotation;
|
||||
xform.NextRotation = angle;
|
||||
xform.LerpParent = xform.ParentUid;
|
||||
base.SetLocalRotation(xform, angle);
|
||||
ActivateLerp(xform);
|
||||
}
|
||||
|
||||
public override void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
|
||||
{
|
||||
xform.PrevPosition = xform._localPosition;
|
||||
xform.NextPosition = pos;
|
||||
xform.PrevRotation = xform._localRotation;
|
||||
xform.NextRotation = rot;
|
||||
xform.LerpParent = xform.ParentUid;
|
||||
base.SetLocalPositionRotation(xform, pos, rot);
|
||||
ActivateLerp(xform);
|
||||
ActivateLerp(uid, xform);
|
||||
base.SetLocalPositionRotation(uid, pos, rot, xform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
@@ -25,11 +24,6 @@ namespace Robust.Client.GameObjects
|
||||
private const float MinInterpolationDistance = 0.001f;
|
||||
private const float MinInterpolationDistanceSquared = MinInterpolationDistance * MinInterpolationDistance;
|
||||
|
||||
private const double MinInterpolationAngle = Math.PI / 720;
|
||||
|
||||
// 45 degrees.
|
||||
private const double MaxInterpolationAngle = Math.PI / 4;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
// Only keep track of transforms actively lerping.
|
||||
@@ -48,21 +42,77 @@ namespace Robust.Client.GameObjects
|
||||
_lerpingTransforms.Clear();
|
||||
}
|
||||
|
||||
public override void ActivateLerp(TransformComponent xform)
|
||||
public override void ActivateLerp(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
if (xform.ActivelyLerping)
|
||||
// This lerping logic is pretty convoluted and generally assumes that the client does not mispredict.
|
||||
// A more foolproof solution would be to just cache the coordinates at which any given entity was most
|
||||
// recently rendered and using that as the lerp origin. However that'd require enumerating over all entities
|
||||
// every tick which is pretty icky.
|
||||
|
||||
// The general considerations are:
|
||||
// - If the client receives a server state for an entity moving from a->b and predicts nothing else, then it
|
||||
// should show the entity lerping.
|
||||
// - If the client predicts an entity will move while already lerping due to a state-application, it should
|
||||
// clear the state's lerp, under the assumption that the client predicted the state and already rendered
|
||||
// the entity in the final position.
|
||||
// - If the client predicts that an entity moves, then we only lerp if this is the first time that the tick
|
||||
// was predicted. I.e., we assume the entity was already rendered in it's final of that lerp.
|
||||
// - If the client predicts that an entity should lerp twice in the same tick, then we need to combine them.
|
||||
// I.e. moving from a->b then b->c, the client should lerp from a->c.
|
||||
|
||||
// If the client predicts an entity moves while already lerping, it should clear the
|
||||
// predict a->b, lerp a->b
|
||||
// predicted a->b, then predict b->c. Lerp b->c
|
||||
// predicted a->b, then predict b->c. Lerp b->c
|
||||
// predicted a->b, predicted b->c, then predict c->d. Lerp c->d
|
||||
// server state a->b, then predicted b->c, lerp b->c
|
||||
// server state a->b, then predicted b->c, then predict d, lerp b->c
|
||||
|
||||
if (_gameTiming.ApplyingState)
|
||||
{
|
||||
if (xform.ActivelyLerping)
|
||||
{
|
||||
// This should not happen, but can happen if some bad component state application code modifies an entity's coordinates.
|
||||
Log.Error($"Entity {(ToPrettyString(uid))} tried to lerp twice while applying component states.");
|
||||
return;
|
||||
}
|
||||
|
||||
_lerpingTransforms.Add(xform);
|
||||
xform.ActivelyLerping = true;
|
||||
xform.PredictedLerp = false;
|
||||
xform.LerpParent = xform.ParentUid;
|
||||
xform.PrevRotation = xform._localRotation;
|
||||
xform.PrevPosition = xform._localPosition;
|
||||
xform.LastLerp = _gameTiming.CurTick;
|
||||
return;
|
||||
}
|
||||
|
||||
xform.ActivelyLerping = true;
|
||||
_lerpingTransforms.Add(xform);
|
||||
}
|
||||
xform.LastLerp = _gameTiming.CurTick;
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
xform.ActivelyLerping = false;
|
||||
return;
|
||||
}
|
||||
|
||||
public override void DeactivateLerp(TransformComponent component)
|
||||
{
|
||||
// this should cause the lerp to do nothing
|
||||
component.NextPosition = null;
|
||||
component.NextRotation = null;
|
||||
component.LerpParent = EntityUid.Invalid;
|
||||
if (!xform.ActivelyLerping)
|
||||
{
|
||||
_lerpingTransforms.Add(xform);
|
||||
xform.ActivelyLerping = true;
|
||||
xform.PredictedLerp = true;
|
||||
xform.PrevRotation = xform._localRotation;
|
||||
xform.PrevPosition = xform._localPosition;
|
||||
xform.LerpParent = xform.ParentUid;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!xform.PredictedLerp || xform.LerpParent != xform.ParentUid)
|
||||
{
|
||||
// Existing lerp was not due to prediction, but due to state application. That lerp should already
|
||||
// have been rendered, so we will start a new lerp from the current position.
|
||||
xform.PrevRotation = xform._localRotation;
|
||||
xform.PrevPosition = xform._localPosition;
|
||||
xform.LerpParent = xform.ParentUid;
|
||||
}
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -74,11 +124,13 @@ namespace Robust.Client.GameObjects
|
||||
for (var i = 0; i < _lerpingTransforms.Count; i++)
|
||||
{
|
||||
var transform = _lerpingTransforms[i];
|
||||
var uid = transform.Owner;
|
||||
var found = false;
|
||||
|
||||
// Only lerp if parent didn't change.
|
||||
// E.g. entering lockers would do it.
|
||||
if (transform.LerpParent == transform.ParentUid
|
||||
if (transform.ActivelyLerping
|
||||
&& transform.LerpParent == transform.ParentUid
|
||||
&& transform.ParentUid.IsValid()
|
||||
&& !transform.Deleted)
|
||||
{
|
||||
@@ -90,8 +142,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (distance is > MinInterpolationDistanceSquared and < MaxInterpolationDistanceSquared)
|
||||
{
|
||||
transform.LocalPosition = Vector2.Lerp(lerpSource, lerpDest, step);
|
||||
// Setting LocalPosition clears LerpPosition so fix that.
|
||||
SetLocalPositionNoLerp(uid, Vector2.Lerp(lerpSource, lerpDest, step), transform);
|
||||
transform.NextPosition = lerpDest;
|
||||
found = true;
|
||||
}
|
||||
@@ -101,15 +152,9 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var lerpDest = transform.NextRotation.Value;
|
||||
var lerpSource = transform.PrevRotation;
|
||||
var distance = Math.Abs(Angle.ShortestDistance(lerpDest, lerpSource));
|
||||
|
||||
if (distance is > MinInterpolationAngle and < MaxInterpolationAngle)
|
||||
{
|
||||
transform.LocalRotation = Angle.Lerp(lerpSource, lerpDest, step);
|
||||
// Setting LocalRotation clears LerpAngle so fix that.
|
||||
transform.NextRotation = lerpDest;
|
||||
found = true;
|
||||
}
|
||||
SetLocalRotationNoLerp(uid, Angle.Lerp(lerpSource, lerpDest, step), transform);
|
||||
transform.NextRotation = lerpDest;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
public event Action<MsgStateLeavePvs>? PvsLeave;
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// If true, this will cause received game states to be ignored. Used by integration tests.
|
||||
/// </summary>
|
||||
public bool DropStates;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
@@ -203,6 +210,10 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void HandleStateMessage(MsgState message)
|
||||
{
|
||||
#if DEBUG
|
||||
if (DropStates)
|
||||
return;
|
||||
#endif
|
||||
// We ONLY ack states that are definitely going to get applied. Otherwise the sever might assume that we
|
||||
// applied a state containing entity-creation information, which it would then no longer send to us when
|
||||
// we re-encounter this entity
|
||||
@@ -277,18 +288,15 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
}
|
||||
|
||||
if (PredictionNeedsResetting)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
ResetPredictedEntities();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// avoid exception spam from repeatedly trying to reset the same entity.
|
||||
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
|
||||
_runtimeLog.LogException(e, "ResetPredictedEntities");
|
||||
}
|
||||
ResetPredictedEntities();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// avoid exception spam from repeatedly trying to reset the same entity.
|
||||
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
|
||||
_runtimeLog.LogException(e, "ResetPredictedEntities");
|
||||
}
|
||||
|
||||
// If we were waiting for a new state, we are now applying it.
|
||||
@@ -477,20 +485,22 @@ namespace Robust.Client.GameStates
|
||||
|
||||
public void ResetPredictedEntities()
|
||||
{
|
||||
PredictionNeedsResetting = false;
|
||||
|
||||
using var _ = _prof.Group("ResetPredictedEntities");
|
||||
using var __ = _timing.StartStateApplicationArea();
|
||||
|
||||
// This is terrible, and I hate it. This also needs to run even when prediction is disabled.
|
||||
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
|
||||
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
|
||||
|
||||
if (!PredictionNeedsResetting)
|
||||
return;
|
||||
|
||||
PredictionNeedsResetting = false;
|
||||
var countReset = 0;
|
||||
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
RemQueue<Component> toRemove = new();
|
||||
|
||||
// This is terrible, and I hate it.
|
||||
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
|
||||
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
|
||||
|
||||
foreach (var entity in system.DirtyEntities)
|
||||
{
|
||||
DebugTools.Assert(toRemove.Count == 0);
|
||||
@@ -1443,6 +1453,9 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool IsQueuedForDetach(NetEntity entity)
|
||||
=> _processor.IsQueuedForDetach(entity);
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logMan.GetSawmill(CVars.NetPredict.Name);
|
||||
|
||||
@@ -404,6 +404,18 @@ namespace Robust.Client.GameStates
|
||||
return _lastStateFullRep.TryGetValue(entity, out dictionary);
|
||||
}
|
||||
|
||||
public bool IsQueuedForDetach(NetEntity entity)
|
||||
{
|
||||
// This isn't fast, but its just meant for use in tests & debug asserts.
|
||||
foreach (var msg in _pvsDetachMessages.Values)
|
||||
{
|
||||
if (msg.Contains(entity))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int CalculateBufferSize(GameTick fromTick)
|
||||
{
|
||||
bool foundState;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
using System;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
internal sealed class NetInterpOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -24,6 +24,11 @@ namespace Robust.Client.GameStates
|
||||
private readonly SharedContainerSystem _container;
|
||||
private readonly SharedTransformSystem _xform;
|
||||
|
||||
/// <summary>
|
||||
/// When an entity stops lerping the overlay will continue to draw a box around the entity for this amount of time.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan Delay = TimeSpan.FromSeconds(2f);
|
||||
|
||||
public NetInterpOverlay(EntityLookupSystem lookup)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -40,8 +45,8 @@ namespace Robust.Client.GameStates
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
var query = _entityManager.AllEntityQueryEnumerator<PhysicsComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var physics, out var transform))
|
||||
var query = _entityManager.AllEntityQueryEnumerator<TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var transform))
|
||||
{
|
||||
// if not on the same map, continue
|
||||
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
|
||||
@@ -50,8 +55,8 @@ namespace Robust.Client.GameStates
|
||||
if (transform.GridUid == uid)
|
||||
continue;
|
||||
|
||||
// This entity isn't lerping, no need to draw debug info for it
|
||||
if(transform.NextPosition == null)
|
||||
var delta = (_timing.CurTick.Value - transform.LastLerp.Value) * _timing.TickPeriod;
|
||||
if(!transform.ActivelyLerping && delta > Delay)
|
||||
continue;
|
||||
|
||||
var aabb = _lookup.GetWorldAABB(uid);
|
||||
@@ -61,7 +66,9 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
|
||||
var (pos, rot) = _xform.GetWorldPositionRotation(transform, _entityManager.GetEntityQuery<TransformComponent>());
|
||||
var boxOffset = transform.NextPosition.Value - transform.LocalPosition;
|
||||
var boxOffset = transform.NextPosition != null
|
||||
? transform.NextPosition.Value - transform.LocalPosition
|
||||
: default;
|
||||
var worldOffset = (rot - transform.LocalRotation).RotateVec(boxOffset);
|
||||
|
||||
var nextPos = pos + worldOffset;
|
||||
|
||||
@@ -82,11 +82,8 @@ namespace Robust.Client.Physics
|
||||
continue;
|
||||
}
|
||||
|
||||
xform.PrevPosition = position;
|
||||
xform.PrevRotation = rotation;
|
||||
xform.LerpParent = parentUid;
|
||||
xform.NextPosition = xform.LocalPosition;
|
||||
xform.NextRotation = xform.LocalRotation;
|
||||
// Transform system will handle lerping.
|
||||
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
|
||||
}
|
||||
|
||||
component.LerpData.Clear();
|
||||
|
||||
@@ -188,6 +188,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
Log.Warning(sb.ToString());
|
||||
|
||||
sessionData.LastSeenAt.Clear();
|
||||
sessionData.LastLeftView.Clear();
|
||||
|
||||
if (sessionData.Overflow != null)
|
||||
{
|
||||
@@ -256,6 +257,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
foreach (var sessionData in PlayerData.Values)
|
||||
{
|
||||
sessionData.LastSeenAt.Remove(metadata.NetEntity);
|
||||
sessionData.LastLeftView.Remove(metadata.NetEntity);
|
||||
if (sessionData.SentEntities.TryGetValue(previousTick, out var ents))
|
||||
ents.Remove(metadata.NetEntity);
|
||||
}
|
||||
@@ -703,6 +705,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
sessionData.SentEntities.TryGetValue(toTick - 1, out var lastSent);
|
||||
var lastAcked = sessionData.LastAcked?.Data;
|
||||
var lastSeen = sessionData.LastSeenAt;
|
||||
var lastLeft = sessionData.LastLeftView;
|
||||
var visibleEnts = _visSetPool.Get();
|
||||
|
||||
if (visibleEnts.Count != 0)
|
||||
@@ -730,7 +733,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
foreach (var rootNode in cache.Value.tree.RootNodes)
|
||||
{
|
||||
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, cache.Value.metadata, stack, in fromTick,
|
||||
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, cache.Value.metadata, stack, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
@@ -741,7 +744,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
var netEntity = globalEnumerator.Current;
|
||||
var uid = GetEntity(netEntity);
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
globalEnumerator.Dispose();
|
||||
@@ -751,7 +754,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
var netEntity = globalRecursiveEnumerator.Current;
|
||||
var uid = GetEntity(netEntity);
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
globalRecursiveEnumerator.Dispose();
|
||||
@@ -761,14 +764,14 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
var netEntity = sessionOverrides.Current;
|
||||
var uid = GetEntity(netEntity);
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick,
|
||||
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
sessionOverrides.Dispose();
|
||||
|
||||
foreach (var viewerEntity in viewers)
|
||||
{
|
||||
RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
|
||||
RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
@@ -778,7 +781,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
{
|
||||
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
|
||||
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
@@ -787,7 +790,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
foreach (var entityUid in expandEvent.RecursiveEntities)
|
||||
{
|
||||
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick,
|
||||
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
|
||||
}
|
||||
}
|
||||
@@ -826,7 +829,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
|
||||
// tell a client to detach entities that have left their view
|
||||
var leftView = ProcessLeavePVS(visibleEnts, lastSent);
|
||||
var leftView = ProcessLeavePvs(visibleEnts, lastSent, lastLeft);
|
||||
|
||||
if (sessionData.SentEntities.Add(toTick, visibleEnts, out var oldEntry))
|
||||
{
|
||||
@@ -863,18 +866,26 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
/// Figure out what entities are no longer visible to the client. These entities are sent reliably to the client
|
||||
/// in a separate net message.
|
||||
/// </summary>
|
||||
private List<NetEntity>? ProcessLeavePVS(
|
||||
private List<NetEntity>? ProcessLeavePvs(
|
||||
Dictionary<NetEntity, PvsEntityVisibility> visibleEnts,
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastSent)
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<NetEntity, GameTick> lastLeft)
|
||||
{
|
||||
if (lastSent == null)
|
||||
return null;
|
||||
|
||||
var leftView = new List<NetEntity>();
|
||||
var tick = _gameTiming.CurTick;
|
||||
var minSize = Math.Max(0, lastSent.Count - lastSent.Count);
|
||||
var leftView = new List<NetEntity>(minSize);
|
||||
|
||||
foreach (var netEntity in lastSent.Keys)
|
||||
{
|
||||
if (!visibleEnts.ContainsKey(netEntity))
|
||||
{
|
||||
leftView.Add(netEntity);
|
||||
lastLeft[netEntity] = tick;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return leftView.Count > 0 ? leftView : null;
|
||||
@@ -886,6 +897,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<NetEntity, PvsEntityVisibility> toSend,
|
||||
Dictionary<NetEntity, GameTick> lastSeen,
|
||||
Dictionary<NetEntity, GameTick> lastLeft,
|
||||
Dictionary<NetEntity, MetaDataComponent> metaDataCache,
|
||||
Stack<NetEntity> stack,
|
||||
in GameTick fromTick,
|
||||
@@ -908,7 +920,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, currentNodeIndex, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen,
|
||||
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen, lastLeft, fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
if (!shouldAdd)
|
||||
@@ -938,6 +950,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<NetEntity, PvsEntityVisibility> toSend,
|
||||
Dictionary<NetEntity, GameTick> lastSeen,
|
||||
Dictionary<NetEntity, GameTick> lastLeft,
|
||||
in GameTick fromTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
@@ -953,7 +966,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
var parent = xform.ParentUid;
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in fromTick,
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, lastLeft, in fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget,
|
||||
in enteredEntityBudget))
|
||||
{
|
||||
@@ -963,7 +976,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
var metadata = _metaQuery.GetComponent(uid);
|
||||
var netEntity = metadata.NetEntity;
|
||||
|
||||
//did we already get added?
|
||||
// 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
|
||||
@@ -971,17 +983,13 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, netEntity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
// 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 netEntity, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
var (entered, _) = ProcessEntry(in netEntity, lastAcked, lastSent, lastSeen, lastLeft, fromTick, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
AddToSendSet(in netEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
if (addChildren)
|
||||
{
|
||||
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, lastLeft, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
@@ -993,6 +1001,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<NetEntity, PvsEntityVisibility> toSend,
|
||||
Dictionary<NetEntity, GameTick> lastSeen,
|
||||
Dictionary<NetEntity, GameTick> lastLeft,
|
||||
in GameTick fromTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
@@ -1011,22 +1020,23 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, childNetEntity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount,
|
||||
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, lastLeft, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
AddToSendSet(in childNetEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, lastLeft, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
|
||||
private (bool Entered, bool ShouldAdd) ProcessEntry(
|
||||
in NetEntity netEntity,
|
||||
private (bool Entered, bool ShouldAdd) ProcessEntry(in NetEntity netEntity,
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastAcked,
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
|
||||
Dictionary<NetEntity, GameTick> lastSeen,
|
||||
Dictionary<NetEntity, GameTick> lastLeft,
|
||||
GameTick fromTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
in int newEntityBudget,
|
||||
@@ -1034,16 +1044,15 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
var enteredSinceLastSent = lastSent == null || !lastSent.ContainsKey(netEntity);
|
||||
|
||||
var entered = enteredSinceLastSent || // OR, entered since last ack:
|
||||
lastAcked == null || !lastAcked.ContainsKey(netEntity);
|
||||
var entered = enteredSinceLastSent
|
||||
|| lastAcked == null
|
||||
|| !lastAcked.ContainsKey(netEntity) // entered since last acked
|
||||
|| lastLeft.GetValueOrDefault(netEntity) >= fromTick; // Just in case a packet was lost. I love dictionary lookups
|
||||
|
||||
// If the entity is entering, but we already sent this entering entity in the last message, we won't add it to
|
||||
// the budget. Chances are the packet will arrive in a nice and orderly fashion, and the client will stick to
|
||||
// their requested budget. However this can cause issues if a packet gets dropped, because a player may create
|
||||
// 2x or more times the normal entity creation budget.
|
||||
//
|
||||
// The fix for that would be to just also give the PVS budget a client-side aspect that controls entity creation
|
||||
// rate.
|
||||
if (enteredSinceLastSent)
|
||||
{
|
||||
if (newEntityCount >= newEntityBudget || enteredEntityCount >= enteredEntityBudget)
|
||||
@@ -1374,6 +1383,11 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
/// </summary>
|
||||
public readonly Dictionary<NetEntity, GameTick> LastSeenAt = new();
|
||||
|
||||
/// <summary>
|
||||
/// Tick at which an entity last left a player's PVS view.
|
||||
/// </summary>
|
||||
public readonly Dictionary<NetEntity, GameTick> LastLeftView = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SentEntities"/> overflow in case a player's last ack is more than <see cref="DirtyBufferSize"/> ticks behind the current tick.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper functions for the container system.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class ContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Am I inside a container? Only checks the direct parent. To see if the entity, or any parent entity, is
|
||||
/// inside a container, use <see cref="ContainerSystem.IsEntityOrParentInContainer"/>
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <returns>If the entity is inside of a container.</returns>
|
||||
[Obsolete("Use ContainerSystem.IsEntityInContainer() instead")]
|
||||
public static bool IsInContainer(this EntityUid entity,
|
||||
IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return (entMan.GetComponent<MetaDataComponent>(entity).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the container manager that this entity is inside (if any).
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <param name="manager">The container manager that this entity is inside of.</param>
|
||||
/// <returns>If a container manager was found.</returns>
|
||||
public static bool TryGetContainerMan(this EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
DebugTools.Assert(entMan.EntityExists(entity));
|
||||
|
||||
var parent = entMan.GetComponent<TransformComponent>(entity).ParentUid;
|
||||
if (parent.IsValid() && TryGetManagerComp(parent, out manager, entMan) && manager.ContainsEntity(entity))
|
||||
return true;
|
||||
|
||||
manager = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the container that this entity is inside (if any).
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <param name="container">The container that this entity is inside of.</param>
|
||||
/// <returns>If a container was found.</returns>
|
||||
[Obsolete("Use ContainerSystem.TryGetContainingContainer() instead")]
|
||||
public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out BaseContainer? container, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
DebugTools.Assert(entMan.EntityExists(entity));
|
||||
|
||||
if (TryGetContainerMan(entity, out var manager, entMan))
|
||||
return manager.TryGetContainer(entity, out container);
|
||||
|
||||
container = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove an entity from its container, if any.
|
||||
/// <see cref="SharedContainerSystem.TryRemoveFromContainer"/>
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
|
||||
/// <param name="wasInContainer">Whether the entity was actually inside a container or not.</param>
|
||||
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
|
||||
[Obsolete("Use SharedContainerSystem.TryRemoveFromContainer() instead")]
|
||||
public static bool TryRemoveFromContainer(this EntityUid entity, bool force, out bool wasInContainer, IEntityManager? entMan = null)
|
||||
{
|
||||
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
|
||||
.TryRemoveFromContainer(entity, force, out wasInContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove an entity from its container, if any.
|
||||
/// <see cref="SharedContainerSystem.TryRemoveFromContainer"/>
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity that might be inside a container.</param>
|
||||
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
|
||||
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
|
||||
[Obsolete("Use SharedContainerSystem.TryRemoveFromContainer() instead")]
|
||||
public static bool TryRemoveFromContainer(this EntityUid entity, bool force = false, IEntityManager? entMan = null)
|
||||
{
|
||||
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
|
||||
.TryRemoveFromContainer(entity, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove all entities in a container.
|
||||
/// <see cref="SharedContainerSystem.EmptyContainer"/>
|
||||
/// </summary>
|
||||
[Obsolete("Use SharedContainerSystem.EmptyContainer() instead")]
|
||||
public static void EmptyContainer(this BaseContainer container, bool force = false, EntityCoordinates? moveTo = null,
|
||||
bool attachToGridOrMap = false, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
|
||||
.EmptyContainer(container, force, moveTo, attachToGridOrMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove and delete all entities in a container.
|
||||
/// <see cref="SharedContainerSystem.CleanContainer"/>
|
||||
/// </summary>
|
||||
[Obsolete("Use SharedContainerSystem.CleanContainer() instead")]
|
||||
public static void CleanContainer(this BaseContainer container, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
|
||||
.CleanContainer(container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SharedContainerSystem.AttachParentToContainerOrGrid"/>
|
||||
/// </summary>
|
||||
[Obsolete("Use SharedContainerSystem.AttachParentToContainerOrGrid() instead")]
|
||||
public static void AttachParentToContainerOrGrid(this TransformComponent transform, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
|
||||
.AttachParentToContainerOrGrid(transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SharedContainerSystem.TryGetManagerComp"/>
|
||||
/// </summary>
|
||||
[Obsolete("Use SharedContainerSystem.TryGetManagerComp() instead")]
|
||||
private static bool TryGetManagerComp(this EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return entMan.System<SharedContainerSystem>()
|
||||
.TryGetManagerComp(entity, out manager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut method to make creation of containers easier.
|
||||
/// Creates a new container on the entity and gives it back to you.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to create the container for.</param>
|
||||
/// <param name="containerId"></param>
|
||||
/// <returns>The new container.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID.</exception>
|
||||
/// <seealso cref="BaseContainerManager.MakeContainer{T}(string)" />
|
||||
[Obsolete("Use ContainerSystem.MakeContainer() instead")]
|
||||
public static T CreateContainer<T>(this EntityUid entity, string containerId, IEntityManager? entMan = null)
|
||||
where T : BaseContainer
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
var containermanager = entMan.EnsureComponent<ContainerManagerComponent>(entity);
|
||||
return containermanager.MakeContainer<T>(containerId);
|
||||
}
|
||||
|
||||
[Obsolete("Use ContainerSystem.EnsureContainer() instead")]
|
||||
public static T EnsureContainer<T>(this EntityUid entity, string containerId, IEntityManager? entMan = null)
|
||||
where T : BaseContainer
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return EnsureContainer<T>(entity, containerId, out _, entMan);
|
||||
}
|
||||
|
||||
[Obsolete("Use ContainerSystem.EnsureContainer() instead")]
|
||||
public static T EnsureContainer<T>(this EntityUid entity, string containerId, out bool alreadyExisted, IEntityManager? entMan = null)
|
||||
where T : BaseContainer
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
var containerManager = entMan.EnsureComponent<ContainerManagerComponent>(entity);
|
||||
|
||||
if (!containerManager.TryGetContainer(containerId, out var existing))
|
||||
{
|
||||
alreadyExisted = false;
|
||||
return containerManager.MakeContainer<T>(containerId);
|
||||
}
|
||||
|
||||
if (!(existing is T container))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The container exists but is of a different type: {existing.GetType()}");
|
||||
}
|
||||
|
||||
alreadyExisted = true;
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[PublicAPI]
|
||||
public static class ComponentExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Convenience wrapper to implement "create component if it does not already exist".
|
||||
/// Always gives you back a component, and creates it if it does not exist yet.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to fetch or create the component on.</param>
|
||||
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
|
||||
/// <returns>The existing component, or the new component if none existed yet.</returns>
|
||||
[Obsolete]
|
||||
public static T EnsureComponent<T>(this EntityUid entity) where T : Component, new()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (entMan.TryGetComponent(entity, out T? component))
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
return entMan.AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience wrapper to implement "create component if it does not already exist".
|
||||
/// Always gives you back a component, and creates it if it does not exist yet.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to fetch or create the component on.</param>
|
||||
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
|
||||
/// <param name="warning">
|
||||
/// The custom warning message to log if the component did not exist already.
|
||||
/// Defaults to a predetermined warning if null.
|
||||
/// </param>
|
||||
/// <returns>The existing component, or the new component if none existed yet.</returns>
|
||||
[Obsolete]
|
||||
public static T EnsureComponentWarn<T>(this EntityUid entity, string? warning = null) where T : Component, new()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (entMan.TryGetComponent(entity, out T? component))
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
warning ??= $"Entity {entity} at {entMan.GetComponent<TransformComponent>(entity).MapPosition} did not have a {typeof(T)}";
|
||||
|
||||
Logger.Warning(warning);
|
||||
|
||||
return entMan.AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static IComponent SetAndDirtyIfChanged<TValue>(
|
||||
this IComponent comp,
|
||||
ref TValue backingField,
|
||||
TValue value)
|
||||
{
|
||||
if (EqualityComparer<TValue>.Default.Equals(backingField, value))
|
||||
{
|
||||
return comp;
|
||||
}
|
||||
|
||||
backingField = value;
|
||||
comp.Dirty();
|
||||
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,8 +85,9 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables]
|
||||
public Angle PrevRotation { get; internal set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
internal bool ActivelyLerping { get; set; }
|
||||
[ViewVariables] public bool ActivelyLerping;
|
||||
|
||||
[ViewVariables] public GameTick LastLerp = GameTick.Zero;
|
||||
|
||||
[ViewVariables] internal readonly HashSet<EntityUid> _children = new();
|
||||
|
||||
@@ -397,7 +398,8 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
[ViewVariables] public int ChildCount => _children.Count;
|
||||
|
||||
[ViewVariables] internal EntityUid LerpParent { get; set; }
|
||||
[ViewVariables] public EntityUid LerpParent;
|
||||
public bool PredictedLerp;
|
||||
|
||||
/// <summary>
|
||||
/// Detaches this entity from its parent.
|
||||
|
||||
@@ -398,6 +398,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (component.LifeStage >= ComponentLifeStage.Removing || !component.NetSyncEnabled)
|
||||
return;
|
||||
|
||||
DebugTools.AssertOwner(uid, component);
|
||||
DirtyEntity(uid, meta);
|
||||
component.LastModifiedTick = CurrentTick;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ public abstract partial class SharedTransformSystem
|
||||
/// </summary>
|
||||
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
|
||||
{
|
||||
return ContainsEntity(xform, entity, _xformQuery);
|
||||
return ContainsEntity(xform, entity, XformQuery);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
@@ -161,7 +161,7 @@ public abstract partial class SharedTransformSystem
|
||||
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
|
||||
{
|
||||
return ContainsEntity(xform, entityTransform, _xformQuery);
|
||||
return ContainsEntity(xform, entityTransform, XformQuery);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
@@ -222,7 +222,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (!component._mapIdInitialized)
|
||||
{
|
||||
FindMapIdAndSet(uid, component, EntityManager, _xformQuery, _mapManager);
|
||||
FindMapIdAndSet(uid, component, EntityManager, XformQuery, _mapManager);
|
||||
component._mapIdInitialized = true;
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ public abstract partial class SharedTransformSystem
|
||||
// Note that _children is a HashSet<EntityUid>,
|
||||
// so duplicate additions (which will happen) don't matter.
|
||||
|
||||
var parentXform = _xformQuery.GetComponent(component.ParentUid);
|
||||
var parentXform = XformQuery.GetComponent(component.ParentUid);
|
||||
if (parentXform.LifeStage > ComponentLifeStage.Running || LifeStage(component.ParentUid) > EntityLifeStage.MapInitialized)
|
||||
{
|
||||
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
|
||||
@@ -298,7 +298,7 @@ public abstract partial class SharedTransformSystem
|
||||
if (!xform._parent.IsValid())
|
||||
return;
|
||||
|
||||
var parentXform = _xformQuery.GetComponent(xform._parent);
|
||||
var parentXform = XformQuery.GetComponent(xform._parent);
|
||||
InitializeGridUid(xform._parent, parentXform);
|
||||
xform._gridUid = parentXform._gridUid;
|
||||
}
|
||||
@@ -356,7 +356,7 @@ public abstract partial class SharedTransformSystem
|
||||
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
|
||||
DebugTools.Assert(gridId == null || HasComp<MapGridComponent>(gridId));
|
||||
|
||||
xformQuery ??= _xformQuery;
|
||||
xformQuery ??= XformQuery;
|
||||
SetGridIdRecursive(uid, xform, gridId, xformQuery.Value);
|
||||
}
|
||||
|
||||
@@ -379,27 +379,26 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
#region Local Position
|
||||
|
||||
public void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
|
||||
[Obsolete("use override with EntityUid")]
|
||||
public void SetLocalPosition(TransformComponent xform, Vector2 value)
|
||||
{
|
||||
if (!Resolve(uid, ref xform)) return;
|
||||
SetLocalPosition(xform, value);
|
||||
SetLocalPosition(xform.Owner, value, xform);
|
||||
}
|
||||
|
||||
public virtual void SetLocalPosition(TransformComponent xform, Vector2 value)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
xform.LocalPosition = value;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
|
||||
=> SetLocalPositionNoLerp(uid, value, xform);
|
||||
|
||||
|
||||
[Obsolete("use override with EntityUid")]
|
||||
public void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
|
||||
=> SetLocalPositionNoLerp(xform.Owner, value, xform);
|
||||
|
||||
public void SetLocalPositionNoLerp(EntityUid uid, Vector2 value, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref xform)) return;
|
||||
SetLocalPositionNoLerp(xform, value);
|
||||
}
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
public virtual void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
xform.LocalPosition = value;
|
||||
#pragma warning restore CS0618
|
||||
@@ -409,27 +408,24 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
#region Local Rotation
|
||||
|
||||
public void SetLocalRotationNoLerp(EntityUid uid, Angle angle)
|
||||
public void SetLocalRotationNoLerp(EntityUid uid, Angle value, TransformComponent? xform = null)
|
||||
{
|
||||
SetLocalRotationNoLerp(_xformQuery.GetComponent(uid), angle);
|
||||
}
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
public virtual void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
|
||||
{
|
||||
xform.LocalRotation = angle;
|
||||
}
|
||||
|
||||
public void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref xform)) return;
|
||||
SetLocalRotation(xform, value);
|
||||
}
|
||||
|
||||
public virtual void SetLocalRotation(TransformComponent xform, Angle value)
|
||||
{
|
||||
xform.LocalRotation = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
|
||||
=> SetLocalRotationNoLerp(uid, value, xform);
|
||||
|
||||
[Obsolete("use override with EntityUid")]
|
||||
public void SetLocalRotation(TransformComponent xform, Angle value)
|
||||
{
|
||||
SetLocalRotation(xform.Owner, value, xform);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coordinates
|
||||
@@ -487,7 +483,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (value.EntityId.IsValid())
|
||||
{
|
||||
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
|
||||
if (!XformQuery.Resolve(value.EntityId, ref newParent, false))
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
if (_netMan.IsServer || IsClientSide(uid))
|
||||
@@ -525,13 +521,13 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
|
||||
recursiveUid = recursiveXform.ParentUid;
|
||||
recursiveXform = _xformQuery.GetComponent(recursiveUid);
|
||||
recursiveXform = XformQuery.GetComponent(recursiveUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xform._parent.IsValid())
|
||||
_xformQuery.Resolve(xform._parent, ref oldParent);
|
||||
XformQuery.Resolve(xform._parent, ref oldParent);
|
||||
|
||||
oldParent?._children.Remove(uid);
|
||||
newParent?._children.Add(uid);
|
||||
@@ -541,7 +537,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (newParent != null)
|
||||
{
|
||||
xform.ChangeMapId(newParent.MapID, _xformQuery);
|
||||
xform.ChangeMapId(newParent.MapID, XformQuery);
|
||||
|
||||
if (!xform._gridInitialized)
|
||||
InitializeGridUid(uid, xform);
|
||||
@@ -554,11 +550,11 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
xform.ChangeMapId(MapId.Nullspace, _xformQuery);
|
||||
xform.ChangeMapId(MapId.Nullspace, XformQuery);
|
||||
if (!xform._gridInitialized)
|
||||
InitializeGridUid(uid, xform);
|
||||
else
|
||||
SetGridId(uid, xform, null, _xformQuery);
|
||||
SetGridId(uid, xform, null, XformQuery);
|
||||
}
|
||||
|
||||
if (xform.Initialized)
|
||||
@@ -593,7 +589,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public void ReparentChildren(EntityUid oldUid, EntityUid uid)
|
||||
{
|
||||
ReparentChildren(oldUid, uid, _xformQuery);
|
||||
ReparentChildren(oldUid, uid, XformQuery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -620,29 +616,29 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public TransformComponent? GetParent(EntityUid uid)
|
||||
{
|
||||
return GetParent(_xformQuery.GetComponent(uid));
|
||||
return GetParent(XformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
public TransformComponent? GetParent(TransformComponent xform)
|
||||
{
|
||||
if (!xform.ParentUid.IsValid())
|
||||
return null;
|
||||
return _xformQuery.GetComponent(xform.ParentUid);
|
||||
return XformQuery.GetComponent(xform.ParentUid);
|
||||
}
|
||||
|
||||
public EntityUid GetParentUid(EntityUid uid)
|
||||
{
|
||||
return _xformQuery.GetComponent(uid).ParentUid;
|
||||
return XformQuery.GetComponent(uid).ParentUid;
|
||||
}
|
||||
|
||||
public void SetParent(EntityUid uid, EntityUid parent)
|
||||
{
|
||||
SetParent(uid, _xformQuery.GetComponent(uid), parent, _xformQuery);
|
||||
SetParent(uid, XformQuery.GetComponent(uid), parent, XformQuery);
|
||||
}
|
||||
|
||||
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, TransformComponent? parentXform = null)
|
||||
{
|
||||
SetParent(uid, xform, parent, _xformQuery, parentXform);
|
||||
SetParent(uid, xform, parent, XformQuery, parentXform);
|
||||
}
|
||||
|
||||
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
|
||||
@@ -671,9 +667,7 @@ public abstract partial class SharedTransformSystem
|
||||
#endregion
|
||||
|
||||
#region States
|
||||
public virtual void ActivateLerp(TransformComponent xform) { }
|
||||
|
||||
public virtual void DeactivateLerp(TransformComponent xform) { }
|
||||
public virtual void ActivateLerp(EntityUid uid, TransformComponent xform) { }
|
||||
|
||||
internal void OnGetState(EntityUid uid, TransformComponent component, ref ComponentGetState args)
|
||||
{
|
||||
@@ -745,24 +739,18 @@ public abstract partial class SharedTransformSystem
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
}
|
||||
|
||||
xform.PrevPosition = newState.LocalPosition;
|
||||
xform.PrevRotation = newState.Rotation;
|
||||
xform._noLocalRotation = newState.NoLocalRotation;
|
||||
|
||||
DebugTools.Assert(xform.ParentUid == parent, "Transform state failed to set parent");
|
||||
DebugTools.Assert(xform.Anchored == newState.Anchored, "Transform state failed to set anchored");
|
||||
}
|
||||
|
||||
if (args.Next is TransformComponentState nextTransform)
|
||||
if (args.Next is TransformComponentState nextTransform
|
||||
&& nextTransform.ParentID == GetNetEntity(xform.ParentUid))
|
||||
{
|
||||
xform.NextPosition = nextTransform.LocalPosition;
|
||||
xform.NextRotation = nextTransform.Rotation;
|
||||
xform.LerpParent = GetEntity(nextTransform.ParentID);
|
||||
ActivateLerp(xform);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeactivateLerp(xform);
|
||||
ActivateLerp(uid, xform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,7 +761,7 @@ public abstract partial class SharedTransformSystem
|
||||
[Pure]
|
||||
public Matrix3 GetWorldMatrix(EntityUid uid)
|
||||
{
|
||||
return GetWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
return GetWorldMatrix(XformQuery.GetComponent(uid), XformQuery);
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
@@ -781,7 +769,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetWorldMatrix(TransformComponent component)
|
||||
{
|
||||
return GetWorldMatrix(component, _xformQuery);
|
||||
return GetWorldMatrix(component, XformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -807,7 +795,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 GetWorldPosition(EntityUid uid)
|
||||
{
|
||||
return GetWorldPosition(_xformQuery.GetComponent(uid));
|
||||
return GetWorldPosition(XformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
@@ -819,7 +807,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
while (component.ParentUid != component.MapUid && component.ParentUid.IsValid())
|
||||
{
|
||||
component = _xformQuery.GetComponent(component.ParentUid);
|
||||
component = XformQuery.GetComponent(component.ParentUid);
|
||||
pos = component._localRotation.RotateVec(pos) + component._localPosition;
|
||||
}
|
||||
|
||||
@@ -842,7 +830,7 @@ public abstract partial class SharedTransformSystem
|
||||
[Pure]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
|
||||
{
|
||||
return GetWorldPositionRotation(_xformQuery.GetComponent(uid));
|
||||
return GetWorldPositionRotation(XformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -853,7 +841,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
while (component.ParentUid != component.MapUid && component.ParentUid.IsValid())
|
||||
{
|
||||
component = _xformQuery.GetComponent(component.ParentUid);
|
||||
component = XformQuery.GetComponent(component.ParentUid);
|
||||
pos = component._localRotation.RotateVec(pos) + component._localPosition;
|
||||
angle += component._localRotation;
|
||||
}
|
||||
@@ -947,7 +935,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
|
||||
{
|
||||
SetWorldPosition(component, worldPos, _xformQuery);
|
||||
SetWorldPosition(component, worldPos, XformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -973,7 +961,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(EntityUid uid)
|
||||
{
|
||||
return GetWorldRotation(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
return GetWorldRotation(XformQuery.GetComponent(uid), XformQuery);
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
@@ -981,7 +969,7 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(TransformComponent component)
|
||||
{
|
||||
return GetWorldRotation(component, _xformQuery);
|
||||
return GetWorldRotation(component, XformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -1040,43 +1028,47 @@ public abstract partial class SharedTransformSystem
|
||||
#region Set Position+Rotation
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var component = xformQuery.GetComponent(uid);
|
||||
SetWorldPositionRotation(component, worldPos, worldRot, xformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use override with EntityUid")]
|
||||
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot)
|
||||
{
|
||||
SetWorldPositionRotation(component, worldPos, worldRot, _xformQuery);
|
||||
SetWorldPositionRotation(component.Owner, worldPos, worldRot, component);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xformQuery)
|
||||
public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, TransformComponent? component = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!component._parent.IsValid())
|
||||
{
|
||||
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
|
||||
return;
|
||||
}
|
||||
|
||||
var (curWorldPos, curWorldRot) = GetWorldPositionRotation(component, xformQuery);
|
||||
var (curWorldPos, curWorldRot) = GetWorldPositionRotation(component);
|
||||
|
||||
var negativeParentWorldRot = component.LocalRotation - curWorldRot;
|
||||
|
||||
var newLocalPos = component.LocalPosition + negativeParentWorldRot.RotateVec(worldPos - curWorldPos);
|
||||
var newLocalRot = component.LocalRotation + worldRot - curWorldRot;
|
||||
|
||||
SetLocalPositionRotation(component, newLocalPos, newLocalRot);
|
||||
SetLocalPositionRotation(uid, newLocalPos, newLocalRot, component);
|
||||
}
|
||||
|
||||
[Obsolete("Use override with EntityUid")]
|
||||
public void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
|
||||
=> SetLocalPositionRotation(xform.Owner, pos, rot, xform);
|
||||
|
||||
/// <summary>
|
||||
/// Simultaneously set the position and rotation. This is better than setting individually, as it reduces the number of move events and matrix rebuilding operations.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
|
||||
public virtual void SetLocalPositionRotation(EntityUid uid, Vector2 pos, Angle rot, TransformComponent? xform = null)
|
||||
{
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
if (!xform._parent.IsValid())
|
||||
{
|
||||
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
|
||||
@@ -1097,14 +1089,14 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
|
||||
|
||||
Dirty(xform.Owner, xform);
|
||||
Dirty(uid, xform);
|
||||
xform.MatricesDirty = true;
|
||||
|
||||
if (!xform.Initialized)
|
||||
return;
|
||||
|
||||
var moveEvent = new MoveEvent(xform.Owner, oldPosition, xform.Coordinates, oldRotation, rot, xform, _gameTiming.ApplyingState);
|
||||
RaiseLocalEvent(xform.Owner, ref moveEvent, true);
|
||||
var moveEvent = new MoveEvent(uid, oldPosition, xform.Coordinates, oldRotation, rot, xform, _gameTiming.ApplyingState);
|
||||
RaiseLocalEvent(uid, ref moveEvent, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1114,14 +1106,14 @@ public abstract partial class SharedTransformSystem
|
||||
[Pure]
|
||||
public Matrix3 GetInvWorldMatrix(EntityUid uid)
|
||||
{
|
||||
return GetInvWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
return GetInvWorldMatrix(XformQuery.GetComponent(uid), XformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetInvWorldMatrix(TransformComponent component)
|
||||
{
|
||||
return GetInvWorldMatrix(component, _xformQuery);
|
||||
return GetInvWorldMatrix(component, XformQuery);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -1146,14 +1138,14 @@ public abstract partial class SharedTransformSystem
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
|
||||
GetWorldPositionRotationMatrix(EntityUid uid)
|
||||
{
|
||||
return GetWorldPositionRotationMatrix(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
return GetWorldPositionRotationMatrix(XformQuery.GetComponent(uid), XformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
|
||||
GetWorldPositionRotationMatrix(TransformComponent xform)
|
||||
{
|
||||
return GetWorldPositionRotationMatrix(xform, _xformQuery);
|
||||
return GetWorldPositionRotationMatrix(xform, XformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1177,13 +1169,13 @@ public abstract partial class SharedTransformSystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(EntityUid uid)
|
||||
{
|
||||
return GetWorldPositionRotationInvMatrix(_xformQuery.GetComponent(uid));
|
||||
return GetWorldPositionRotationInvMatrix(XformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(TransformComponent xform)
|
||||
{
|
||||
return GetWorldPositionRotationInvMatrix(xform, _xformQuery);
|
||||
return GetWorldPositionRotationInvMatrix(xform, XformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1207,14 +1199,14 @@ public abstract partial class SharedTransformSystem
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
|
||||
GetWorldPositionRotationMatrixWithInv(EntityUid uid)
|
||||
{
|
||||
return GetWorldPositionRotationMatrixWithInv(_xformQuery.GetComponent(uid), _xformQuery);
|
||||
return GetWorldPositionRotationMatrixWithInv(XformQuery.GetComponent(uid), XformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
|
||||
GetWorldPositionRotationMatrixWithInv(TransformComponent xform)
|
||||
{
|
||||
return GetWorldPositionRotationMatrixWithInv(xform, _xformQuery);
|
||||
return GetWorldPositionRotationMatrixWithInv(xform, XformQuery);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1237,8 +1229,8 @@ public abstract partial class SharedTransformSystem
|
||||
#region AttachToGridOrMap
|
||||
public void AttachToGridOrMap(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
if (_xformQuery.Resolve(uid, ref xform))
|
||||
AttachToGridOrMap(uid, xform, _xformQuery);
|
||||
if (XformQuery.Resolve(uid, ref xform))
|
||||
AttachToGridOrMap(uid, xform, XformQuery);
|
||||
}
|
||||
|
||||
public void AttachToGridOrMap(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> query)
|
||||
@@ -1278,15 +1270,15 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
coordinates = null;
|
||||
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return false;
|
||||
|
||||
if (!xform.ParentUid.IsValid())
|
||||
return false;
|
||||
|
||||
EntityUid newParent;
|
||||
var oldPos = GetWorldPosition(xform, _xformQuery);
|
||||
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, _xformQuery, out var gridUid, out _))
|
||||
var oldPos = GetWorldPosition(xform, XformQuery);
|
||||
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, XformQuery, out var gridUid, out _))
|
||||
{
|
||||
newParent = gridUid;
|
||||
}
|
||||
@@ -1299,7 +1291,7 @@ public abstract partial class SharedTransformSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
coordinates = new(newParent, GetInvWorldMatrix(newParent, _xformQuery).Transform(oldPos));
|
||||
coordinates = new(newParent, GetInvWorldMatrix(newParent, XformQuery).Transform(oldPos));
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
@@ -1308,7 +1300,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public void DetachParentToNull(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
_xformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
|
||||
XformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
|
||||
DetachParentToNull(uid, xform, oldXform);
|
||||
}
|
||||
|
||||
@@ -1369,7 +1361,7 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (LifeStage(uid) > EntityLifeStage.Initialized)
|
||||
{
|
||||
SetGridId(uid, component, uid, _xformQuery);
|
||||
SetGridId(uid, component, uid, XformQuery);
|
||||
return;
|
||||
}
|
||||
component._gridInitialized = true;
|
||||
@@ -1383,10 +1375,10 @@ public abstract partial class SharedTransformSystem
|
||||
public void PlaceNextToOrDrop(EntityUid uid, EntityUid target,
|
||||
TransformComponent? xform = null, TransformComponent? targetXform = null)
|
||||
{
|
||||
if (!_xformQuery.Resolve(target, ref targetXform))
|
||||
if (!XformQuery.Resolve(target, ref targetXform))
|
||||
return;
|
||||
|
||||
if (!_xformQuery.Resolve(uid, ref xform))
|
||||
if (!XformQuery.Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
var meta = _metaQuery.GetComponent(target);
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
protected EntityQuery<TransformComponent> XformQuery;
|
||||
|
||||
private readonly Queue<MoveEvent> _gridMoves = new();
|
||||
private readonly Queue<MoveEvent> _otherMoves = new();
|
||||
@@ -39,7 +39,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
XformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<TileChangedEvent>(MapManagerOnTileChanged);
|
||||
SubscribeLocalEvent<TransformComponent, ComponentInit>(OnCompInit);
|
||||
@@ -85,17 +85,17 @@ namespace Robust.Shared.GameObjects
|
||||
if (!TryComp(gridId, out BroadphaseComponent? lookup) || !_mapManager.TryGetGrid(gridId, out var grid))
|
||||
return;
|
||||
|
||||
if (!_xformQuery.TryGetComponent(gridId, out var gridXform))
|
||||
if (!XformQuery.TryGetComponent(gridId, out var gridXform))
|
||||
return;
|
||||
|
||||
if (!_xformQuery.TryGetComponent(gridXform.MapUid, out var mapTransform))
|
||||
if (!XformQuery.TryGetComponent(gridXform.MapUid, out var mapTransform))
|
||||
return;
|
||||
|
||||
var aabb = _lookup.GetLocalBounds(tileIndices, grid.TileSize);
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(lookup, aabb, LookupFlags.Uncontained | LookupFlags.Approximate))
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(entity, out var xform) || xform.ParentUid != gridId)
|
||||
if (!XformQuery.TryGetComponent(entity, out var xform) || xform.ParentUid != gridId)
|
||||
continue;
|
||||
|
||||
if (!aabb.Contains(xform.LocalPosition))
|
||||
@@ -148,7 +148,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public EntityCoordinates GetMoverCoordinates(EntityUid uid)
|
||||
{
|
||||
return GetMoverCoordinates(uid, _xformQuery.GetComponent(uid));
|
||||
return GetMoverCoordinates(uid, XformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
public EntityCoordinates GetMoverCoordinates(EntityUid uid, TransformComponent xform)
|
||||
@@ -168,11 +168,11 @@ namespace Robust.Shared.GameObjects
|
||||
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
|
||||
|
||||
// Not parented to grid so convert their pos back to the grid.
|
||||
var worldPos = GetWorldPosition(xform, _xformQuery);
|
||||
var worldPos = GetWorldPosition(xform, XformQuery);
|
||||
|
||||
return xform.GridUid == null
|
||||
? new EntityCoordinates(xform.MapUid ?? xform.ParentUid, worldPos)
|
||||
: new EntityCoordinates(xform.GridUid.Value, _xformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
|
||||
: new EntityCoordinates(xform.GridUid.Value, XformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
|
||||
}
|
||||
|
||||
public EntityCoordinates GetMoverCoordinates(EntityCoordinates coordinates, EntityQuery<TransformComponent> xformQuery)
|
||||
@@ -191,7 +191,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (!parentUid.IsValid())
|
||||
return coordinates;
|
||||
|
||||
var parentXform = _xformQuery.GetComponent(parentUid);
|
||||
var parentXform = XformQuery.GetComponent(parentUid);
|
||||
|
||||
// GriddUid is only set after init.
|
||||
if (!parentXform._gridInitialized)
|
||||
@@ -209,11 +209,11 @@ namespace Robust.Shared.GameObjects
|
||||
DebugTools.Assert(!_mapManager.IsGrid(parentUid) && !_mapManager.IsMap(parentUid));
|
||||
|
||||
// Not parented to grid so convert their pos back to the grid.
|
||||
var worldPos = GetWorldMatrix(parentXform, _xformQuery).Transform(coordinates.Position);
|
||||
var worldPos = GetWorldMatrix(parentXform, XformQuery).Transform(coordinates.Position);
|
||||
|
||||
return parentXform.GridUid == null
|
||||
? new EntityCoordinates(mapId ?? parentUid, worldPos)
|
||||
: new EntityCoordinates(parentXform.GridUid.Value, _xformQuery.GetComponent(parentXform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
|
||||
: new EntityCoordinates(parentXform.GridUid.Value, XformQuery.GetComponent(parentXform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,15 +231,15 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// Is the entity directly parented to the grid?
|
||||
if (xform.GridUid == xform.ParentUid)
|
||||
return (xform.Coordinates, GetWorldRotation(xform, _xformQuery));
|
||||
return (xform.Coordinates, GetWorldRotation(xform, XformQuery));
|
||||
|
||||
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
|
||||
|
||||
var (pos, worldRot) = GetWorldPositionRotation(xform, _xformQuery);
|
||||
var (pos, worldRot) = GetWorldPositionRotation(xform, XformQuery);
|
||||
|
||||
var coords = xform.GridUid == null
|
||||
? new EntityCoordinates(xform.MapUid ?? xform.ParentUid, pos)
|
||||
: new EntityCoordinates(xform.GridUid.Value, _xformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(pos));
|
||||
: new EntityCoordinates(xform.GridUid.Value, XformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(pos));
|
||||
|
||||
return (coords, worldRot);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -110,5 +109,29 @@ public abstract partial class SharedJointSystem
|
||||
}
|
||||
|
||||
Dirty(uid, component);
|
||||
|
||||
#if DEBUG
|
||||
if (component.Relay == null)
|
||||
return;
|
||||
|
||||
if (TryComp(uid, out JointComponent? jointComp))
|
||||
{
|
||||
foreach (var joint in jointComp.Joints.Values)
|
||||
{
|
||||
DebugTools.AssertNotEqual(joint.BodyAUid, component.Relay);
|
||||
DebugTools.AssertNotEqual(joint.BodyBUid, component.Relay);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (TryComp(component.Relay, out JointComponent? relayJointComp))
|
||||
{
|
||||
foreach (var joint in relayJointComp.Joints.Values)
|
||||
{
|
||||
DebugTools.AssertNotEqual(joint.BodyAUid, uid);
|
||||
DebugTools.AssertNotEqual(joint.BodyBUid, uid);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,8 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
jointComponentA ??= EnsureComp<JointComponent>(aUid);
|
||||
jointComponentB ??= EnsureComp<JointComponent>(bUid);
|
||||
DebugTools.Assert(jointComponentA.Owner == aUid && jointComponentB.Owner == bUid);
|
||||
DebugTools.AssertNotEqual(jointComponentA.Relay, bUid);
|
||||
DebugTools.AssertNotEqual(jointComponentB.Relay, aUid);
|
||||
|
||||
var jointsA = jointComponentA.Joints;
|
||||
var jointsB = jointComponentB.Joints;
|
||||
@@ -190,10 +192,10 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
|
||||
_physics.WakeBody(aUid, body: bodyA);
|
||||
_physics.WakeBody(bUid, body: bodyB);
|
||||
Dirty(bodyA);
|
||||
Dirty(bodyB);
|
||||
Dirty(jointComponentA);
|
||||
Dirty(jointComponentB);
|
||||
Dirty(aUid, bodyA);
|
||||
Dirty(bUid, bodyB);
|
||||
Dirty(aUid, jointComponentA);
|
||||
Dirty(bUid, jointComponentB);
|
||||
|
||||
// Also flag these for checking juusssttt in case.
|
||||
_dirtyJoints.Add(jointComponentA);
|
||||
|
||||
@@ -423,16 +423,19 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
var uidA = joint.BodyAUid;
|
||||
var uidB = joint.BodyBUid;
|
||||
DebugTools.AssertNotEqual(uidA, uidB);
|
||||
|
||||
if (jointQuery.TryGetComponent(uidA, out var jointCompA) &&
|
||||
jointCompA.Relay != null)
|
||||
{
|
||||
DebugTools.AssertNotEqual(uidB, jointCompA.Relay.Value);
|
||||
uidA = jointCompA.Relay.Value;
|
||||
}
|
||||
|
||||
if (jointQuery.TryGetComponent(uidB, out var jointCompB) &&
|
||||
jointCompB.Relay != null)
|
||||
{
|
||||
DebugTools.AssertNotEqual(uidA, jointCompB.Relay.Value);
|
||||
uidB = jointCompB.Relay.Value;
|
||||
}
|
||||
|
||||
|
||||
@@ -222,9 +222,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
Assert.That(entManager.GetComponent<TransformComponent>(containerEntity).ChildCount, Is.EqualTo(1));
|
||||
Assert.That(entManager.GetComponent<TransformComponent>(containerEntity).ChildEntities.First(), Is.EqualTo(insertEntity));
|
||||
|
||||
result = insertEntity.TryGetContainerMan(out var resultContainerMan);
|
||||
result = containerSys.TryGetContainingContainer(insertEntity, out var resultContainerMan);
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(resultContainerMan, Is.EqualTo(container.Manager));
|
||||
Assert.That(resultContainerMan?.Manager, Is.EqualTo(container.Manager));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
214
Robust.UnitTesting/Server/GameStates/PvsReEntryTest.cs
Normal file
214
Robust.UnitTesting/Server/GameStates/PvsReEntryTest.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using cIPlayerManager = Robust.Client.Player.IPlayerManager;
|
||||
using sIPlayerManager = Robust.Server.Player.IPlayerManager;
|
||||
|
||||
namespace Robust.UnitTesting.Server.GameStates;
|
||||
|
||||
public sealed class PvsReEntryTest : RobustIntegrationTest
|
||||
{
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Checks that there are no issues when an entity enters, leaves, then enters pvs while the client drops packets.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestLossyReEntry()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var sEntMan = server.ResolveDependency<IEntityManager>();
|
||||
var confMan = server.ResolveDependency<IConfigurationManager>();
|
||||
var sPlayerMan = server.ResolveDependency<sIPlayerManager>();
|
||||
var xforms = sEntMan.System<SharedTransformSystem>();
|
||||
var stateMan = (ClientGameStateManager) client.ResolveDependency<IClientGameStateManager>();
|
||||
|
||||
var cEntMan = client.ResolveDependency<IEntityManager>();
|
||||
var netMan = client.ResolveDependency<IClientNetManager>();
|
||||
var cPlayerMan = client.ResolveDependency<cIPlayerManager>();
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
||||
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Ensure client & server ticks are synced.
|
||||
// Client runs 1 tick ahead
|
||||
{
|
||||
var sTick = (int)server.Timing.CurTick.Value;
|
||||
var cTick = (int)client.Timing.CurTick.Value;
|
||||
var delta = cTick - sTick;
|
||||
|
||||
if (delta > 1)
|
||||
await server.WaitRunTicks(delta - 1);
|
||||
else if (delta < 1)
|
||||
await client.WaitRunTicks(1 - delta);
|
||||
|
||||
sTick = (int)server.Timing.CurTick.Value;
|
||||
cTick = (int)client.Timing.CurTick.Value;
|
||||
delta = cTick - sTick;
|
||||
Assert.That(delta, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
// Set up map and spawn player
|
||||
EntityUid map = default;
|
||||
NetEntity player = default;
|
||||
NetEntity entity = default;
|
||||
EntityCoordinates coords = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapId = mapMan.CreateMap();
|
||||
map = mapMan.GetMapEntityId(mapId);
|
||||
coords = new(map, default);
|
||||
|
||||
var playerUid = sEntMan.SpawnEntity(null, coords);
|
||||
var entUid = sEntMan.SpawnEntity(null, coords);
|
||||
entity = sEntMan.GetNetEntity(entUid);
|
||||
player = sEntMan.GetNetEntity(playerUid);
|
||||
|
||||
// Attach player.
|
||||
var session = (IPlayerSession) sPlayerMan.Sessions.First();
|
||||
sEntMan.System<ActorSystem>().Attach(playerUid, session);
|
||||
session.JoinGame();
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
Assert.That(player, Is.Not.EqualTo(NetEntity.Invalid));
|
||||
Assert.That(entity, Is.Not.EqualTo(NetEntity.Invalid));
|
||||
|
||||
// Check player got properly attached, and has received the other entity.
|
||||
MetaDataComponent? meta = default!;
|
||||
await client.WaitPost(() =>
|
||||
{
|
||||
Assert.That(cEntMan.TryGetEntityData(entity, out _, out meta));
|
||||
Assert.That(cEntMan.TryGetEntity(player, out var cPlayerUid));
|
||||
Assert.That(cPlayerMan.LocalPlayer?.ControlledEntity, Is.EqualTo(cPlayerUid));
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
|
||||
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
|
||||
});
|
||||
|
||||
var lastDirty = meta.LastModifiedTick;
|
||||
Assert.That(lastDirty, Is.GreaterThan(GameTick.Zero));
|
||||
|
||||
// Move the player outside of the entity's PVS range
|
||||
// note that we move the PLAYER not the entity, as we don't want to dirty the entity.
|
||||
var farAway = new EntityCoordinates(map, new Vector2(100, 100));
|
||||
await server.WaitPost( () => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Client should have detached the entity to null space.
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.Detached));
|
||||
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
|
||||
|
||||
// Move the player back into range
|
||||
await server.WaitPost( () => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Entity is back in pvs range
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
|
||||
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
|
||||
|
||||
// Oh no, the client is going through a tunnel!
|
||||
stateMan.DropStates = true;
|
||||
var timing = client.ResolveDependency<IClientGameTiming>();
|
||||
var lastRealTick = timing.LastRealTick;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Even though the client is receiving no new states, it will still have applied some from the state buffer.
|
||||
Assert.That(timing.LastRealTick, Is.GreaterThan(lastRealTick));
|
||||
lastRealTick = timing.LastRealTick;
|
||||
|
||||
// Move the player outside of pvs range.
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
|
||||
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Client should have exhausted the buffer -- client has not been applying any states.
|
||||
Assert.That(timing.LastRealTick, Is.EqualTo(lastRealTick));
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
|
||||
|
||||
// However pvs-leave messages are sent separately, and are sent reliably. So they are still being received.
|
||||
// Though in a realistic scenario they should probably be delayed somewhat.
|
||||
Assert.That(stateMan.IsQueuedForDetach(entity), Is.True);
|
||||
|
||||
// Move the entity back into range
|
||||
await server.WaitPost( () => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Still hasn't been applying states.
|
||||
Assert.That(timing.LastRealTick, Is.EqualTo(lastRealTick));
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
|
||||
|
||||
// The pvs-leave message has not yet been processed, detaching is still queued.
|
||||
Assert.That(stateMan.IsQueuedForDetach(entity), Is.True);
|
||||
|
||||
// Client clears the tunnel, starts receiving states again.
|
||||
stateMan.DropStates = false;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Entity should be in PVS range, client should know about it:
|
||||
Assert.That(timing.LastRealTick, Is.GreaterThan(lastRealTick));
|
||||
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
|
||||
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
|
||||
|
||||
// The entity itself should not have been dirtied since creation -- only the player has been moving.
|
||||
// If the test moves the entity instead of the player, then the test doesn't actually work.
|
||||
Assert.That(meta.LastModifiedTick, Is.EqualTo(lastDirty));
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user