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
This commit is contained in:
R. Neuser
2020-07-06 16:37:39 -05:00
committed by GitHub
parent b35333d366
commit 88f49961d8
24 changed files with 647 additions and 85 deletions

View File

@@ -1,18 +1,21 @@
using System.Collections.Generic; using System;
using Content.Client.Graphics.Overlays; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Interfaces;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics.Overlays; using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics.Overlays; using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; 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
{ {
/// <summary> /// <summary>
/// A character UI component which shows the current damage state of the mob (living/dead) /// 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))] [ComponentReference(typeof(SharedOverlayEffectsComponent))]
public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI
{ {
/// <summary> /// <summary>
/// An enum representing the current state being applied to the user /// An enum representing the current state being applied to the user
/// </summary> /// </summary>
private ScreenEffects _currentEffect = ScreenEffects.None; private readonly List<OverlayContainer> _currentEffects = new List<OverlayContainer>();
[ViewVariables(VVAccess.ReadOnly)]
public List<OverlayContainer> ActiveOverlays
{
get => _currentEffects;
set => SetEffects(value);
}
#pragma warning disable 649 #pragma warning disable 649
// Required dependencies // Required dependencies
[Dependency] private readonly IOverlayManager _overlayManager; [Dependency] private readonly IOverlayManager _overlayManager;
[Dependency] private readonly IPlayerManager _playerManager; [Dependency] private readonly IReflectionManager _reflectionManager;
#pragma warning restore 649 #pragma warning restore 649
/// <summary>
/// Holds the screen effects that can be applied mapped ot their relevant overlay
/// </summary>
private Dictionary<ScreenEffects, Overlay> _effectsDictionary;
/// <summary>
/// Allows calculating if we need to act due to this component being controlled by the current mob
/// </summary>
private bool CurrentlyControlled => _playerManager.LocalPlayer.ControlledEntity == Owner;
public override void OnAdd()
{
base.OnAdd();
_effectsDictionary = new Dictionary<ScreenEffects, Overlay>()
{
{ ScreenEffects.CircleMask, new CircleMaskOverlay() },
{ ScreenEffects.GradientCircleMask, new GradientCircleMask() }
};
}
public override void HandleMessage(ComponentMessage message, IComponent component) public override void HandleMessage(ComponentMessage message, IComponent component)
{ {
switch (message) switch (message)
{ {
case PlayerAttachedMsg _: case PlayerAttachedMsg _:
SetOverlay(_currentEffect); SetEffects(ActiveOverlays);
break; break;
case PlayerDetachedMsg _: case PlayerDetachedMsg _:
RemoveOverlay(); ActiveOverlays = new List<OverlayContainer>();
break; break;
} }
} }
@@ -70,42 +58,77 @@ namespace Content.Client.GameObjects
public override void HandleComponentState(ComponentState curState, ComponentState nextState) public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{ {
base.HandleComponentState(curState, nextState); base.HandleComponentState(curState, nextState);
if (!(curState is OverlayEffectComponentState state) || _currentEffect == state.ScreenEffect) return; if (!(curState is OverlayEffectComponentState state) || ActiveOverlays.Equals(state.Overlays))
SetOverlay(state.ScreenEffect);
}
private void SetOverlay(ScreenEffects effect)
{
RemoveOverlay();
_currentEffect = effect;
ApplyOverlay();
}
private void RemoveOverlay()
{
if (CurrentlyControlled && _currentEffect != ScreenEffects.None)
{ {
var appliedEffect = _effectsDictionary[_currentEffect]; return;
_overlayManager.RemoveOverlay(appliedEffect.ID);
} }
_currentEffect = ScreenEffects.None; ActiveOverlays = state.Overlays;
} }
private void ApplyOverlay() private void SetEffects(List<OverlayContainer> newOverlays)
{ {
if (CurrentlyControlled && _currentEffect != ScreenEffects.None) foreach (var container in ActiveOverlays.ShallowClone())
{ {
var overlay = _effectsDictionary[_currentEffect]; if (!newOverlays.Contains(container))
if (_overlayManager.HasOverlay(overlay.ID))
{ {
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<Overlay>();
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;
} }
} }
} }

View File

@@ -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.Overlays;
using Robust.Client.Graphics.Shaders; using Robust.Client.Graphics.Shaders;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
@@ -17,7 +18,7 @@ namespace Content.Client.Graphics.Overlays
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public CircleMaskOverlay() : base(nameof(CircleMaskOverlay)) public CircleMaskOverlay() : base(nameof(OverlayType.CircleMaskOverlay))
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
Shader = _prototypeManager.Index<ShaderPrototype>("CircleMask").Instance(); Shader = _prototypeManager.Index<ShaderPrototype>("CircleMask").Instance();

View File

@@ -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<TimedOverlayContainer>
{
#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<ShaderPrototype>("FlashedEffect").Instance().Duplicate();
_startTime = _gameTiming.CurTime.TotalMilliseconds;
_displayManager.Screenshot(ScreenshotType.BeforeUI, image =>
{
var rgba32Image = image.CloneAs<Rgba32>(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;
}
}
}

View File

@@ -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.Overlays;
using Robust.Client.Graphics.Shaders; using Robust.Client.Graphics.Shaders;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
@@ -8,7 +9,7 @@ using Robust.Shared.Prototypes;
namespace Content.Client.Graphics.Overlays namespace Content.Client.Graphics.Overlays
{ {
public class GradientCircleMask : Overlay public class GradientCircleMaskOverlay : Overlay
{ {
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager; [Dependency] private readonly IPrototypeManager _prototypeManager;
@@ -16,7 +17,7 @@ namespace Content.Client.Graphics.Overlays
#pragma warning restore 649 #pragma warning restore 649
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public GradientCircleMask() : base(nameof(GradientCircleMask)) public GradientCircleMaskOverlay() : base(nameof(OverlayType.GradientCircleMaskOverlay))
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
Shader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").Instance(); Shader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").Instance();

View File

@@ -69,21 +69,24 @@ namespace Content.Server.GameObjects
statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Health, statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Health,
"/Textures/Mob/UI/Human/human" + modifier + ".png"); "/Textures/Mob/UI/Human/human" + modifier + ".png");
overlayComponent?.ChangeOverlay(ScreenEffects.None); overlayComponent?.RemoveOverlay(OverlayType.GradientCircleMaskOverlay);
overlayComponent?.RemoveOverlay(OverlayType.CircleMaskOverlay);
return; return;
case ThresholdType.Critical: case ThresholdType.Critical:
statusEffectsComponent?.ChangeStatusEffectIcon( statusEffectsComponent?.ChangeStatusEffectIcon(
StatusEffect.Health, StatusEffect.Health,
"/Textures/Mob/UI/Human/humancrit-0.png"); "/Textures/Mob/UI/Human/humancrit-0.png");
overlayComponent?.ChangeOverlay(ScreenEffects.GradientCircleMask); overlayComponent?.ClearOverlays();
overlayComponent?.AddOverlay(OverlayType.GradientCircleMaskOverlay);
return; return;
case ThresholdType.Death: case ThresholdType.Death:
statusEffectsComponent?.ChangeStatusEffectIcon( statusEffectsComponent?.ChangeStatusEffectIcon(
StatusEffect.Health, StatusEffect.Health,
"/Textures/Mob/UI/Human/humandead.png"); "/Textures/Mob/UI/Human/humandead.png");
overlayComponent?.ChangeOverlay(ScreenEffects.CircleMask); overlayComponent?.ClearOverlays();
overlayComponent?.AddOverlay(OverlayType.CircleMaskOverlay);
return; return;
default: default:

View File

@@ -1,5 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Timers;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Mobs namespace Content.Server.GameObjects.Components.Mobs
{ {
@@ -7,20 +12,49 @@ namespace Content.Server.GameObjects.Components.Mobs
[ComponentReference(typeof(SharedOverlayEffectsComponent))] [ComponentReference(typeof(SharedOverlayEffectsComponent))]
public sealed class ServerOverlayEffectsComponent : SharedOverlayEffectsComponent public sealed class ServerOverlayEffectsComponent : SharedOverlayEffectsComponent
{ {
private ScreenEffects _currentOverlay = ScreenEffects.None; private readonly List<OverlayContainer> _currentOverlays = new List<OverlayContainer>();
[ViewVariables(VVAccess.ReadWrite)]
private List<OverlayContainer> ActiveOverlays => _currentOverlays;
public override ComponentState GetComponentState() 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(); Dirty();
} }
} }

View File

@@ -79,7 +79,7 @@ namespace Content.Server.GameObjects
statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Health); statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Health);
Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent); Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent);
overlayEffectsComponent?.ChangeOverlay(ScreenEffects.None); overlayEffectsComponent?.ClearOverlays();
} }
bool IActionBlocker.CanMove() bool IActionBlocker.CanMove()

