From 386c7f9223d44bcd76d978dba034c65bde5d5b0d Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Sun, 9 Oct 2022 18:17:53 -0400 Subject: [PATCH] Cardboard Box and Stealth Components (#11569) --- .../CardboardBox/CardboardBoxSystem.cs | 60 +++++++++ Content.Client/Stealth/StealthSystem.cs | 74 +++++++++++ .../CardboardBox/CardboardBoxSystem.cs | 61 +++++++++ Content.Server/Stealth/StealthSystem.cs | 8 ++ .../Components/EntityStorageComponent.cs | 9 ++ .../Storage/EntitySystems/StorageSystem.cs | 5 +- .../Components/SharedCardboardBoxComponent.cs | 68 ++++++++++ .../CardboardBox/SharedCardboardBoxSystem.cs | 6 + .../Stealth/Components/StealthComponent.cs | 60 +++++++++ Content.Shared/Stealth/SharedStealthSystem.cs | 122 ++++++++++++++++++ Resources/Audio/Effects/box_deploy.ogg | Bin 0 -> 16791 bytes Resources/Audio/Effects/chime.ogg | Bin 0 -> 9549 bytes Resources/Audio/Effects/licenses.txt | 14 +- .../Structures/Storage/Closets/big_boxes.yml | 115 +++++++++++++++++ Resources/Prototypes/Shaders/shaders.yml | 6 + Resources/Textures/Shaders/stealth.swsl | 37 ++++++ 16 files changed, 643 insertions(+), 2 deletions(-) create mode 100644 Content.Client/CardboardBox/CardboardBoxSystem.cs create mode 100644 Content.Client/Stealth/StealthSystem.cs create mode 100644 Content.Server/CardboardBox/CardboardBoxSystem.cs create mode 100644 Content.Server/Stealth/StealthSystem.cs create mode 100644 Content.Shared/CardboardBox/Components/SharedCardboardBoxComponent.cs create mode 100644 Content.Shared/CardboardBox/SharedCardboardBoxSystem.cs create mode 100644 Content.Shared/Stealth/Components/StealthComponent.cs create mode 100644 Content.Shared/Stealth/SharedStealthSystem.cs create mode 100644 Resources/Audio/Effects/box_deploy.ogg create mode 100644 Resources/Audio/Effects/chime.ogg create mode 100644 Resources/Prototypes/Entities/Structures/Storage/Closets/big_boxes.yml create mode 100644 Resources/Textures/Shaders/stealth.swsl diff --git a/Content.Client/CardboardBox/CardboardBoxSystem.cs b/Content.Client/CardboardBox/CardboardBoxSystem.cs new file mode 100644 index 0000000000..1b67c8be75 --- /dev/null +++ b/Content.Client/CardboardBox/CardboardBoxSystem.cs @@ -0,0 +1,60 @@ +using Content.Shared.CardboardBox; +using Content.Shared.CardboardBox.Components; +using Content.Shared.Examine; +using Content.Shared.Movement.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.CardboardBox; + +public sealed class CardboardBoxSystem : SharedCardboardBoxSystem +{ + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnBoxEffect); + } + + private void OnBoxEffect(PlayBoxEffectMessage msg) + { + if (!TryComp(msg.Source, out var box)) + return; + + var xformQuery = GetEntityQuery(); + + if (!xformQuery.TryGetComponent(msg.Source, out var xform)) + return; + + var sourcePos = xform.MapPosition; + + //Any mob that can move should be surprised? + //God mind rework needs to come faster so it can just check for mind + //TODO: Replace with Mind Query when mind rework is in. + var mobMoverEntities = new HashSet(); + + //Filter out entities in range to see that they're a mob and add them to the mobMoverEntities hash for faster lookup + foreach (var moverComp in _entityLookup.GetComponentsInRange(xform.Coordinates, box.Distance)) + { + if (moverComp.Owner == msg.Mover) + continue; + + mobMoverEntities.Add(moverComp.Owner); + } + + //Play the effect for the mobs as long as they can see the box and are in range. + foreach (var mob in mobMoverEntities) + { + if (!xformQuery.TryGetComponent(mob, out var moverTransform) || !ExamineSystemShared.InRangeUnOccluded(sourcePos, moverTransform.MapPosition, box.Distance, null)) + continue; + + var ent = Spawn(box.Effect, moverTransform.MapPosition); + + if (!xformQuery.TryGetComponent(ent, out var entTransform) || !TryComp(ent, out var sprite)) + continue; + + sprite.Offset = new Vector2(0, 1); + entTransform.AttachParent(mob); + } + } +} diff --git a/Content.Client/Stealth/StealthSystem.cs b/Content.Client/Stealth/StealthSystem.cs new file mode 100644 index 0000000000..0e116f07a0 --- /dev/null +++ b/Content.Client/Stealth/StealthSystem.cs @@ -0,0 +1,74 @@ +using Content.Client.Interactable.Components; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +namespace Content.Client.Stealth; + +public sealed class StealthSystem : SharedStealthSystem +{ + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + private ShaderInstance _shader = default!; + + public override void Initialize() + { + base.Initialize(); + + _shader = _protoMan.Index("Stealth").InstanceUnique(); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnShaderRender); + } + + protected override void OnInit(EntityUid uid, StealthComponent component, ComponentInit args) + { + base.OnInit(uid, component, args); + if (!TryComp(uid, out SpriteComponent? sprite)) + return; + + sprite.PostShader = _shader; + sprite.GetScreenTexture = true; + sprite.RaiseShaderEvent = true; + + if (TryComp(uid, out InteractionOutlineComponent? outline)) + { + RemComp(uid, outline); + component.HadOutline = true; + } + } + + private void OnRemove(EntityUid uid, StealthComponent component, ComponentRemove args) + { + if (!TryComp(uid, out SpriteComponent? sprite)) + return; + + sprite.PostShader = null; + sprite.GetScreenTexture = false; + sprite.RaiseShaderEvent = false; + sprite.Color = Color.White; + + if (component.HadOutline) + AddComp(uid); + } + + private void OnShaderRender(EntityUid uid, StealthComponent component, BeforePostShaderRenderEvent args) + { + // Distortion effect uses screen coordinates. If a player moves, the entities appear to move on screen. this + // makes the distortion very noticeable. + + // So we need to use relative screen coordinates. The reference frame we use is the parent's position on screen. + // this ensures that if the Stealth is not moving relative to the parent, its relative screen position remains + // unchanged. + var parentXform = Transform(Transform(uid).ParentUid); + var reference = args.Viewport.WorldToLocal(parentXform.WorldPosition); + var visibility = GetVisibility(uid, component); + _shader.SetParameter("reference", reference); + _shader.SetParameter("visibility", visibility); + + visibility = MathF.Max(0, visibility); + args.Sprite.Color = new Color(visibility, visibility, 1, 1); + } +} + diff --git a/Content.Server/CardboardBox/CardboardBoxSystem.cs b/Content.Server/CardboardBox/CardboardBoxSystem.cs new file mode 100644 index 0000000000..6b30bf0013 --- /dev/null +++ b/Content.Server/CardboardBox/CardboardBoxSystem.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Content.Shared.CardboardBox.Components; +using Content.Server.Storage.Components; +using Content.Shared.CardboardBox; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Systems; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Server.CardboardBox; + +public sealed class CardboardBoxSystem : SharedCardboardBoxSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBeforeStorageClosed); + SubscribeLocalEvent(AfterStorageOpen); + } + + private void OnBeforeStorageClosed(EntityUid uid, CardboardBoxComponent component, StorageBeforeCloseEvent args) + { + var mobMover = args.Contents.Where(HasComp).ToList(); + + //Grab the first mob to set as the mover and to prevent other mobs from entering. + foreach (var mover in mobMover) + { + //Set the movement relay for the box as the first mob + if (component.Mover == null && args.Contents.Contains(mover)) + { + var relay = EnsureComp(mover); + _mover.SetRelay(mover, uid, relay); + component.Mover = mover; + } + + if (mover != component.Mover) + args.Contents.Remove(mover); + } + } + + private void AfterStorageOpen(EntityUid uid, CardboardBoxComponent component, StorageAfterOpenEvent args) + { + //Remove the mover after the box is opened and play the effect if it hasn't been played yet. + if (component.Mover != null) + { + RemComp(component.Mover.Value); + if (_timing.CurTime > component.EffectCooldown) + { + RaiseNetworkEvent(new PlayBoxEffectMessage(component.Owner, component.Mover.Value), Filter.PvsExcept(component.Owner)); + _audio.PlayPvs(component.EffectSound, component.Owner); + component.EffectCooldown = _timing.CurTime + CardboardBoxComponent.MaxEffectCooldown; + } + } + + component.Mover = null; + } +} diff --git a/Content.Server/Stealth/StealthSystem.cs b/Content.Server/Stealth/StealthSystem.cs new file mode 100644 index 0000000000..b64eb4b9af --- /dev/null +++ b/Content.Server/Stealth/StealthSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Stealth; + +namespace Content.Server.Stealth; + +public sealed class StealthSystem : SharedStealthSystem +{ + +} diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index 66f98b7838..a15911e584 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -37,6 +37,15 @@ public sealed class EntityStorageComponent : Component, IGasMixtureHolder [DataField("isCollidableWhenOpen")] public bool IsCollidableWhenOpen; + /// + /// If true, it opens the storage when the entity inside of it moves + /// If false, it prevents the storage from opening when the entity inside of it moves. + /// This is for objects that you want the player to move while inside, like large cardboard boxes, without opening the storage. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("openOnMove")] + public bool OpenOnMove = true; + //The offset for where items are emptied/vacuumed for the EntityStorage. [DataField("enteringOffset")] public Vector2 EnteringOffset = new(0, 0); diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs index 9b135f177a..64a29aaf29 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -149,7 +149,10 @@ namespace Content.Server.Storage.EntitySystems return; component.LastInternalOpenAttempt = _gameTiming.CurTime; - _entityStorage.TryOpenStorage(args.Entity, component.Owner); + if (component.OpenOnMove) + { + _entityStorage.TryOpenStorage(args.Entity, component.Owner); + } } diff --git a/Content.Shared/CardboardBox/Components/SharedCardboardBoxComponent.cs b/Content.Shared/CardboardBox/Components/SharedCardboardBoxComponent.cs new file mode 100644 index 0000000000..7fdccaaa9e --- /dev/null +++ b/Content.Shared/CardboardBox/Components/SharedCardboardBoxComponent.cs @@ -0,0 +1,68 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.CardboardBox.Components; +/// +/// Allows a user to control an EntityStorage entity while inside of it. +/// Used for big cardboard box entities. +/// +[RegisterComponent, NetworkedComponent] +public sealed class CardboardBoxComponent : Component +{ + /// + /// The person in control of this box + /// + [ViewVariables] + [DataField("mover")] + public EntityUid? Mover; + + /// + /// The entity used for the box opening effect + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("effect")] + public string Effect = "Exclamation"; + + /// + /// Sound played upon effect creation + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("effectSound")] + public SoundSpecifier? EffectSound; + + /// + /// How far should the box opening effect go? + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("distance")] + public float Distance = 6f; + + /// + /// Current time + max effect cooldown to check to see if effect can play again + /// Prevents effect spam + /// + [DataField("effectCooldown", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan EffectCooldown = TimeSpan.FromSeconds(1f); + + /// + /// How much time should pass + current time until the effect plays again + /// Prevents effect spam + /// + [DataField("maxEffectCooldown", customTypeSerializer: typeof(TimeOffsetSerializer))] + public static readonly TimeSpan MaxEffectCooldown = TimeSpan.FromSeconds(5f); +} + +[Serializable, NetSerializable] +public sealed class PlayBoxEffectMessage : EntityEventArgs +{ + public EntityUid Source; + public EntityUid Mover; + + public PlayBoxEffectMessage(EntityUid source, EntityUid mover) + { + Source = source; + Mover = mover; + } +} diff --git a/Content.Shared/CardboardBox/SharedCardboardBoxSystem.cs b/Content.Shared/CardboardBox/SharedCardboardBoxSystem.cs new file mode 100644 index 0000000000..856bb94ae0 --- /dev/null +++ b/Content.Shared/CardboardBox/SharedCardboardBoxSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.CardboardBox; + +public abstract class SharedCardboardBoxSystem : EntitySystem +{ + +} diff --git a/Content.Shared/Stealth/Components/StealthComponent.cs b/Content.Shared/Stealth/Components/StealthComponent.cs new file mode 100644 index 0000000000..b41f05c9ae --- /dev/null +++ b/Content.Shared/Stealth/Components/StealthComponent.cs @@ -0,0 +1,60 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Stealth.Components; +/// +/// Add this component to an entity that you want to be cloaked. +/// It overlays a shader on the entity to give them an invisibility cloaked effect +/// It also turns the entity invisible +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedStealthSystem))] +public sealed class StealthComponent : Component +{ + /// + /// Whether or not the entity previously had an interaction outline prior to cloaking. + /// + [DataField("hadOutline")] + public bool HadOutline; + + /// + /// Last set level of visibility. Ranges from 1 (fully visible) and -1 (fully hidden). To get the actual current + /// visibility, use + /// + [DataField("lastVisibility")] + [Access(typeof(SharedStealthSystem), Other = AccessPermissions.None)] + public float LastVisibility; + + /// + /// Time at which was set. Null implies the entity is currently paused and not + /// accumulating any visibility change. + /// + [DataField("lastUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))] + public TimeSpan? LastUpdated; + + /// + /// Rate that effects how fast an entity's visibility passively changes. + /// + [DataField("passiveVisibilityRate")] + public readonly float PassiveVisibilityRate = -0.15f; + + /// + /// Rate for movement induced visibility changes. Scales with distance moved. + /// + [DataField("movementVisibilityRate")] + public readonly float MovementVisibilityRate = 0.2f; +} + +[Serializable, NetSerializable] +public sealed class StealthComponentState : ComponentState +{ + public float Visibility; + public TimeSpan? LastUpdated; + + public StealthComponentState(float stealthLevel, TimeSpan? lastUpdated) + { + Visibility = stealthLevel; + LastUpdated = lastUpdated; + } +} diff --git a/Content.Shared/Stealth/SharedStealthSystem.cs b/Content.Shared/Stealth/SharedStealthSystem.cs new file mode 100644 index 0000000000..582bd7b57d --- /dev/null +++ b/Content.Shared/Stealth/SharedStealthSystem.cs @@ -0,0 +1,122 @@ +using Content.Shared.Stealth.Components; +using Robust.Shared.GameStates; +using Robust.Shared.Timing; + +namespace Content.Shared.Stealth; + +public abstract class SharedStealthSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStealthGetState); + SubscribeLocalEvent(OnStealthHandleState); + SubscribeLocalEvent(OnMove); + SubscribeLocalEvent(OnPaused); + SubscribeLocalEvent(OnInit); + } + + private void OnPaused(EntityUid uid, StealthComponent component, EntityPausedEvent args) + { + if (args.Paused) + { + component.LastVisibility = GetVisibility(uid, component); + component.LastUpdated = null; + } + else + { + component.LastUpdated = _timing.CurTime; + } + + Dirty(component); + } + + protected virtual void OnInit(EntityUid uid, StealthComponent component, ComponentInit args) + { + if (component.LastUpdated != null || Paused(uid)) + return; + + component.LastUpdated = _timing.CurTime; + } + + private void OnStealthGetState(EntityUid uid, StealthComponent component, ref ComponentGetState args) + { + args.State = new StealthComponentState(component.LastVisibility, component.LastUpdated); + } + + private void OnStealthHandleState(EntityUid uid, StealthComponent component, ref ComponentHandleState args) + { + if (args.Current is not StealthComponentState cast) + return; + + component.LastVisibility = cast.Visibility; + component.LastUpdated = cast.LastUpdated; + } + + private void OnMove(EntityUid uid, StealthComponent component, ref MoveEvent args) + { + if (args.FromStateHandling) + return; + + if (args.NewPosition.EntityId != args.OldPosition.EntityId) + return; + + var delta = component.MovementVisibilityRate * (args.NewPosition.Position - args.OldPosition.Position).Length; + ModifyVisibility(uid, delta, component); + } + + /// + /// Modifies the visibility based on the delta provided. + /// + /// The delta to be used in visibility calculation. + public void ModifyVisibility(EntityUid uid, float delta, StealthComponent? component = null) + { + if (delta == 0 || !Resolve(uid, ref component)) + return; + + if (component.LastUpdated != null) + { + component.LastVisibility = GetVisibility(uid, component); + component.LastUpdated = _timing.CurTime; + } + + component.LastVisibility = Math.Clamp(component.LastVisibility + delta, -1f, 1f); + Dirty(component); + } + + /// + /// Sets the visibility directly with no modifications + /// + /// The value to set the visibility to. -1 is fully invisible, 1 is fully visible + public void SetVisibility(EntityUid uid, float value, StealthComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.LastVisibility = value; + if (component.LastUpdated != null) + component.LastUpdated = _timing.CurTime; + + Dirty(component); + } + + /// + /// Gets the current visibility from the + /// Use this instead of getting LastVisibility from the component directly. + /// + /// Returns a calculation that accounts for any stealth change that happened since last update, otherwise returns based on if it can resolve the component. + public float GetVisibility(EntityUid uid, StealthComponent? component = null) + { + if (!Resolve(uid, ref component)) + return 1; + + if (component.LastUpdated == null) + return component.LastVisibility; + + var deltaTime = _timing.CurTime - component.LastUpdated.Value; + return Math.Clamp(component.LastVisibility + (float) deltaTime.TotalSeconds * component.PassiveVisibilityRate, -1f, 1f); + } +} diff --git a/Resources/Audio/Effects/box_deploy.ogg b/Resources/Audio/Effects/box_deploy.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3fbe895950e1eaa65193efcb199a6b7b8a0e1da7 GIT binary patch literal 16791 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 E13TZhyZ`_I literal 0 HcmV?d00001 diff --git a/Resources/Audio/Effects/chime.ogg b/Resources/Audio/Effects/chime.ogg new file mode 100644 index 0000000000000000000000000000000000000000..20548c73cda0409d289c92c62f67c33e35472237 GIT binary patch literal 9549 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 + alpha = sprite.a * visibility; + else + alpha = 0; + + COLOR.xyz = mix(background.xyz, sprite.xyz, alpha); + COLOR.a = 1.0; +}