From 88f49961d8296056a403ae40e51cbf5b4fd2ad2f Mon Sep 17 00:00:00 2001 From: "R. Neuser" Date: Mon, 6 Jul 2020 16:37:39 -0500 Subject: [PATCH] OverlayManager refactor and Flash (#1218) * Flash component, overlay and shader Add BeginDraw method to Overlay.cs * Add flash icons, sounds * Progress * Multiple overlays without enums * This is probably the worst way to do this IDK * Remove nullable reference type * Add AttackEventArgs as parameter to OnHitEntities MeleeWeaponComponent.Attack now continues when OnHitEntities returns true (it hit something) Add OverlayType enum so client and server can agree on overlay ids Move IConfigurable to its own file Add AoE flash with shorter duration Flashing someone slows them down * Add arc to flash Set item size to something reasonable Remove chat log message when flash burns out * Remove unused interface --- .../Mobs/ClientOverlayEffectsComponent.cs | 139 +++++++------ .../Graphics/Overlays/CircleMaskOverlay.cs | 5 +- .../Graphics/Overlays/FlashOverlay.cs | 73 +++++++ .../Graphics/Overlays/GradientCircleMask.cs | 7 +- .../DamageThresholdTemplates/HumanTemplate.cs | 9 +- .../Mobs/ServerOverlayEffectsComponent.cs | 46 ++++- .../Components/Mobs/SpeciesComponent.cs | 2 +- .../Components/Weapon/Melee/FlashComponent.cs | 182 ++++++++++++++++++ .../Weapon/Melee/MeleeWeaponComponent.cs | 6 +- .../Weapon/Melee/StunbatonComponent.cs | 4 +- Content.Server/Mobs/Commands.cs | 49 +++++ .../Mobs/SharedOverlayEffectsComponent.cs | 73 ++++++- Content.Shared/Interfaces/IConfigurable.cs | 7 + Resources/Audio/weapons/flash.ogg | Bin 0 -> 9891 bytes Resources/Groups/groups.yml | 4 + .../Entities/Items/Weapons/security.yml | 27 +++ Resources/Prototypes/Shaders/shaders.yml | 5 + Resources/Shaders/flashed_effect.swsl | 18 ++ .../Objects/Melee/flash.rsi/burnt.png | Bin 0 -> 354 bytes .../Objects/Melee/flash.rsi/flash.png | Bin 0 -> 368 bytes .../Objects/Melee/flash.rsi/flashing.png | Bin 0 -> 385 bytes .../Objects/Melee/flash.rsi/inhand-left.png | Bin 0 -> 280 bytes .../Objects/Melee/flash.rsi/inhand-right.png | Bin 0 -> 296 bytes .../Objects/Melee/flash.rsi/meta.json | 76 ++++++++ 24 files changed, 647 insertions(+), 85 deletions(-) create mode 100644 Content.Client/Graphics/Overlays/FlashOverlay.cs create mode 100644 Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs create mode 100644 Content.Shared/Interfaces/IConfigurable.cs create mode 100644 Resources/Audio/weapons/flash.ogg create mode 100644 Resources/Shaders/flashed_effect.swsl create mode 100644 Resources/Textures/Objects/Melee/flash.rsi/burnt.png create mode 100644 Resources/Textures/Objects/Melee/flash.rsi/flash.png create mode 100644 Resources/Textures/Objects/Melee/flash.rsi/flashing.png create mode 100644 Resources/Textures/Objects/Melee/flash.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Melee/flash.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Melee/flash.rsi/meta.json diff --git a/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs index 5b5c24d579..e1a1cb8b38 100644 --- a/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs @@ -1,18 +1,21 @@ -using System.Collections.Generic; -using Content.Client.Graphics.Overlays; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.Interfaces; using Robust.Client.GameObjects; using Robust.Client.Graphics.Overlays; using Robust.Client.Interfaces.Graphics.Overlays; -using Robust.Client.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Players; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; -namespace Content.Client.GameObjects +namespace Content.Client.GameObjects.Components.Mobs { /// /// A character UI component which shows the current damage state of the mob (living/dead) @@ -21,48 +24,33 @@ namespace Content.Client.GameObjects [ComponentReference(typeof(SharedOverlayEffectsComponent))] public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI { - /// /// An enum representing the current state being applied to the user /// - private ScreenEffects _currentEffect = ScreenEffects.None; + private readonly List _currentEffects = new List(); + + [ViewVariables(VVAccess.ReadOnly)] + public List ActiveOverlays + { + get => _currentEffects; + set => SetEffects(value); + } #pragma warning disable 649 // Required dependencies [Dependency] private readonly IOverlayManager _overlayManager; - [Dependency] private readonly IPlayerManager _playerManager; + [Dependency] private readonly IReflectionManager _reflectionManager; #pragma warning restore 649 - /// - /// Holds the screen effects that can be applied mapped ot their relevant overlay - /// - private Dictionary _effectsDictionary; - - /// - /// Allows calculating if we need to act due to this component being controlled by the current mob - /// - private bool CurrentlyControlled => _playerManager.LocalPlayer.ControlledEntity == Owner; - - public override void OnAdd() - { - base.OnAdd(); - - _effectsDictionary = new Dictionary() - { - { ScreenEffects.CircleMask, new CircleMaskOverlay() }, - { ScreenEffects.GradientCircleMask, new GradientCircleMask() } - }; - } - public override void HandleMessage(ComponentMessage message, IComponent component) { switch (message) { case PlayerAttachedMsg _: - SetOverlay(_currentEffect); + SetEffects(ActiveOverlays); break; case PlayerDetachedMsg _: - RemoveOverlay(); + ActiveOverlays = new List(); break; } } @@ -70,42 +58,77 @@ namespace Content.Client.GameObjects public override void HandleComponentState(ComponentState curState, ComponentState nextState) { base.HandleComponentState(curState, nextState); - if (!(curState is OverlayEffectComponentState state) || _currentEffect == state.ScreenEffect) return; - SetOverlay(state.ScreenEffect); - } - - private void SetOverlay(ScreenEffects effect) - { - RemoveOverlay(); - - _currentEffect = effect; - - ApplyOverlay(); - } - - private void RemoveOverlay() - { - if (CurrentlyControlled && _currentEffect != ScreenEffects.None) + if (!(curState is OverlayEffectComponentState state) || ActiveOverlays.Equals(state.Overlays)) { - var appliedEffect = _effectsDictionary[_currentEffect]; - _overlayManager.RemoveOverlay(appliedEffect.ID); + return; } - _currentEffect = ScreenEffects.None; + ActiveOverlays = state.Overlays; } - private void ApplyOverlay() + private void SetEffects(List newOverlays) { - if (CurrentlyControlled && _currentEffect != ScreenEffects.None) + foreach (var container in ActiveOverlays.ShallowClone()) { - var overlay = _effectsDictionary[_currentEffect]; - if (_overlayManager.HasOverlay(overlay.ID)) + if (!newOverlays.Contains(container)) { - return; + RemoveOverlay(container); } - _overlayManager.AddOverlay(overlay); - Logger.InfoS("overlay", $"Changed overlay to {overlay}"); } + + foreach (var container in newOverlays) + { + if (!ActiveOverlays.Contains(container)) + { + AddOverlay(container); + } + } + } + + private void RemoveOverlay(OverlayContainer container) + { + ActiveOverlays.Remove(container); + _overlayManager.RemoveOverlay(container.ID); + } + + private void AddOverlay(OverlayContainer container) + { + ActiveOverlays.Add(container); + if (TryCreateOverlay(container, out var overlay)) + { + _overlayManager.AddOverlay(overlay); + } + else + { + Logger.ErrorS("overlay", $"Could not add overlay {container.ID}"); + } + } + + private bool TryCreateOverlay(OverlayContainer container, out Overlay overlay) + { + var overlayTypes = _reflectionManager.GetAllChildren(); + var foundType = overlayTypes.FirstOrDefault(t => t.Name == container.ID); + + if (foundType != null) + { + overlay = Activator.CreateInstance(foundType) as Overlay; + var configurable = foundType + .GetInterfaces() + .FirstOrDefault(type => + type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IConfigurable<>) + && type.GenericTypeArguments.First() == container.GetType()); + + if (configurable != null) + { + var method = overlay?.GetType().GetMethod("Configure"); + method?.Invoke(overlay, new []{ container }); + } + + return true; + } + + overlay = default; + return false; } } } diff --git a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs index 3b0a1358ce..ef6832480a 100644 --- a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs +++ b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs @@ -1,4 +1,5 @@ -using Robust.Client.Graphics.Drawing; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; using Robust.Client.Graphics.Shaders; using Robust.Client.Interfaces.Graphics.ClientEye; @@ -17,7 +18,7 @@ namespace Content.Client.Graphics.Overlays public override OverlaySpace Space => OverlaySpace.WorldSpace; - public CircleMaskOverlay() : base(nameof(CircleMaskOverlay)) + public CircleMaskOverlay() : base(nameof(OverlayType.CircleMaskOverlay)) { IoCManager.InjectDependencies(this); Shader = _prototypeManager.Index("CircleMask").Instance(); diff --git a/Content.Client/Graphics/Overlays/FlashOverlay.cs b/Content.Client/Graphics/Overlays/FlashOverlay.cs new file mode 100644 index 0000000000..f156225434 --- /dev/null +++ b/Content.Client/Graphics/Overlays/FlashOverlay.cs @@ -0,0 +1,73 @@ +using System.Net.Mime; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.Interfaces; +using Robust.Client.Graphics; +using Robust.Client.Graphics.Drawing; +using Robust.Client.Graphics.Overlays; +using Robust.Client.Graphics.Shaders; +using Robust.Client.Interfaces.Graphics; +using Robust.Client.Interfaces.Graphics.ClientEye; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Color = Robust.Shared.Maths.Color; + +namespace Content.Client.Graphics.Overlays +{ + public class FlashOverlay : Overlay, IConfigurable + { +#pragma warning disable 649 + [Dependency] private readonly IPrototypeManager _prototypeManager; + [Dependency] private readonly IClyde _displayManager; + [Dependency] private readonly IGameTiming _gameTiming; +#pragma warning restore 649 + + public override OverlaySpace Space => OverlaySpace.ScreenSpace; + private double _startTime; + private int lastsFor = 5000; + private Texture _screenshotTexture; + + public FlashOverlay() : base(nameof(OverlayType.FlashOverlay)) + { + IoCManager.InjectDependencies(this); + Shader = _prototypeManager.Index("FlashedEffect").Instance().Duplicate(); + + _startTime = _gameTiming.CurTime.TotalMilliseconds; + _displayManager.Screenshot(ScreenshotType.BeforeUI, image => + { + var rgba32Image = image.CloneAs(Configuration.Default); + _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); + }); + } + + protected override void Draw(DrawingHandleBase handle) + { + var percentComplete = (float) ((_gameTiming.CurTime.TotalMilliseconds - _startTime) / lastsFor); + Shader?.SetParameter("percentComplete", percentComplete); + + var screenSpaceHandle = handle as DrawingHandleScreen; + var screenSize = UIBox2.FromDimensions((0, 0), _displayManager.ScreenSize); + + if (_screenshotTexture != null) + { + screenSpaceHandle?.DrawTextureRect(_screenshotTexture, screenSize); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _screenshotTexture = null; + } + + public void Configure(TimedOverlayContainer parameters) + { + lastsFor = parameters.Length; + } + } +} diff --git a/Content.Client/Graphics/Overlays/GradientCircleMask.cs b/Content.Client/Graphics/Overlays/GradientCircleMask.cs index 72b1a7e3c3..e95fe30d83 100644 --- a/Content.Client/Graphics/Overlays/GradientCircleMask.cs +++ b/Content.Client/Graphics/Overlays/GradientCircleMask.cs @@ -1,4 +1,5 @@ -using Robust.Client.Graphics.Drawing; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; using Robust.Client.Graphics.Shaders; using Robust.Client.Interfaces.Graphics.ClientEye; @@ -8,7 +9,7 @@ using Robust.Shared.Prototypes; namespace Content.Client.Graphics.Overlays { - public class GradientCircleMask : Overlay + public class GradientCircleMaskOverlay : Overlay { #pragma warning disable 649 [Dependency] private readonly IPrototypeManager _prototypeManager; @@ -16,7 +17,7 @@ namespace Content.Client.Graphics.Overlays #pragma warning restore 649 public override OverlaySpace Space => OverlaySpace.WorldSpace; - public GradientCircleMask() : base(nameof(GradientCircleMask)) + public GradientCircleMaskOverlay() : base(nameof(OverlayType.GradientCircleMaskOverlay)) { IoCManager.InjectDependencies(this); Shader = _prototypeManager.Index("GradientCircleMask").Instance(); diff --git a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs index 18a93231da..d800514df1 100644 --- a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs +++ b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs @@ -69,21 +69,24 @@ namespace Content.Server.GameObjects statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Mob/UI/Human/human" + modifier + ".png"); - overlayComponent?.ChangeOverlay(ScreenEffects.None); + overlayComponent?.RemoveOverlay(OverlayType.GradientCircleMaskOverlay); + overlayComponent?.RemoveOverlay(OverlayType.CircleMaskOverlay); return; case ThresholdType.Critical: statusEffectsComponent?.ChangeStatusEffectIcon( StatusEffect.Health, "/Textures/Mob/UI/Human/humancrit-0.png"); - overlayComponent?.ChangeOverlay(ScreenEffects.GradientCircleMask); + overlayComponent?.ClearOverlays(); + overlayComponent?.AddOverlay(OverlayType.GradientCircleMaskOverlay); return; case ThresholdType.Death: statusEffectsComponent?.ChangeStatusEffectIcon( StatusEffect.Health, "/Textures/Mob/UI/Human/humandead.png"); - overlayComponent?.ChangeOverlay(ScreenEffects.CircleMask); + overlayComponent?.ClearOverlays(); + overlayComponent?.AddOverlay(OverlayType.CircleMaskOverlay); return; default: diff --git a/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs index 8cfcb33cb8..92c0b2d5d5 100644 --- a/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs @@ -1,5 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; using Content.Shared.GameObjects.Components.Mobs; using Robust.Shared.GameObjects; +using Robust.Shared.Timers; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Mobs { @@ -7,20 +12,49 @@ namespace Content.Server.GameObjects.Components.Mobs [ComponentReference(typeof(SharedOverlayEffectsComponent))] public sealed class ServerOverlayEffectsComponent : SharedOverlayEffectsComponent { - private ScreenEffects _currentOverlay = ScreenEffects.None; + private readonly List _currentOverlays = new List(); + + [ViewVariables(VVAccess.ReadWrite)] + private List ActiveOverlays => _currentOverlays; public override ComponentState GetComponentState() { - return new OverlayEffectComponentState(_currentOverlay); + return new OverlayEffectComponentState(_currentOverlays); } - public void ChangeOverlay(ScreenEffects effect) + public void AddOverlay(OverlayContainer container) { - if (effect == _currentOverlay) + if (!ActiveOverlays.Contains(container)) { - return; + ActiveOverlays.Add(container); + Dirty(); } - _currentOverlay = effect; + } + + public void AddOverlay(string id) => AddOverlay(new OverlayContainer(id)); + public void AddOverlay(OverlayType type) => AddOverlay(new OverlayContainer(type)); + + public void RemoveOverlay(OverlayContainer container) + { + if (ActiveOverlays.RemoveAll(c => c.Equals(container)) > 0) + { + Dirty(); + } + } + + public void RemoveOverlay(string id) + { + if (ActiveOverlays.RemoveAll(container => container.ID == id) > 0) + { + Dirty(); + } + } + + public void RemoveOverlay(OverlayType type) => RemoveOverlay(type.ToString()); + + public void ClearOverlays() + { + ActiveOverlays.Clear(); Dirty(); } } diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs index 23342f3a15..ed64d334b6 100644 --- a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs @@ -79,7 +79,7 @@ namespace Content.Server.GameObjects statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Health); Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent); - overlayEffectsComponent?.ChangeOverlay(ScreenEffects.None); + overlayEffectsComponent?.ClearOverlays(); } bool IActionBlocker.CanMove() diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs new file mode 100644 index 0000000000..354974a685 --- /dev/null +++ b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces; +using Content.Shared.Chat; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.Interfaces; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; +using Timer = Robust.Shared.Timers.Timer; + +namespace Content.Server.GameObjects.Components.Weapon.Melee +{ + [RegisterComponent] + public class FlashComponent : MeleeWeaponComponent, IUse, IExamine + { +#pragma warning disable 649 + [Dependency] private readonly ILocalizationManager _localizationManager; + [Dependency] private readonly IEntityManager _entityManager; + [Dependency] private readonly ISharedNotifyManager _notifyManager; +#pragma warning restore 649 + + public override string Name => "Flash"; + + [ViewVariables(VVAccess.ReadWrite)] private int _flashDuration = 5000; + [ViewVariables(VVAccess.ReadWrite)] private float _flashFalloffExp = 8f; + [ViewVariables(VVAccess.ReadWrite)] private int _uses = 5; + [ViewVariables(VVAccess.ReadWrite)] private float _range = 3f; + [ViewVariables(VVAccess.ReadWrite)] private int _aoeFlashDuration = 5000 / 3; + [ViewVariables(VVAccess.ReadWrite)] private float _slowTo = 0.75f; + private bool _flashing; + + private int Uses + { + get => _uses; + set + { + _uses = value; + Dirty(); + } + } + + private bool HasUses => _uses > 0; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _flashDuration, "duration", 5000); + serializer.DataField(ref _flashFalloffExp, "flashFalloffExp", 8f); + serializer.DataField(ref _uses, "uses", 5); + serializer.DataField(ref _range, "range", 3f); + serializer.DataField(ref _aoeFlashDuration, "aoeFlashDuration", _flashDuration / 3); + serializer.DataField(ref _slowTo, "slowTo", 0.75f); + } + + protected override bool OnHitEntities(IReadOnlyList entities, AttackEventArgs eventArgs) + { + if (entities.Count == 0) + { + return false; + } + + if (!Use(eventArgs.User)) + { + return false; + } + + foreach (var entity in entities) + { + Flash(entity, eventArgs.User); + } + + return true; + } + + public bool UseEntity(UseEntityEventArgs eventArgs) + { + if (!Use(eventArgs.User)) + { + return false; + } + + foreach (var entity in _entityManager.GetEntitiesInRange(Owner.Transform.GridPosition, _range)) + { + Flash(entity, eventArgs.User, _aoeFlashDuration); + } + + return true; + } + + private bool Use(IEntity user) + { + if (HasUses) + { + var sprite = Owner.GetComponent(); + if (--Uses == 0) + { + sprite.LayerSetState(0, "burnt"); + + _notifyManager.PopupMessage(Owner, user, "The flash burns out!"); + } + else if (!_flashing) + { + int animLayer = sprite.AddLayerWithState("flashing"); + _flashing = true; + + Timer.Spawn(400, () => + { + sprite.RemoveLayer(animLayer); + _flashing = false; + }); + } + + EntitySystem.Get().PlayAtCoords("/Audio/weapons/flash.ogg", Owner.Transform.GridPosition, + AudioParams.Default); + + return true; + } + + return false; + } + + private void Flash(IEntity entity, IEntity user) + { + Flash(entity, user, _flashDuration); + } + + // TODO: Check if target can be flashed (e.g. things like sunglasses would block a flash) + private void Flash(IEntity entity, IEntity user, int flashDuration) + { + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent)) + { + var container = new TimedOverlayContainer(nameof(OverlayType.FlashOverlay), flashDuration); + overlayEffectsComponent.AddOverlay(container); + container.StartTimer(() => overlayEffectsComponent.RemoveOverlay(container)); + } + + if (entity.TryGetComponent(out StunnableComponent stunnableComponent)) + { + stunnableComponent.Slowdown(flashDuration / 1000f, _slowTo, _slowTo); + } + + if (entity != user) + { + _notifyManager.PopupMessage(user, entity, $"{user.Name} blinds you with the {Owner.Name}"); + } + } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + if (!HasUses) + { + message.AddText("It's burnt out."); + return; + } + + if (inDetailsRange) + { + message.AddMarkup(_localizationManager.GetString( + $"The flash has [color=green]{Uses}[/color] uses remaining.")); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs index 29dbe2f98c..121d755527 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs @@ -81,9 +81,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee serializer.DataField(ref _cooldownTime, "cooldownTime", 1f); } - public virtual bool OnHitEntities(IReadOnlyList entities) + protected virtual bool OnHitEntities(IReadOnlyList entities, AttackEventArgs eventArgs) { - return false; + return true; } void IAttack.Attack(AttackEventArgs eventArgs) @@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee } } - if(OnHitEntities(hitEntities)) return; + if(!OnHitEntities(hitEntities, eventArgs)) return; var audioSystem = EntitySystem.Get(); var emitter = hitEntities.Count == 0 ? eventArgs.User : hitEntities[0]; diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs index 029610ba15..367c67fdac 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs @@ -86,7 +86,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee serializer.DataField(ref _slowdownTime, "slowdownTime", 5f); } - public override bool OnHitEntities(IReadOnlyList entities) + protected override bool OnHitEntities(IReadOnlyList entities, AttackEventArgs eventArgs) { var cell = Cell; if (!Activated || entities.Count == 0 || cell == null) @@ -118,7 +118,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee TurnOff(); } - return false; + return true; } private bool ToggleStatus(IEntity user) diff --git a/Content.Server/Mobs/Commands.cs b/Content.Server/Mobs/Commands.cs index 2b04692431..a31eac223c 100644 --- a/Content.Server/Mobs/Commands.cs +++ b/Content.Server/Mobs/Commands.cs @@ -1,4 +1,5 @@ using System.Text; +using Content.Server.GameObjects.Components.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Players; using Content.Shared.Jobs; @@ -117,4 +118,52 @@ namespace Content.Server.Mobs } } } + + public class AddOverlayCommand : IClientCommand + { + public string Command => "addoverlay"; + public string Description => "Adds an overlay by its ID"; + public string Help => "addoverlay "; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + if (args.Length != 1) + { + shell.SendText(player, "Expected 1 argument."); + return; + } + + if (player?.AttachedEntity != null) + { + if (player.AttachedEntity.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent)) + { + overlayEffectsComponent.AddOverlay(args[0]); + } + } + } + } + + public class RemoveOverlayCommand : IClientCommand + { + public string Command => "rmoverlay"; + public string Description => "Removes an overlay by its ID"; + public string Help => "rmoverlay "; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + if (args.Length != 1) + { + shell.SendText(player, "Expected 1 argument."); + return; + } + + if (player?.AttachedEntity != null) + { + if (player.AttachedEntity.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent)) + { + overlayEffectsComponent.RemoveOverlay(args[0]); + } + } + } + } } diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs index 07b2b839c2..fd68608336 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs @@ -1,6 +1,14 @@ using System; +using System.Collections.Generic; +using System.ComponentModel; +using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; +using Robust.Shared.Timers; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; +using Component = Robust.Shared.GameObjects.Component; namespace Content.Shared.GameObjects.Components.Mobs { @@ -13,21 +21,72 @@ namespace Content.Shared.GameObjects.Components.Mobs public sealed override uint? NetID => ContentNetIDs.OVERLAYEFFECTS; } - public enum ScreenEffects + [Serializable, NetSerializable] + public class OverlayContainer { - None, - CircleMask, - GradientCircleMask, + [ViewVariables(VVAccess.ReadOnly)] + public string ID { get; } + + public OverlayContainer([NotNull] string id) + { + ID = id; + } + + public OverlayContainer(OverlayType type) : this(type.ToString()) + { + + } + + public override bool Equals(object obj) + { + if (obj is OverlayContainer container) + { + return container.ID == ID; + } + + if (obj is string idString) + { + return idString == ID; + } + + return base.Equals(obj); + } + + public override int GetHashCode() + { + return (ID != null ? ID.GetHashCode() : 0); + } } [Serializable, NetSerializable] public class OverlayEffectComponentState : ComponentState { - public ScreenEffects ScreenEffect; + public List Overlays; - public OverlayEffectComponentState(ScreenEffects screenEffect) : base(ContentNetIDs.OVERLAYEFFECTS) + public OverlayEffectComponentState(List overlays) : base(ContentNetIDs.OVERLAYEFFECTS) { - ScreenEffect = screenEffect; + Overlays = overlays; } } + + [Serializable, NetSerializable] + public class TimedOverlayContainer : OverlayContainer + { + [ViewVariables(VVAccess.ReadOnly)] + public int Length { get; } + + public TimedOverlayContainer(string id, int length) : base(id) + { + Length = length; + } + + public void StartTimer(Action finished) => Timer.Spawn(Length, finished); + } + + public enum OverlayType + { + GradientCircleMaskOverlay, + CircleMaskOverlay, + FlashOverlay + } } diff --git a/Content.Shared/Interfaces/IConfigurable.cs b/Content.Shared/Interfaces/IConfigurable.cs new file mode 100644 index 0000000000..17fbe07880 --- /dev/null +++ b/Content.Shared/Interfaces/IConfigurable.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Interfaces +{ + public interface IConfigurable + { + public void Configure(T parameters); + } +} diff --git a/Resources/Audio/weapons/flash.ogg b/Resources/Audio/weapons/flash.ogg new file mode 100644 index 0000000000000000000000000000000000000000..eed2ed0d52410cb4047db0a99f9d2e08a664d32f GIT binary patch literal 9891 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 literal 0 HcmV?d00001 diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index d1b4d32d96..dc76b17683 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -74,6 +74,8 @@ - mindinfo - addrole - rmrole + - addoverlay + - rmoverlay - showtime - group - addai @@ -121,6 +123,8 @@ - mindinfo - addrole - rmrole + - addoverlay + - rmoverlay - srvpopupmsg - group - showtime diff --git a/Resources/Prototypes/Entities/Items/Weapons/security.yml b/Resources/Prototypes/Entities/Items/Weapons/security.yml index 43e28a9ef7..957bab088b 100644 --- a/Resources/Prototypes/Entities/Items/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Items/Weapons/security.yml @@ -23,3 +23,30 @@ HeldPrefix: off - type: ItemCooldown + +- type: entity + name: flash + parent: BaseItem + id: Flash + components: + - type: Sprite + sprite: Objects/Melee/flash.rsi + state: flash + + - type: Icon + sprite: Objects/Melee/flash.rsi + state: flash + + - type: Flash + damage: 0 + cooldownTime: 1 + arc: smash + hitSound: /Audio/weapons/flash.ogg + slowTo: 0.7 + + - type: Item + Size: 2 + sprite: Objects/Melee/flash.rsi + + - type: ItemCooldown + diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index e278d35c9a..b206811bc0 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -7,3 +7,8 @@ id: GradientCircleMask kind: source path: "/Shaders/gradient_circle_mask.swsl" + +- type: shader + id: FlashedEffect + kind: source + path: "/Shaders/flashed_effect.swsl" diff --git a/Resources/Shaders/flashed_effect.swsl b/Resources/Shaders/flashed_effect.swsl new file mode 100644 index 0000000000..fdbe860cb7 --- /dev/null +++ b/Resources/Shaders/flashed_effect.swsl @@ -0,0 +1,18 @@ +uniform float percentComplete; +uniform float fadeFalloffExp = 8; + +void fragment() { + // Higher exponent -> stronger blinding effect + float remaining = -pow(percentComplete, fadeFalloffExp) + 1; + + // Two ghost textures that spin around the character + vec4 tex1 = texture(TEXTURE, vec2(UV.x + (0.02) * sin(TIME * 3), UV.y + (0.02) * cos(TIME * 3))); + vec4 tex2 = texture(TEXTURE, vec2(UV.x + (0.01) * sin(TIME * 2), UV.y + (0.01) * cos(TIME * 2))); + + vec4 textureMix = mix(tex1, tex2, 0.5); + + // Gradually mixes between the texture mix and a full-white texture, causing the "blinding" effect + vec4 mixed = mix(vec4(1, 1, 1, 1), textureMix, percentComplete); + + COLOR = vec4(mixed.rgb, remaining); +} diff --git a/Resources/Textures/Objects/Melee/flash.rsi/burnt.png b/Resources/Textures/Objects/Melee/flash.rsi/burnt.png new file mode 100644 index 0000000000000000000000000000000000000000..b35979f0f75bff0c35d3cdae3530bced037b2bc0 GIT binary patch literal 354 zcmV-o0iFJdP)EqzL|BvIoJ1`gw{~b%G`Mgp|v-)APQQE7j(&RKdDZmRbvv;`~j{#B@ z3F!u>*-5>@1pp$B@cjUQM7;8uB;^a#>$UvYd&3bTj(CqgST9#f5<9Zt-M&Y+?{U<2 zxxc=|sWkww5B6lXk{8Ir+pY`1ajTU}oSmL1g-Z!!Ou|q(Oj}?!oo=l~94QO@dJGqf z*aFhx+S??gHyrW!^jyBcHi=^RdbuiJ0N|@?Q+x+>!a?B@ig{q&-rVK$uZy(6U!x^( z*w_cuA0cglBC3CZ(r+l{f#v@gNtJMe!C(OR2D(&pMl2!uD*ylh07*qoM6N<$f-FX& Ah5!Hn literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Melee/flash.rsi/flash.png b/Resources/Textures/Objects/Melee/flash.rsi/flash.png new file mode 100644 index 0000000000000000000000000000000000000000..a5afbcdae9d167b0ebea95769ee2c859ab356ff4 GIT binary patch literal 368 zcmV-$0gwKPP)A#;;^N>U?Wq*1pP-YG*a!HCk)dq{**TCBQTA zTAbsu*8`9y650)Zr791P4ge6tm@te0NW=?YNs_;Sx4D{adn2eLhB0sP2jjuWNMc4e zyw++`Yc-ia-s8M`fxlb^ptw@fSxR3Z4L@Dn1z=}uJCg`D){Vlo1X4~u?~KE&1v(G+ zKaRyPHWv8xHI(7IH=!*%N^Nr1rV-TXK0f6yph~23HGDi6>uin zTvRVO?L6h1(`MLyN;FYg0-9Ie01 ziZb~5U*Usb(at-^r|*8qCm;v}nX1QkIi7zSex@Sv?moTs&71yT-2C_Z|B|fF8!i{W zp^wHbw@8ee)SlRCK7kIr4Jnm*B(GA5E>xda=AM{aMr1t{>iU z>}Mu7{PCEn&iGm7!Qr!(t3@~V3*Tl7&+jk&wfb#sW#xtMk*Wzcb8~w8Q-i172@EcI a&7W{`*$RzKncBdhW$<+Mb6Mw<&;$Tle6Xhg literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Melee/flash.rsi/inhand-left.png b/Resources/Textures/Objects/Melee/flash.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..950195a37d41d514ff09b7b29a5544a4d2d7d211 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=_dQ)4Ln`LHy`{)^$U&g>p*T;I z@s$a({aWkl-e2ClRs123hsX=p31+qjq=OFqpSf%M$u}iudsu)v85m^d)!zR$XM5=h zj=eT=yU*SJB%bgh%4Nlrq@7!Xyt+9uUj*$s5Z$ma`>#n>-Q6`CKfhj9Cl%b4@V?>o zOC8TH4)1%DHa~fH|H9U&)nAuot$k}=$LRUA?oUl`{sA*_umvEZdXnY&^7)^Ie>2W} z{v*Bl$?0N6+Id3>M=lOb{Y7Wi(hAH;v%ik*kwLZAN YhmpU|x3#6}d_Bl0Pgg&ebxsLQ0GCsHhX4Qo literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Melee/flash.rsi/inhand-right.png b/Resources/Textures/Objects/Melee/flash.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..7a17666d9cd7c615deb6649fb3228eea50a35448 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=?>$`{Ln`LHy`?MEY#`9~aPF+G zd5o7&{?dB&s963SoA^VXsVf!*i(c2{{MmSJPk-A<^V33`wV8lA85s6ee&1_v<=i!8 z&-5AXa;G=hpJfdClqYsT;&Eu`k+T06S+{*&rFCq^iamj{25UEed0g;M>BhP5j}g}TZ+%k|M!}G+vjsTi>4?1n11HbP0l+XkK)J=b6 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Melee/flash.rsi/meta.json b/Resources/Textures/Objects/Melee/flash.rsi/meta.json new file mode 100644 index 0000000000..c0299aa63d --- /dev/null +++ b/Resources/Textures/Objects/Melee/flash.rsi/meta.json @@ -0,0 +1,76 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC BY-SA 3.0", + "copyright": "Taken from CEV Eris", + "states": [ + { + "name": "flash", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "flashing", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.3 + ] + ] + }, + { + "name": "burnt", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "inhand-right", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + } + ] +}