diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index 2284015a6cb..4372e5e80fb 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Client.Chat.Managers;
+using System.Text; // Arcane
using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Speech;
@@ -38,6 +39,13 @@ public enum SpeechType : byte
///
private static readonly TimeSpan FadeTime = TimeSpan.FromSeconds(0.25f);
+ // Arcane-start
+ private const float RevealRunesPerSecond = 15.5f;
+ private const float SpaceRevealWeight = 2.25f;
+ private static readonly TimeSpan MaxRevealTime = TimeSpan.FromSeconds(4);
+ protected virtual float RevealSpeedMultiplier => 1f;
+ // Arcane-end
+
///
/// The distance in world space to offset the speech bubble from the center of the entity.
/// i.e. greater -> higher above the mob's head.
@@ -55,6 +63,12 @@ public enum SpeechType : byte
/// The time at which this bubble will die.
///
private TimeSpan _deathTime;
+ // Arcane-start
+ private readonly TimeSpan _creationTime;
+ private readonly TimeSpan _revealTime;
+ private readonly float _maxRevealWeight;
+ private readonly List _textReveals = new();
+ // Arcane-end
public float VerticalOffset { get; set; }
private float _verticalOffsetAchieved;
@@ -95,6 +109,10 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt
RectClipContent = true;
var bubble = BuildBubble(message, speechStyleClass, fontColor);
+ // Arcane-start
+ bubble.HorizontalAlignment = HAlignment.Center;
+ bubble.VerticalAlignment = VAlignment.Bottom;
+ // Arcane-end
AddChild(bubble);
@@ -102,8 +120,16 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt
bubble.Measure(Vector2Helpers.Infinity);
ContentSize = bubble.DesiredSize;
+ // Arcane-start
+ bubble.MinWidth = ContentSize.X;
+ _creationTime = _timing.RealTime;
+ _maxRevealWeight = GetMaxRevealWeight();
+ _revealTime = GetRevealTime(_maxRevealWeight);
+ _deathTime = _creationTime + TotalTime + _revealTime;
+ UpdateTextReveal();
+ // Arcane-end
_verticalOffsetAchieved = -ContentSize.Y;
- _deathTime = _timing.RealTime + TotalTime;
+ // _deathTime = _timing.RealTime + TotalTime; Arcane delete
}
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
@@ -120,6 +146,10 @@ protected override void FrameUpdate(FrameEventArgs args)
return;
}
+ // Arcane-start
+ UpdateTextReveal();
+ // Arcane-end
+
// Lerp to our new vertical offset if it's been modified.
if (MathHelper.CloseToPercent(_verticalOffsetAchieved - VerticalOffset, 0, 0.1))
{
@@ -175,6 +205,173 @@ private void Die()
OnDied?.Invoke(_senderEntity, this);
}
+ // Arcane-start
+ protected void SetRevealedMessage(RichTextLabel label, FormattedMessage message)
+ {
+ label.SetMessage(message);
+
+ var revealWeight = CountRevealWeight(message);
+ if (revealWeight <= 0f)
+ return;
+
+ _textReveals.Add(new SpeechTextReveal(label, message, revealWeight));
+ }
+
+ private float GetMaxRevealWeight()
+ {
+ var revealWeight = 0f;
+
+ foreach (var reveal in _textReveals)
+ {
+ revealWeight = Math.Max(revealWeight, reveal.RevealWeight);
+ }
+
+ return revealWeight;
+ }
+
+ private TimeSpan GetRevealTime(float revealWeight)
+ {
+ if (revealWeight <= 0f)
+ return TimeSpan.Zero;
+
+ var seconds = revealWeight / (RevealRunesPerSecond * RevealSpeedMultiplier);
+ return TimeSpan.FromSeconds(MathF.Min(seconds, (float) MaxRevealTime.TotalSeconds));
+ }
+
+ private void UpdateTextReveal()
+ {
+ if (_textReveals.Count == 0)
+ return;
+
+ var progress = _revealTime <= TimeSpan.Zero
+ ? 1f
+ : MathHelper.Clamp((float) ((_timing.RealTime - _creationTime).TotalSeconds / _revealTime.TotalSeconds), 0f, 1f);
+
+ var visibleWeight = _maxRevealWeight * progress;
+
+ foreach (var reveal in _textReveals)
+ {
+ var visibleRunes = CountVisibleRunes(reveal.Message, Math.Min(visibleWeight, reveal.RevealWeight));
+ if (visibleRunes == reveal.LastVisibleRunes)
+ continue;
+
+ reveal.LastVisibleRunes = visibleRunes;
+ reveal.Label.SetMessage(CreateRevealedMessage(reveal.Message, visibleRunes));
+ }
+ }
+
+ private static float CountRevealWeight(FormattedMessage message)
+ {
+ var weight = 0f;
+
+ foreach (var node in message.Nodes)
+ {
+ if (node.Name != null || node.Value.StringValue == null)
+ continue;
+
+ foreach (var rune in node.Value.StringValue.EnumerateRunes())
+ {
+ weight += GetRevealWeight(rune);
+ }
+ }
+
+ return weight;
+ }
+
+ private static int CountVisibleRunes(FormattedMessage message, float visibleWeight)
+ {
+ var remaining = visibleWeight;
+ var count = 0;
+
+ foreach (var node in message.Nodes)
+ {
+ if (node.Name != null)
+ continue;
+
+ var text = node.Value.StringValue;
+ if (text == null || remaining <= 0f)
+ continue;
+
+ foreach (var rune in text.EnumerateRunes())
+ {
+ if (remaining <= 0f)
+ break;
+
+ count++;
+ remaining -= GetRevealWeight(rune);
+ }
+ }
+
+ return count;
+ }
+
+ private static FormattedMessage CreateRevealedMessage(FormattedMessage message, int visibleRunes)
+ {
+ var result = new FormattedMessage(message.Count);
+ var remaining = visibleRunes;
+
+ foreach (var node in message.Nodes)
+ {
+ if (node.Name != null)
+ {
+ result.AddMarkupOrThrow(node.ToString());
+ continue;
+ }
+
+ var text = node.Value.StringValue;
+ if (text == null)
+ continue;
+
+ AddRevealedText(result, text, ref remaining);
+ }
+
+ return result;
+ }
+
+ private static void AddRevealedText(FormattedMessage result, string text, ref int remainingVisibleRunes)
+ {
+ var visible = new StringBuilder();
+ var hidden = new StringBuilder();
+
+ foreach (var rune in text.EnumerateRunes())
+ {
+ if (remainingVisibleRunes > 0)
+ {
+ visible.Append(rune);
+ remainingVisibleRunes--;
+ continue;
+ }
+
+ hidden.Append(rune);
+ }
+
+ if (visible.Length > 0)
+ result.AddText(visible.ToString());
+
+ if (hidden.Length == 0)
+ return;
+
+ result.PushColor(Color.Transparent);
+ result.AddText(hidden.ToString());
+ result.Pop();
+ }
+
+ private static float GetRevealWeight(Rune rune)
+ {
+ return rune.Value is ' ' or '\n' or '\t'
+ ? SpaceRevealWeight
+ : 1f;
+ }
+
+ private sealed class SpeechTextReveal(RichTextLabel label, FormattedMessage message, float revealWeight)
+ {
+ public readonly RichTextLabel Label = label;
+ public readonly FormattedMessage Message = message;
+ public readonly float RevealWeight = revealWeight;
+ public int LastVisibleRunes = -1;
+ }
+ // Arcane-end
+
///
/// Causes the speech bubble to start fading IMMEDIATELY.
///
@@ -204,6 +401,8 @@ protected FormattedMessage ExtractAndFormatSpeechSubstring(ChatMessage message,
public sealed class TextSpeechBubble : SpeechBubble
{
+ protected override float RevealSpeedMultiplier => 3f; // Arcane
+
public TextSpeechBubble(ChatMessage message, EntityUid senderEntity, string speechStyleClass, Color? fontColor = null)
: base(message, senderEntity, speechStyleClass, fontColor)
{
@@ -216,7 +415,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
MaxWidth = SpeechMaxWidth,
};
- label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor));
+ SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor)); // Arcane label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor)); -> SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor));
var panel = new PanelContainer
{
@@ -246,7 +445,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
MaxWidth = SpeechMaxWidth
};
- label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
+ SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); -> SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
var unfanciedPanel = new PanelContainer
{
@@ -267,13 +466,13 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
{
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleTextOpacity)),
MaxWidth = SpeechMaxWidth,
- Margin = new Thickness(2, 6, 2, 2),
+ Margin = new Thickness(2, 2, 2, 2), // LuaM Margin = new Thickness(2, 6, 2, 2), -> Margin = new Thickness(2, 2, 2, 2),
StyleClasses = { "bubbleContent" },
};
//We'll be honest. *Yes* this is hacky. Doing this in a cleaner way would require a bottom-up refactor of how saycode handles sending chat messages. -Myr
bubbleHeader.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleHeader", fontColor));
- bubbleContent.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
+ SetRevealedMessage(bubbleContent, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane bubbleContent.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); -> SetRevealedMessage(bubbleContent, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
//As for below: Some day this could probably be converted to xaml. But that is not today. -Myr
var mainPanel = new PanelContainer
@@ -282,8 +481,8 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
Children = { bubbleContent },
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)),
HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Bottom,
- Margin = new Thickness(4, 14, 4, 2)
+ // VerticalAlignment = VAlignment.Bottom, Arcane delete
+ Margin = new Thickness(4, 0, 4, 2) // Arcane Margin = new Thickness(4, 14, 4, 2) -> Margin = new Thickness(4, 0, 4, 2)
};
var headerPanel = new PanelContainer
@@ -295,9 +494,11 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
VerticalAlignment = VAlignment.Top
};
- var panel = new PanelContainer
+ var panel = new BoxContainer // Arcane var panel = new PanelContainer -> var panel = new BoxContainer
{
- Children = { mainPanel, headerPanel }
+ Orientation = BoxContainer.LayoutOrientation.Vertical, // Arcane
+ HorizontalAlignment = HAlignment.Center, // Arcane
+ Children = { headerPanel, mainPanel }
};
return panel;
diff --git a/Content.Client/CombatMode/CombatModeSystem.cs b/Content.Client/CombatMode/CombatModeSystem.cs
index a364f107aeb..0a17a49f083 100644
--- a/Content.Client/CombatMode/CombatModeSystem.cs
+++ b/Content.Client/CombatMode/CombatModeSystem.cs
@@ -2,10 +2,13 @@
using Content.Client.NPC.HTN;
using Content.Shared.CCVar;
using Content.Shared.CombatMode;
+using Content.Shared.StatusIcon.Components; // Europa
+using Robust.Client.GameObjects; // Europa
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared.Configuration;
+using Robust.Shared.Utility; // Europa
namespace Content.Client.CombatMode;
@@ -21,14 +24,18 @@ public sealed partial class CombatModeSystem : SharedCombatModeSystem
/// Raised whenever combat mode changes.
///
public event Action? LocalPlayerCombatModeUpdated;
+ private EntityQuery _spriteQuery; // Europa
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnHandleState);
-
+ SubscribeLocalEvent(UpdateCombatModeIndicator); // Europa
Subs.CVar(_cfg, CCVars.CombatModeIndicatorsPointShow, OnShowCombatIndicatorsChanged, true);
+ Subs.CVar(_cfg, CCVars.CombatIndicator, (bool value) => OnShowCombatIndicatorChanged(value), true); // Europa
+
+ _spriteQuery = GetEntityQuery(); // Europa
}
private void OnHandleState(EntityUid uid, CombatModeComponent component, ref AfterAutoHandleStateEvent args)
@@ -91,4 +98,47 @@ private void OnShowCombatIndicatorsChanged(bool isShow)
_overlayManager.RemoveOverlay();
}
}
+
+ // Europa-Start
+ private bool _combatIndicatorEnabled = false;
+
+ private void OnShowCombatIndicatorChanged(bool value)
+ {
+ _combatIndicatorEnabled = value;
+ }
+
+ private void UpdateCombatModeIndicator(EntityUid uid, CombatModeComponent comp, ref GetStatusIconsEvent _)
+ {
+ if (!_combatIndicatorEnabled)
+ {
+ if (_spriteQuery.TryComp(uid, out var sprite) && sprite.LayerMapTryGet("combat_mode_indicator", out var layerToRemove))
+ {
+ sprite.RemoveLayer(layerToRemove);
+ }
+ return;
+ }
+
+ if (comp.IsInCombatMode)
+ {
+ if (!_spriteQuery.TryComp(uid, out var sprite))
+ return;
+
+ if (!sprite.LayerMapTryGet("combat_mode_indicator", out var layer))
+ {
+ if (!_spriteQuery.TryComp(uid, out var sprite2))
+ return;
+
+ layer = sprite2.AddLayer(new SpriteSpecifier.Rsi(new ResPath("_Europa/Effects/combat_mode.rsi"), "combat_mode"));
+ sprite2.LayerMapSet("combat_mode_indicator", layer);
+ }
+ }
+ else
+ {
+ if (_spriteQuery.TryComp(uid, out var sprite) && sprite.LayerMapTryGet("combat_mode_indicator", out var layerToRemove))
+ {
+ sprite.RemoveLayer(layerToRemove);
+ }
+ }
+ }
+ // Europa-End
}
diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs
index 09473ea0cfc..962d2237665 100644
--- a/Content.Client/DoAfter/DoAfterOverlay.cs
+++ b/Content.Client/DoAfter/DoAfterOverlay.cs
@@ -23,6 +23,7 @@ public sealed class DoAfterOverlay : Overlay
private readonly SharedContainerSystem _container;
private readonly Texture _barTexture;
+ private readonly SpriteSpecifier _cogTexture; // Europa
private readonly ShaderInstance _unshadedShader;
///
@@ -47,6 +48,7 @@ public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager,
_progressColor = _entManager.System();
var sprite = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
_barTexture = _entManager.EntitySysManager.GetEntitySystem().Frame0(sprite);
+ _cogTexture = new SpriteSpecifier.Rsi(new("/Textures/Backmen/Interface/Misc/progress_cog.rsi"), "cog"); // Europa
_unshadedShader = protoManager.Index("unshaded").Instance();
}
@@ -126,8 +128,14 @@ protected override void Draw(in OverlayDrawArgs args)
var position = new Vector2(-_barTexture.Width / 2f / EyeManager.PixelsPerMeter,
yOffset / scale + offset / EyeManager.PixelsPerMeter * scale);
+ // Europa-Start
+ var cogPos = new Vector2(position.X + _barTexture.Width / scale / EyeManager.PixelsPerMeter, position.Y + _barTexture.Height * 2 / scale) / EyeManager.PixelsPerMeter;
+ var cogTexture = _entManager.System().GetFrame(_cogTexture, curTime);
+ // Europa-End
+
// Draw the underlying bar texture
handle.DrawTexture(_barTexture, position);
+ handle.DrawTexture(cogTexture, cogPos); // Europa
Color color;
float elapsedRatio;
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
index 4e1508f710b..2ae993d4e48 100644
--- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
@@ -4,6 +4,7 @@
+
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
index 7ae16b77929..52592f5d856 100644
--- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
@@ -14,6 +14,7 @@ public AccessibilityTab()
Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox);
Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox);
+ Control.AddOptionCheckBox(CCVars.CombatIndicator, CombatIndicatorCheckBox); // Europa
Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox);
Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider);
Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
diff --git a/Content.Shared/CombatMode/SharedCombatModeSystem.cs b/Content.Shared/CombatMode/SharedCombatModeSystem.cs
index d96e1371d63..95a3054d05e 100644
--- a/Content.Shared/CombatMode/SharedCombatModeSystem.cs
+++ b/Content.Shared/CombatMode/SharedCombatModeSystem.cs
@@ -1,5 +1,8 @@
using Content.Shared.Actions;
+using Content.Shared.Bed.Sleep; // Europa
using Content.Shared.Mind;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Systems; // Europa
using Content.Shared.MouseRotator;
using Content.Shared.Movement.Components;
using Content.Shared.Popups;
@@ -11,9 +14,10 @@ namespace Content.Shared.CombatMode;
public abstract partial class SharedCombatModeSystem : EntitySystem
{
[Dependency] protected IGameTiming Timing = default!;
- [Dependency] private SharedActionsSystem _actionsSystem = default!;
- [Dependency] private SharedPopupSystem _popup = default!;
- [Dependency] private SharedMindSystem _mind = default!;
+ [Dependency] private SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private SharedPopupSystem _popup = default!;
+ [Dependency] private SharedMindSystem _mind = default!;
+ [Dependency] private MobStateSystem _mobState = default!; // Europa
public override void Initialize()
{
@@ -45,8 +49,8 @@ private void OnActionPerform(EntityUid uid, CombatModeComponent component, Toggl
args.Handled = true;
SetInCombatMode(uid, !component.IsInCombatMode, component);
- var msg = component.IsInCombatMode ? "action-popup-combat-enabled" : "action-popup-combat-disabled";
- _popup.PopupClient(Loc.GetString(msg), args.Performer, args.Performer);
+// var msg = component.IsInCombatMode ? "action-popup-combat-enabled" : "action-popup-combat-disabled"; // Europa-Remove
+// _popup.PopupClient(Loc.GetString(msg), args.Performer, args.Performer); // Europa-Remove
}
public void SetCanDisarm(EntityUid entity, bool canDisarm, CombatModeComponent? component = null)
@@ -70,6 +74,14 @@ public virtual void SetInCombatMode(EntityUid entity, bool value, CombatModeComp
if (component.IsInCombatMode == value)
return;
+ // Europa-Start | Dont let entity gone postal when unconscious
+ if (_mobState.IsDead(entity) || _mobState.IsCritical(entity) || HasComp(entity))
+ {
+ if (value)
+ return;
+ }
+ // Europa-End
+
component.IsInCombatMode = value;
Dirty(entity, component);
diff --git a/Content.Shared/_Europa/CCVar/CCVars.Accessibility.cs b/Content.Shared/_Europa/CCVar/CCVars.Accessibility.cs
new file mode 100644
index 00000000000..0b4a3928dfd
--- /dev/null
+++ b/Content.Shared/_Europa/CCVar/CCVars.Accessibility.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.Configuration;
+
+namespace Content.Shared.CCVar;
+
+public sealed partial class CCVars
+{
+ ///
+ /// When false - dont show combat indicator.
+ ///
+ public static readonly CVarDef CombatIndicator =
+ CVarDef.Create("accessibility.CombatIndicator", true, CVar.CLIENTONLY | CVar.ARCHIVE);
+}
diff --git a/Resources/Locale/en-US/_europa/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/_europa/escape-menu/ui/options-menu.ftl
new file mode 100644
index 00000000000..58649621aab
--- /dev/null
+++ b/Resources/Locale/en-US/_europa/escape-menu/ui/options-menu.ftl
@@ -0,0 +1 @@
+ui-options-combat-indicator = Show combat mode indicator
diff --git a/Resources/Locale/ru-RU/_europa/escape-menu/ui/options-menu.ftl b/Resources/Locale/ru-RU/_europa/escape-menu/ui/options-menu.ftl
new file mode 100644
index 00000000000..e46c6d02bcf
--- /dev/null
+++ b/Resources/Locale/ru-RU/_europa/escape-menu/ui/options-menu.ftl
@@ -0,0 +1 @@
+ui-options-combat-indicator = Отображение индикатора боевого режима
diff --git a/Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/cog.png b/Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/cog.png
new file mode 100644
index 00000000000..4d098e2717b
Binary files /dev/null and b/Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/cog.png differ
diff --git a/Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/meta.json b/Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/meta.json
new file mode 100644
index 00000000000..df81cc1146e
--- /dev/null
+++ b/Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/meta.json
@@ -0,0 +1,22 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/tgstation/tgstation/blob/886ca0f8dddf83ecaf10c92ff106172722352192/icons/effects/progessbar.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "cog",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/_Europa/Effects/combat_mode.rsi/combat_mode.png b/Resources/Textures/_Europa/Effects/combat_mode.rsi/combat_mode.png
new file mode 100644
index 00000000000..40c2444eb50
Binary files /dev/null and b/Resources/Textures/_Europa/Effects/combat_mode.rsi/combat_mode.png differ
diff --git a/Resources/Textures/_Europa/Effects/combat_mode.rsi/meta.json b/Resources/Textures/_Europa/Effects/combat_mode.rsi/meta.json
new file mode 100644
index 00000000000..7264508ae9b
--- /dev/null
+++ b/Resources/Textures/_Europa/Effects/combat_mode.rsi/meta.json
@@ -0,0 +1,30 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/modular_sand/icons/mob/combat_indicator.dmi | Edited by PuroSlavKing (Github)",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "combat_mode",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}