diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 1fc00a81b9..2957dafdb7 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -4,6 +4,7 @@ using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; using Robust.Shared.Graphics; +using Robust.Client.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -14,6 +15,7 @@ public sealed class DoAfterOverlay : Overlay { private readonly IEntityManager _entManager; private readonly IGameTiming _timing; + private readonly IPlayerManager _player; private readonly SharedTransformSystem _transform; private readonly MetaDataSystem _meta; @@ -31,13 +33,14 @@ public sealed class DoAfterOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; - public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, IGameTiming timing) + public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, IGameTiming timing, IPlayerManager player) { _entManager = entManager; _timing = timing; + _player = player; _transform = _entManager.EntitySysManager.GetEntitySystem(); _meta = _entManager.EntitySysManager.GetEntitySystem(); - var sprite = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/progress_bar.rsi"), "icon"); + var sprite = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/progress_bar.rsi"), "icon"); _barTexture = _entManager.EntitySysManager.GetEntitySystem().Frame0(sprite); _shader = protoManager.Index("unshaded").Instance(); @@ -58,6 +61,7 @@ public sealed class DoAfterOverlay : Overlay var curTime = _timing.CurTime; var bounds = args.WorldAABB.Enlarged(5f); + var localEnt = _player.LocalSession?.AttachedEntity; var metaQuery = _entManager.GetEntityQuery(); var enumerator = _entManager.AllEntityQueryEnumerator(); @@ -88,6 +92,17 @@ public sealed class DoAfterOverlay : Overlay foreach (var doAfter in comp.DoAfters.Values) { + // Hide some DoAfters from other players for stealthy actions (ie: thieving gloves) + var alpha = 1f; + if (doAfter.Args.Hidden) + { + if (uid != localEnt) + continue; + + // Hints to the local player that this do-after is not visible to other players. + alpha = 0.5f; + } + // Use the sprite itself if we know its bounds. This means short or tall sprites don't get overlapped // by the bar. float yOffset = sprite.Bounds.Height / 2f + 0.05f; @@ -108,15 +123,15 @@ public sealed class DoAfterOverlay : Overlay { var elapsed = doAfter.CancelledTime.Value - doAfter.StartTime; elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds); - var cancelElapsed = (time - doAfter.CancelledTime.Value).TotalSeconds; + var cancelElapsed = (time - doAfter.CancelledTime.Value).TotalSeconds; var flash = Math.Floor(cancelElapsed / FlashTime) % 2 == 0; - color = new Color(1f, 0f, 0f, flash ? 1f : 0f); + color = new Color(1f, 0f, 0f, flash ? alpha : 0f); } else { var elapsed = time - doAfter.StartTime; elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds); - color = GetProgressColor(elapsedRatio); + color = GetProgressColor(elapsedRatio, alpha); } var xProgress = (EndX - StartX) * elapsedRatio + StartX; @@ -131,14 +146,14 @@ public sealed class DoAfterOverlay : Overlay handle.SetTransform(Matrix3.Identity); } - public static Color GetProgressColor(float progress) + public static Color GetProgressColor(float progress, float alpha = 1f) { if (progress >= 1.0f) { - return new Color(0f, 1f, 0f); + return new Color(0f, 1f, 0f, alpha); } // lerp var hue = (5f / 18f) * progress; - return Color.FromHsv((hue, 1f, 0.75f, 1f)); + return Color.FromHsv((hue, 1f, 0.75f, alpha)); } } diff --git a/Content.Client/DoAfter/DoAfterSystem.cs b/Content.Client/DoAfter/DoAfterSystem.cs index 8c2d8344d3..38e1a714ac 100644 --- a/Content.Client/DoAfter/DoAfterSystem.cs +++ b/Content.Client/DoAfter/DoAfterSystem.cs @@ -21,7 +21,7 @@ public sealed class DoAfterSystem : SharedDoAfterSystem public override void Initialize() { base.Initialize(); - _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype, GameTiming)); + _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype, GameTiming, _player)); } public override void Shutdown() diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 75374e7de5..b354f9071a 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -221,6 +221,7 @@ namespace Content.Server.Strip var doAfterArgs = new DoAfterArgs(EntityManager, user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: held) { ExtraCheck = Check, + Hidden = ev.Stealth, AttemptFrequency = AttemptFrequency.EveryTick, BreakOnDamage = true, BreakOnTargetMove = true, @@ -294,6 +295,7 @@ namespace Content.Server.Strip var doAfterArgs = new DoAfterArgs(EntityManager, user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: held) { ExtraCheck = Check, + Hidden = ev.Stealth, AttemptFrequency = AttemptFrequency.EveryTick, BreakOnDamage = true, BreakOnTargetMove = true, @@ -308,7 +310,7 @@ namespace Content.Server.Strip if (result != DoAfterStatus.Finished) return; _handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: userHands); - _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: true, handsComp: hands); + _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: !ev.Stealth, animate: !ev.Stealth, handsComp: hands); _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands"); // hand update will trigger strippable update } @@ -354,6 +356,7 @@ namespace Content.Server.Strip var doAfterArgs = new DoAfterArgs(EntityManager, user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: item) { ExtraCheck = Check, + Hidden = ev.Stealth, AttemptFrequency = AttemptFrequency.EveryTick, BreakOnDamage = true, BreakOnTargetMove = true, @@ -389,7 +392,7 @@ namespace Content.Server.Strip // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping RaiseLocalEvent(item, new DroppedEvent(user), true); - _handsSystem.PickupOrDrop(user, item); + _handsSystem.PickupOrDrop(user, item, animateUser: !ev.Stealth, animate: !ev.Stealth); _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}"); } @@ -430,6 +433,7 @@ namespace Content.Server.Strip var doAfterArgs = new DoAfterArgs(EntityManager, user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: item) { ExtraCheck = Check, + Hidden = ev.Stealth, AttemptFrequency = AttemptFrequency.EveryTick, BreakOnDamage = true, BreakOnTargetMove = true, @@ -439,7 +443,7 @@ namespace Content.Server.Strip DuplicateCondition = DuplicateConditions.SameTool }; - if (Check() && _handsSystem.TryGetHand(target, handName, out var handSlot, hands) && handSlot.HeldEntity != null) + if (!ev.Stealth && Check() && _handsSystem.TryGetHand(target, handName, out var handSlot, hands) && handSlot.HeldEntity != null) { _popup.PopupEntity( Loc.GetString("strippable-component-alert-owner", @@ -456,7 +460,7 @@ namespace Content.Server.Strip return; _handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: hands); - _handsSystem.PickupOrDrop(user, item, handsComp: userHands); + _handsSystem.PickupOrDrop(user, item, animateUser: !ev.Stealth, animate: !ev.Stealth, handsComp: userHands); // hand update will trigger strippable update _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}"); diff --git a/Content.Shared/DoAfter/DoAfterArgs.cs b/Content.Shared/DoAfter/DoAfterArgs.cs index c5cbc1f302..d2729ad3c6 100644 --- a/Content.Shared/DoAfter/DoAfterArgs.cs +++ b/Content.Shared/DoAfter/DoAfterArgs.cs @@ -40,6 +40,12 @@ public sealed partial class DoAfterArgs public NetEntity? NetUsed; + /// + /// Whether the progress bar for this DoAfter should be hidden from other players. + /// + [DataField] + public bool Hidden; + #region Event options /// /// The event that will get raised when the DoAfter has finished. If null, this will simply raise a @@ -239,6 +245,7 @@ public sealed partial class DoAfterArgs Delay = other.Delay; Target = other.Target; Used = other.Used; + Hidden = other.Hidden; EventTarget = other.EventTarget; Broadcast = other.Broadcast; NeedHand = other.NeedHand;