View File

@@ -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<IEntity> 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<SpriteComponent>();
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<AudioSystem>().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."));
}
}
}
}

View File

@@ -81,9 +81,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
serializer.DataField(ref _cooldownTime, "cooldownTime", 1f); serializer.DataField(ref _cooldownTime, "cooldownTime", 1f);
} }
public virtual bool OnHitEntities(IReadOnlyList<IEntity> entities) protected virtual bool OnHitEntities(IReadOnlyList<IEntity> entities, AttackEventArgs eventArgs)
{ {
return false; return true;
} }
void IAttack.Attack(AttackEventArgs eventArgs) 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<AudioSystem>(); var audioSystem = EntitySystem.Get<AudioSystem>();
var emitter = hitEntities.Count == 0 ? eventArgs.User : hitEntities[0]; var emitter = hitEntities.Count == 0 ? eventArgs.User : hitEntities[0];

View File

@@ -86,7 +86,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
serializer.DataField(ref _slowdownTime, "slowdownTime", 5f); serializer.DataField(ref _slowdownTime, "slowdownTime", 5f);
} }
public override bool OnHitEntities(IReadOnlyList<IEntity> entities) protected override bool OnHitEntities(IReadOnlyList<IEntity> entities, AttackEventArgs eventArgs)
{ {
var cell = Cell; var cell = Cell;
if (!Activated || entities.Count == 0 || cell == null) if (!Activated || entities.Count == 0 || cell == null)
@@ -118,7 +118,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
TurnOff(); TurnOff();
} }
return false; return true;
} }
private bool ToggleStatus(IEntity user) private bool ToggleStatus(IEntity user)

