From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 31 Mar 2023 03:40:38 +0000 (+1100) Subject: Make trays clientside (#14826) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=763089570d988dfa80255595c669b40199278bd6;p=space-station-14.git Make trays clientside (#14826) --- diff --git a/Content.Client/SubFloor/SubFloorHideSystem.cs b/Content.Client/SubFloor/SubFloorHideSystem.cs index 0532f177de..1d47f93a5a 100644 --- a/Content.Client/SubFloor/SubFloorHideSystem.cs +++ b/Content.Client/SubFloor/SubFloorHideSystem.cs @@ -40,16 +40,12 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem scannerRevealed &= !ShowAll; // no transparency for show-subfloor mode. var revealed = !covered || ShowAll || scannerRevealed; - var transparency = scannerRevealed ? component.ScannerTransparency : 1f; // set visibility & color of each layer foreach (var layer in args.Sprite.AllLayers) { // pipe connection visuals are updated AFTER this, and may re-hide some layers layer.Visible = revealed; - - if (layer.Visible) - layer.Color = layer.Color.WithAlpha(transparency); } // Is there some layer that is always visible? diff --git a/Content.Client/SubFloor/TrayRevealedComponent.cs b/Content.Client/SubFloor/TrayRevealedComponent.cs new file mode 100644 index 0000000000..e1a8b0caf3 --- /dev/null +++ b/Content.Client/SubFloor/TrayRevealedComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Client.SubFloor; + +/// +/// Added clientside if an entity is revealed for TRay. +/// +[RegisterComponent] +public sealed class TrayRevealedComponent : Component +{ + +} diff --git a/Content.Client/SubFloor/TrayScannerSystem.cs b/Content.Client/SubFloor/TrayScannerSystem.cs new file mode 100644 index 0000000000..2f53226d62 --- /dev/null +++ b/Content.Client/SubFloor/TrayScannerSystem.cs @@ -0,0 +1,144 @@ +using Content.Client.Hands; +using Content.Shared.SubFloor; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Shared.Map; +using Robust.Shared.Timing; + +namespace Content.Client.SubFloor; + +public sealed class TrayScannerSystem : SharedTrayScannerSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly AnimationPlayerSystem _animation = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private const string TRayAnimationKey = "trays"; + private const double AnimationLength = 0.5; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + // TODO: Multiple viewports or w/e + var player = _player.LocalPlayer?.ControlledEntity; + var xformQuery = GetEntityQuery(); + + if (!xformQuery.TryGetComponent(player, out var playerXform)) + return; + + var playerPos = _transform.GetWorldPosition(playerXform, xformQuery); + var playerMap = playerXform.MapID; + var range = 0f; + + if (TryComp(player, out var playerHands) && + TryComp(playerHands.ActiveHandEntity, out var scanner) && scanner.Enabled) + { + range = scanner.Range; + + foreach (var comp in _lookup.GetComponentsInRange(playerMap, playerPos, range)) + { + var uid = comp.Owner; + if (!comp.IsUnderCover || !comp.BlockAmbience | !comp.BlockInteractions) + continue; + + EnsureComp(uid); + } + } + + var revealedQuery = AllEntityQuery(); + var subfloorQuery = GetEntityQuery(); + + while (revealedQuery.MoveNext(out var uid, out _, out var sprite, out var xform)) + { + var worldPos = _transform.GetWorldPosition(xform, xformQuery); + + // Revealing + // Add buffer range to avoid flickers. + if (subfloorQuery.HasComponent(uid) && + xform.MapID != MapId.Nullspace && + xform.MapID == playerMap && + xform.Anchored && + range != 0f && + (playerPos - worldPos).Length <= range + 0.5f) + { + // Due to the fact client is predicting this server states will reset it constantly + if ((!_appearance.TryGetData(uid, SubFloorVisuals.ScannerRevealed, out bool value) || !value) && + sprite.Color.A > SubfloorRevealAlpha) + { + sprite.Color = sprite.Color.WithAlpha(0f); + } + + SetRevealed(uid, true); + + if (sprite.Color.A >= SubfloorRevealAlpha || _animation.HasRunningAnimation(uid, TRayAnimationKey)) + continue; + + _animation.Play(uid, new Animation() + { + Length = TimeSpan.FromSeconds(AnimationLength), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Color), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), 0f), + new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(SubfloorRevealAlpha), (float) AnimationLength) + } + } + } + }, TRayAnimationKey); + } + // Hiding + else + { + // Hidden completely so unreveal and reset the alpha. + if (sprite.Color.A <= 0f) + { + SetRevealed(uid, false); + RemCompDeferred(uid); + sprite.Color = sprite.Color.WithAlpha(1f); + continue; + } + + SetRevealed(uid, true); + + if (_animation.HasRunningAnimation(uid, TRayAnimationKey)) + continue; + + _animation.Play(uid, new Animation() + { + Length = TimeSpan.FromSeconds(AnimationLength), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Color), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Color, 0f), + new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), (float) AnimationLength) + } + } + } + }, TRayAnimationKey); + } + } + } + + private void SetRevealed(EntityUid uid, bool value) + { + _appearance.SetData(uid, SubFloorVisuals.ScannerRevealed, value); + } +} diff --git a/Content.Server/SubFloor/TrayScannerSystem.cs b/Content.Server/SubFloor/TrayScannerSystem.cs new file mode 100644 index 0000000000..f93d656d61 --- /dev/null +++ b/Content.Server/SubFloor/TrayScannerSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.SubFloor; + +namespace Content.Server.SubFloor; + +public sealed class TrayScannerSystem : SharedTrayScannerSystem +{ + +} diff --git a/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs b/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs index f726827b47..9d2eb3de69 100644 --- a/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs @@ -16,7 +16,6 @@ namespace Content.Shared.SubFloor { [Dependency] protected readonly IMapManager MapManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; - [Dependency] private readonly TrayScannerSystem _trayScannerSystem = default!; [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; @@ -70,7 +69,6 @@ namespace Content.Shared.SubFloor if (args.Anchored) { var xform = Transform(uid); - _trayScannerSystem.OnSubfloorAnchored(uid, component, xform); UpdateFloorCover(uid, component, xform); } else if (component.IsUnderCover) @@ -139,37 +137,6 @@ namespace Content.Shared.SubFloor } } - /// - /// This function is used by T-Ray scanners or other sub-floor revealing entities to toggle visibility. - /// - public void SetEntitiesRevealed(IEnumerable entities, EntityUid revealer, bool visible) - { - foreach (var uid in entities) - { - SetEntityRevealed(uid, revealer, visible); - } - } - - /// - /// This function is used by T-Ray scanners or other sub-floor revealing entities to toggle visibility. - /// - public void SetEntityRevealed(EntityUid uid, EntityUid revealer, bool visible, SubFloorHideComponent? hideComp = null) - { - if (!Resolve(uid, ref hideComp, false)) - return; - - if (visible) - { - if (hideComp.RevealedBy.Add(revealer) && hideComp.RevealedBy.Count == 1) - UpdateAppearance(uid, hideComp); - - return; - } - - if (hideComp.RevealedBy.Remove(revealer) && hideComp.RevealedBy.Count == 0) - UpdateAppearance(uid, hideComp); - } - public void UpdateAppearance( EntityUid uid, SubFloorHideComponent? hideComp = null, @@ -186,7 +153,6 @@ namespace Content.Shared.SubFloor if (Resolve(uid, ref appearance, false)) { Appearance.SetData(uid, SubFloorVisuals.Covered, hideComp.IsUnderCover, appearance); - Appearance.SetData(uid, SubFloorVisuals.ScannerRevealed, hideComp.RevealedBy.Count != 0, appearance); } } } @@ -194,8 +160,15 @@ namespace Content.Shared.SubFloor [Serializable, NetSerializable] public enum SubFloorVisuals : byte { - Covered, // is there a floor tile over this entity - ScannerRevealed, // is this entity revealed by a scanner or some other entity? + /// + /// Is there a floor tile over this entity + /// + Covered, + + /// + /// Is this entity revealed by a scanner or some other entity? + /// + ScannerRevealed, } public enum SubfloorLayers : byte diff --git a/Content.Shared/SubFloor/SharedTrayScannerSystem.cs b/Content.Shared/SubFloor/SharedTrayScannerSystem.cs new file mode 100644 index 0000000000..1ba88c571c --- /dev/null +++ b/Content.Shared/SubFloor/SharedTrayScannerSystem.cs @@ -0,0 +1,69 @@ +using Content.Shared.Interaction; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using System.Linq; + +namespace Content.Shared.SubFloor; + +public abstract class SharedTrayScannerSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public const float SubfloorRevealAlpha = 0.8f; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrayScannerGetState); + SubscribeLocalEvent(OnTrayScannerHandleState); + SubscribeLocalEvent(OnTrayScannerActivate); + } + + private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args) + { + SetScannerEnabled(uid, !scanner.Enabled, scanner); + } + + private void SetScannerEnabled(EntityUid uid, bool enabled, TrayScannerComponent? scanner = null) + { + if (!Resolve(uid, ref scanner) || scanner.Enabled == enabled) + return; + + scanner.Enabled = enabled; + Dirty(scanner); + + // We don't remove from _activeScanners on disabled, because the update function will handle that, as well as + // managing the revealed subfloor entities + + if (TryComp(uid, out var appearance)) + { + _appearance.SetData(uid, TrayScannerVisual.Visual, scanner.Enabled ? TrayScannerVisual.On : TrayScannerVisual.Off, appearance); + } + } + + private void OnTrayScannerGetState(EntityUid uid, TrayScannerComponent scanner, ref ComponentGetState args) + { + args.State = new TrayScannerState(scanner.Enabled); + } + + private void OnTrayScannerHandleState(EntityUid uid, TrayScannerComponent scanner, ref ComponentHandleState args) + { + if (args.Current is not TrayScannerState state) + return; + + SetScannerEnabled(uid, state.Enabled, scanner); + } +} + +[Serializable, NetSerializable] +public enum TrayScannerVisual : sbyte +{ + Visual, + On, + Off +} diff --git a/Content.Shared/SubFloor/SubFloorHideComponent.cs b/Content.Shared/SubFloor/SubFloorHideComponent.cs index e3a170b89d..415b2127e9 100644 --- a/Content.Shared/SubFloor/SubFloorHideComponent.cs +++ b/Content.Shared/SubFloor/SubFloorHideComponent.cs @@ -39,24 +39,11 @@ namespace Content.Shared.SubFloor [DataField("blockAmbience")] public bool BlockAmbience { get; set; } = true; - /// - /// When revealed using some scanning tool, what transparency should be used to draw this item? - /// - [DataField("scannerTransparency")] - public float ScannerTransparency = 0.8f; - /// /// Sprite layer keys for the layers that are always visible, even if the entity is below a floor tile. E.g., /// the vent part of a vent is always visible, even though the piping is hidden. /// [DataField("visibleLayers")] public HashSet VisibleLayers = new() { SubfloorLayers.FirstLayer }; - - /// - /// The entities this subfloor is revealed by. - /// - [ViewVariables] - [Access(typeof(SharedSubFloorHideSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public HashSet RevealedBy { get; set; } = new(); } } diff --git a/Content.Shared/SubFloor/TrayScannerComponent.cs b/Content.Shared/SubFloor/TrayScannerComponent.cs index c6bc3d46f3..811d120a1d 100644 --- a/Content.Shared/SubFloor/TrayScannerComponent.cs +++ b/Content.Shared/SubFloor/TrayScannerComponent.cs @@ -3,33 +3,19 @@ using Robust.Shared.Serialization; namespace Content.Shared.SubFloor; -[RegisterComponent] -[NetworkedComponent] +[RegisterComponent, NetworkedComponent] public sealed class TrayScannerComponent : Component { /// /// Whether the scanner is currently on. /// - [ViewVariables] - public bool Enabled { get; set; } - - /// - /// Last position of the scanner. Rounded to integers to avoid excessive entity lookups when moving. - /// - [ViewVariables] - public Vector2i? LastLocation { get; set; } + [ViewVariables, DataField("enabled")] public bool Enabled; /// /// Radius in which the scanner will reveal entities. Centered on the . /// - [DataField("range")] - public float Range { get; set; } = 2.5f; - - /// - /// The sub-floor entities that this scanner is currently revealing. - /// - [ViewVariables] - public HashSet RevealedSubfloors = new(); + [ViewVariables(VVAccess.ReadWrite), DataField("range")] + public float Range = 4f; } [Serializable, NetSerializable] diff --git a/Content.Shared/SubFloor/TrayScannerSystem.cs b/Content.Shared/SubFloor/TrayScannerSystem.cs deleted file mode 100644 index c9eecb3ea2..0000000000 --- a/Content.Shared/SubFloor/TrayScannerSystem.cs +++ /dev/null @@ -1,253 +0,0 @@ -using Content.Shared.Interaction; -using Robust.Shared.Containers; -using Robust.Shared.GameStates; -using Robust.Shared.Map; -using Robust.Shared.Serialization; -using Robust.Shared.Timing; -using Robust.Shared.Utility; -using System.Linq; - -namespace Content.Shared.SubFloor; - -public sealed class TrayScannerSystem : EntitySystem -{ - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedSubFloorHideSystem _subfloorSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - private HashSet _activeScanners = new(); - private RemQueue _invalidScanners = new(); - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentShutdown); - SubscribeLocalEvent(OnTrayScannerGetState); - SubscribeLocalEvent(OnTrayScannerHandleState); - SubscribeLocalEvent(OnTrayScannerActivate); - } - - private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args) - { - SetScannerEnabled(uid, !scanner.Enabled, scanner); - } - - private void SetScannerEnabled(EntityUid uid, bool enabled, TrayScannerComponent? scanner = null) - { - if (!Resolve(uid, ref scanner)) - return; - - scanner.Enabled = enabled; - Dirty(scanner); - - if (scanner.Enabled) - _activeScanners.Add(uid); - - // We don't remove from _activeScanners on disabled, because the update function will handle that, as well as - // managing the revealed subfloor entities - - if (EntityManager.TryGetComponent(uid, out var appearance)) - { - _appearance.SetData(uid, TrayScannerVisual.Visual, scanner.Enabled ? TrayScannerVisual.On : TrayScannerVisual.Off, appearance); - } - } - - private void OnTrayScannerGetState(EntityUid uid, TrayScannerComponent scanner, ref ComponentGetState args) - { - args.State = new TrayScannerState(scanner.Enabled); - } - - private void OnTrayScannerHandleState(EntityUid uid, TrayScannerComponent scanner, ref ComponentHandleState args) - { - if (args.Current is not TrayScannerState state) - return; - - SetScannerEnabled(uid, state.Enabled, scanner); - - // This is hacky and somewhat inefficient for the client. But when resetting predicted entities we have to unset - // last position. This is because appearance data gets reset, but if the position isn't reset the scanner won't - // re-reveal entities leading to odd visuals. - scanner.LastLocation = null; - } - - public void OnComponentShutdown(EntityUid uid, TrayScannerComponent scanner, ComponentShutdown args) - { - _subfloorSystem.SetEntitiesRevealed(scanner.RevealedSubfloors, uid, false); - _activeScanners.Remove(uid); - } - - public override void Update(float frameTime) - { - if (!_gameTiming.IsFirstTimePredicted) - return; - - if (!_activeScanners.Any()) - return; - - foreach (var scanner in _activeScanners) - { - if (_invalidScanners.List != null - && _invalidScanners.List.Contains(scanner)) - continue; - - if (!UpdateTrayScanner(scanner)) - _invalidScanners.Add(scanner); - } - - foreach (var invalidScanner in _invalidScanners) - { - _activeScanners.Remove(invalidScanner); - } - - _invalidScanners.List?.Clear(); - } - - /// - /// When a subfloor entity gets anchored (which includes spawning & coming into PVS range), Check for nearby scanners. - /// - public void OnSubfloorAnchored(EntityUid uid, SubFloorHideComponent? hideComp = null, TransformComponent? xform = null) - { - if (!Resolve(uid, ref hideComp, ref xform)) - return; - - var pos = xform.MapPosition; - - foreach (var entity in _activeScanners) - { - if (!TryComp(entity, out TrayScannerComponent? scanner)) - continue; - - if (!Transform(entity).MapPosition.InRange(pos, scanner.Range)) - continue; - - hideComp.RevealedBy.Add(entity); - scanner.RevealedSubfloors.Add(uid); - } - } - - /// - /// Updates a T-Ray scanner. Should be called on immediate - /// state change (turned on/off), or during the update - /// loop. - /// - /// true if the update was successful, false otherwise - private bool UpdateTrayScanner(EntityUid uid, TrayScannerComponent? scanner = null, TransformComponent? transform = null) - { - if (!Resolve(uid, ref scanner, ref transform)) - return false; - - // if the scanner was toggled off recently, - // set all the known subfloor to invisible, - // and return false so it's removed from - // the active scanner list - if (!scanner.Enabled || transform.MapID == MapId.Nullspace) - { - _subfloorSystem.SetEntitiesRevealed(scanner.RevealedSubfloors, uid, false); - scanner.LastLocation = null; - scanner.RevealedSubfloors.Clear(); - return false; - } - - var pos = transform.LocalPosition; - var parent = _transform.GetParent(transform); - - // zero vector implies container - // - // this means we should get the entity transform's parent - if (pos == Vector2.Zero - && parent != null - && _containerSystem.ContainsEntity(transform.ParentUid, uid)) - { - pos = parent.LocalPosition; - - // if this is also zero, we can check one more time - // - // could recurse through fully but i think that's useless, - // just attempt to check through the gp's transform and if - // that doesn't work, just don't bother any further - if (pos == Vector2.Zero) - { - var gpTransform = _transform.GetParent(parent); - if (gpTransform != null - && _containerSystem.ContainsEntity(gpTransform.Owner, transform.ParentUid)) - { - pos = gpTransform.LocalPosition; - } - } - } - - // is the position still logically zero? just clear, - // but we need to keep it as 'true' since this t-ray - // is still technically on - if (pos == Vector2.Zero) - { - _subfloorSystem.SetEntitiesRevealed(scanner.RevealedSubfloors, uid, false); - scanner.RevealedSubfloors.Clear(); - return true; - } - - // get the rounded position so that small movements don't cause this to - // update every time - var flooredPos = (Vector2i) pos; - - // MAYBE redo this. Currently different players can see different entities - // - // Here we avoid the entity lookup & return early if the scanner's position hasn't appreciably changed. However, - // if a new player enters PVS-range, they will update the in-range entities on their end and use that to set - // LastLocation. This means that different players can technically see different entities being revealed by the - // same scanner. The correct fix for this is probably just to network the revealed entity set.... But I CBF - // doing that right now.... - if (flooredPos == scanner.LastLocation - || float.IsNaN(flooredPos.X) && float.IsNaN(flooredPos.Y)) - return true; - - scanner.LastLocation = flooredPos; - - // Update entities in Range - HashSet nearby = new(); - var coords = transform.MapPosition; - var worldBox = Box2.CenteredAround(coords.Position, (scanner.Range * 2, scanner.Range * 2)); - - // For now, limiting to the scanner's own grid. We could do a grid-lookup, but then what do we do if one grid - // flies away, while the scanner's local-position remains unchanged? - if (_mapManager.TryGetGrid(transform.GridUid, out var grid)) - { - foreach (var entity in grid.GetAnchoredEntities(worldBox)) - { - if (!Transform(entity).MapPosition.InRange(coords, scanner.Range)) - continue; - - if (!TryComp(entity, out SubFloorHideComponent? hideComp)) - continue; // Not a hide-able entity. - - nearby.Add(entity); - - if (scanner.RevealedSubfloors.Add(entity)) - _subfloorSystem.SetEntityRevealed(entity, uid, true, hideComp); - } - } - - // get all the old elements that are no longer detected - HashSet missing = new(scanner.RevealedSubfloors.Except(nearby)); - - // remove those from the list - scanner.RevealedSubfloors.ExceptWith(missing); - - // and hide them - _subfloorSystem.SetEntitiesRevealed(missing, uid, false); - - return true; - } -} - -[Serializable, NetSerializable] -public enum TrayScannerVisual : sbyte -{ - Visual, - On, - Off -}