View File

@@ -1,4 +1,5 @@
using System.Text; using System.Text;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Mobs.Roles; using Content.Server.Mobs.Roles;
using Content.Server.Players; using Content.Server.Players;
using Content.Shared.Jobs; 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 <id>";
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 <id>";
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]);
}
}
}
}
} }

View File

@@ -1,6 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; 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 namespace Content.Shared.GameObjects.Components.Mobs
{ {
@@ -13,21 +21,72 @@ namespace Content.Shared.GameObjects.Components.Mobs
public sealed override uint? NetID => ContentNetIDs.OVERLAYEFFECTS; public sealed override uint? NetID => ContentNetIDs.OVERLAYEFFECTS;
} }
public enum ScreenEffects [Serializable, NetSerializable]
public class OverlayContainer
{ {
None, [ViewVariables(VVAccess.ReadOnly)]
CircleMask, public string ID { get; }
GradientCircleMask,
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] [Serializable, NetSerializable]
public class OverlayEffectComponentState : ComponentState public class OverlayEffectComponentState : ComponentState
{ {
public ScreenEffects ScreenEffect; public List<OverlayContainer> Overlays;
public OverlayEffectComponentState(ScreenEffects screenEffect) : base(ContentNetIDs.OVERLAYEFFECTS) public OverlayEffectComponentState(List<OverlayContainer> 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
}
} }

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.Interfaces
{
public interface IConfigurable<in T>
{
public void Configure(T parameters);
}
}

Binary file not shown.

View File

@@ -74,6 +74,8 @@
- mindinfo - mindinfo
- addrole - addrole
- rmrole - rmrole
- addoverlay
- rmoverlay
- showtime - showtime
- group - group
- addai - addai
@@ -121,6 +123,8 @@
- mindinfo - mindinfo
- addrole - addrole
- rmrole - rmrole
- addoverlay
- rmoverlay
- srvpopupmsg - srvpopupmsg
- group - group
- showtime - showtime

View File

@@ -23,3 +23,30 @@
HeldPrefix: off HeldPrefix: off
- type: ItemCooldown - 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

View File

@@ -7,3 +7,8 @@
id: GradientCircleMask id: GradientCircleMask
kind: source kind: source
path: "/Shaders/gradient_circle_mask.swsl" path: "/Shaders/gradient_circle_mask.swsl"
- type: shader
id: FlashedEffect
kind: source
path: "/Shaders/flashed_effect.swsl"

View File

@@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

View File

@@ -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
]
]
}
]
}