From dcf21d1358bee2ae395b05ce7643e31d293cc1ed Mon Sep 17 00:00:00 2001
From: Vecortys <123871947+Vecortys@users.noreply.github.com>
Date: Fri, 3 Jul 2026 22:16:02 +0300
Subject: [PATCH 1/6] =?UTF-8?q?speechbubble=20=D0=BF=D1=80=D0=BE=D1=8F?=
=?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D1=82=D0=B5=D0=BA=D1=81=D1=82?=
=?UTF-8?q?=20=D0=BF=D0=BE=D1=81=D1=82=D0=B5=D0=BF=D0=B5=D0=BD=D0=BD=D0=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Content.Client/Chat/UI/SpeechBubble.cs | 217 ++++++++++++++++++++++++-
1 file changed, 209 insertions(+), 8 deletions(-)
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index 2284015a6cb..b9042b3abfd 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,15 @@ 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();
_verticalOffsetAchieved = -ContentSize.Y;
- _deathTime = _timing.RealTime + TotalTime;
+ // Arcane-end
}
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
@@ -120,6 +145,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 +204,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 +400,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 +414,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
MaxWidth = SpeechMaxWidth,
};
- label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor));
+ SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor)); // Arcane
var panel = new PanelContainer
{
@@ -246,7 +444,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
MaxWidth = SpeechMaxWidth
};
- label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
+ SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane
var unfanciedPanel = new PanelContainer
{
@@ -273,7 +471,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
//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
//As for below: Some day this could probably be converted to xaml. But that is not today. -Myr
var mainPanel = new PanelContainer
@@ -282,8 +480,7 @@ 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)
+ Margin = new Thickness(4, 0, 4, 2) // Arcane
};
var headerPanel = new PanelContainer
@@ -295,10 +492,14 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
VerticalAlignment = VAlignment.Top
};
- var panel = new PanelContainer
+ // Arcane-start
+ var panel = new BoxContainer
{
- Children = { mainPanel, headerPanel }
+ Orientation = BoxContainer.LayoutOrientation.Vertical,
+ HorizontalAlignment = HAlignment.Center,
+ Children = { headerPanel, mainPanel }
};
+ // Arcane-end
return panel;
}
From 6e051f216dcbe1b88552525fc2dea4865e048e67 Mon Sep 17 00:00:00 2001
From: PuroSlavKing <103608145+PuroSlavKing@users.noreply.github.com>
Date: Sun, 4 May 2025 15:32:59 +0300
Subject: [PATCH 2/6] [Port] Doafter Cog (#1295)
Co-authored-by: Doctor-Cpu <77215380+Doctor-Cpu@users.noreply.github.com>
---
Content.Client/DoAfter/DoAfterOverlay.cs | 8 +++++++
.../Interface/Misc/progress_cog.rsi/cog.png | Bin 0 -> 663 bytes
.../Interface/Misc/progress_cog.rsi/meta.json | 22 ++++++++++++++++++
3 files changed, 30 insertions(+)
create mode 100644 Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/cog.png
create mode 100644 Resources/Textures/Backmen/Interface/Misc/progress_cog.rsi/meta.json
diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs
index 09473ea0cfc..42ebb93df92 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; // Backmen;
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"); // Backmen
_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);
+ // Backmen-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);
+ // Backmen-End
+
// Draw the underlying bar texture
handle.DrawTexture(_barTexture, position);
+ handle.DrawTexture(cogTexture, cogPos); // Backmen
Color color;
float elapsedRatio;
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 0000000000000000000000000000000000000000..4d098e2717b0f76c1802362424df47b0da2f2b65
GIT binary patch
literal 663
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|S$%
zr;B4q#hkaZ_If!7@-!&_5aa7}6933}r>3K6@#-BGJ1u~?WA)+-?FFA1mv|q%c<}iO
zU&Be#=eC}-RM(wR6Y%cs(n)jP|4Eqr#)jes5Y?eol#%neT1n$^wdG2{h6!*LZ9kpZvo0MtnFugZ2X9E?Y_7ftRGkk
zHt>HhfBpOQ?Au#KY96NBY&-byV2VM7-2cjNpMLXfKXmnJi0FM42el2{8Lbm@{>9i`
zPKaX^zx?@N&g9B@9oOo3Egst}>&ZW=!oc3g`6u<}Yl9=HYk$ruWeQ=cD462#AZvl-
zgYSDMHb1}JaPNAT`C5iKwIU78u6FyyGO9ekuYSHRo#p6scKN;Q9aR~oi=1ORYZsR}
zb0W_ZcaQR8kBT2|-FUWr!JYWH=4($bC~u2ucz$x<=^sCH1)3%^DOhMS;G&}b&8S=d
zeE<3U&)W~n?d816
zq!yM6%{u)!MDo$s%KQr536Fx0-tr2r+|yFNy5|k&g!5~K6T}qQ--in7DTXJkv_H7j
zvupaX;~(xZ%??{2T3qj-$+=HG+Ux2qnTqKg{b#G&wuF3TKcP}__uk?tpXTQuGlf#>
zuKfs&UJx?>`yc*-HTDT#rRH#(e!9vlRIzmCi^%!corEg>$XU(^J5c{d@CVygh6KYC
T@d;~yDUHF?)z4*}Q$iB}Z#X8K
literal 0
HcmV?d00001
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
+ ]
+ ]
+ }
+ ]
+}
From 1b32c32e843d2abe0c248d3cc5e1df1c0ddacc9f Mon Sep 17 00:00:00 2001
From: PuroSlavKing <103608145+PuroSlavKing@users.noreply.github.com>
Date: Wed, 6 Aug 2025 16:05:23 +0300
Subject: [PATCH 3/6] [Port] Combat Indicator (#30)
---
Content.Client/CombatMode/CombatModeSystem.cs | 52 +++++++++++++++++-
.../Options/UI/Tabs/AccessibilityTab.xaml | 1 +
.../Options/UI/Tabs/AccessibilityTab.xaml.cs | 1 +
.../CombatMode/SharedCombatModeSystem.cs | 22 ++++++--
.../_Europa/CCVar/CCVars.Accessibility.cs | 12 ++++
.../_europa/escape-menu/ui/options-menu.ftl | 1 +
.../_europa/escape-menu/ui/options-menu.ftl | 1 +
.../Effects/combat_mode.rsi/combat_mode.png | Bin 0 -> 710 bytes
.../_Europa/Effects/combat_mode.rsi/meta.json | 30 ++++++++++
9 files changed, 114 insertions(+), 6 deletions(-)
create mode 100644 Content.Shared/_Europa/CCVar/CCVars.Accessibility.cs
create mode 100644 Resources/Locale/en-US/_europa/escape-menu/ui/options-menu.ftl
create mode 100644 Resources/Locale/ru-RU/_europa/escape-menu/ui/options-menu.ftl
create mode 100644 Resources/Textures/_Europa/Effects/combat_mode.rsi/combat_mode.png
create mode 100644 Resources/Textures/_Europa/Effects/combat_mode.rsi/meta.json
diff --git a/Content.Client/CombatMode/CombatModeSystem.cs b/Content.Client/CombatMode/CombatModeSystem.cs
index a364f107aeb..de2d36cc658 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;
+using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared.Configuration;
+using Robust.Shared.Utility;
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/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..717561e1fda 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;
using Content.Shared.Mind;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Systems;
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/_Europa/Effects/combat_mode.rsi/combat_mode.png b/Resources/Textures/_Europa/Effects/combat_mode.rsi/combat_mode.png
new file mode 100644
index 0000000000000000000000000000000000000000..40c2444eb5085799873fe4481307aa3469020e7e
GIT binary patch
literal 710
zcmeAS@N?(olHy`uVBq!ia0vp^2|(Py!3HG1+{xJmq&N#aB8wRq_>O=u<5X=vX$A(S
z3Qrfukcv5P@9r&J?I7atu(eQ=OT)3#YC%lb0v4vLOldzqdp-U!Z|=jI#<$|JkMUDWPx|{e{N(v7hm;v-(p?ELFLbWQ5t?Yi>jyyK7i4cQtQv+uj@|J3#K{^q|DT*eG%7z~&X@Gy|d_{&hS
z?>FOznjHHD(?0(U{M7lz#P{nPc9+?wYgrQNg6DND`^G$NpZsTLOEG1q`^}&24*U``
zFst8R^Wsn1eDh`Ne=c0lkf8a)Y;A6_2&B8
zeb3riXY@Dkyz=wUpVQATU*z5)KViSg<&wCawq^DYsy;3J{PU;Fx1fi0PB9Hz?r&Kr
zBe(9*&pUsaAKhMZdQ1tar%$9Q}Ze&;L8~9sU)0`{bPy(NYFHiJ&t6
aGS!@YT-~~4!9rldWbkzLb6Mw<&;$UX;3~fW
literal 0
HcmV?d00001
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
+ ]
+ ]
+ }
+ ]
+}
From 79c38cf0ba5937e4cadb50179d007804844873f1 Mon Sep 17 00:00:00 2001
From: fox76055
Date: Wed, 5 Aug 2026 09:52:48 +0500
Subject: [PATCH 4/6] Update SpeechBubble.cs
---
Content.Client/Chat/UI/SpeechBubble.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index b9042b3abfd..7596df7d68f 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -465,7 +465,7 @@ 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),
StyleClasses = { "bubbleContent" },
};
From 7f029db74dca32ad78e1fdd50fd3366655d809fd Mon Sep 17 00:00:00 2001
From: fox76055
Date: Wed, 5 Aug 2026 16:17:39 +0500
Subject: [PATCH 5/6] request
---
Content.Client/Chat/UI/SpeechBubble.cs | 16 ++++++++++------
Content.Client/CombatMode/CombatModeSystem.cs | 6 +++---
Content.Client/DoAfter/DoAfterOverlay.cs | 10 +++++-----
.../CombatMode/SharedCombatModeSystem.cs | 4 ++--
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index 7596df7d68f..b2c58bb6436 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -127,8 +127,9 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt
_revealTime = GetRevealTime(_maxRevealWeight);
_deathTime = _creationTime + TotalTime + _revealTime;
UpdateTextReveal();
- _verticalOffsetAchieved = -ContentSize.Y;
// Arcane-end
+ _verticalOffsetAchieved = -ContentSize.Y;
+ // _deathTime = _timing.RealTime + TotalTime; Arcane delete
}
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
@@ -414,7 +415,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
MaxWidth = SpeechMaxWidth,
};
- SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor)); // Arcane
+ SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor)); // Arcane label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor)); -> SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor));
var panel = new PanelContainer
{
@@ -444,7 +445,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
MaxWidth = SpeechMaxWidth
};
- SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane
+ SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); -> SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
var unfanciedPanel = new PanelContainer
{
@@ -465,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, 2, 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));
- SetRevealedMessage(bubbleContent, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane
+ 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
@@ -480,7 +481,8 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
Children = { bubbleContent },
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)),
HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(4, 0, 4, 2) // Arcane
+ // 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
@@ -492,6 +494,8 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
VerticalAlignment = VAlignment.Top
};
+ //var panel = new PanelContainer Arcane delete
+
// Arcane-start
var panel = new BoxContainer
{
diff --git a/Content.Client/CombatMode/CombatModeSystem.cs b/Content.Client/CombatMode/CombatModeSystem.cs
index de2d36cc658..0a17a49f083 100644
--- a/Content.Client/CombatMode/CombatModeSystem.cs
+++ b/Content.Client/CombatMode/CombatModeSystem.cs
@@ -2,13 +2,13 @@
using Content.Client.NPC.HTN;
using Content.Shared.CCVar;
using Content.Shared.CombatMode;
-using Content.Shared.StatusIcon.Components;
-using Robust.Client.GameObjects;
+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;
+using Robust.Shared.Utility; // Europa
namespace Content.Client.CombatMode;
diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs
index 42ebb93df92..962d2237665 100644
--- a/Content.Client/DoAfter/DoAfterOverlay.cs
+++ b/Content.Client/DoAfter/DoAfterOverlay.cs
@@ -23,7 +23,7 @@ public sealed class DoAfterOverlay : Overlay
private readonly SharedContainerSystem _container;
private readonly Texture _barTexture;
- private readonly SpriteSpecifier _cogTexture; // Backmen;
+ private readonly SpriteSpecifier _cogTexture; // Europa
private readonly ShaderInstance _unshadedShader;
///
@@ -48,7 +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"); // Backmen
+ _cogTexture = new SpriteSpecifier.Rsi(new("/Textures/Backmen/Interface/Misc/progress_cog.rsi"), "cog"); // Europa
_unshadedShader = protoManager.Index("unshaded").Instance();
}
@@ -128,14 +128,14 @@ protected override void Draw(in OverlayDrawArgs args)
var position = new Vector2(-_barTexture.Width / 2f / EyeManager.PixelsPerMeter,
yOffset / scale + offset / EyeManager.PixelsPerMeter * scale);
- // Backmen-Start
+ // 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);
- // Backmen-End
+ // Europa-End
// Draw the underlying bar texture
handle.DrawTexture(_barTexture, position);
- handle.DrawTexture(cogTexture, cogPos); // Backmen
+ handle.DrawTexture(cogTexture, cogPos); // Europa
Color color;
float elapsedRatio;
diff --git a/Content.Shared/CombatMode/SharedCombatModeSystem.cs b/Content.Shared/CombatMode/SharedCombatModeSystem.cs
index 717561e1fda..95a3054d05e 100644
--- a/Content.Shared/CombatMode/SharedCombatModeSystem.cs
+++ b/Content.Shared/CombatMode/SharedCombatModeSystem.cs
@@ -1,8 +1,8 @@
using Content.Shared.Actions;
-using Content.Shared.Bed.Sleep;
+using Content.Shared.Bed.Sleep; // Europa
using Content.Shared.Mind;
using Content.Shared.Mobs;
-using Content.Shared.Mobs.Systems;
+using Content.Shared.Mobs.Systems; // Europa
using Content.Shared.MouseRotator;
using Content.Shared.Movement.Components;
using Content.Shared.Popups;
From 3c5b70d2ed3577504612185d6c3e3ed09ccedbf5 Mon Sep 17 00:00:00 2001
From: fox76055
Date: Wed, 5 Aug 2026 16:19:25 +0500
Subject: [PATCH 6/6] this better
---
Content.Client/Chat/UI/SpeechBubble.cs | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index b2c58bb6436..4372e5e80fb 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -494,16 +494,12 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl
VerticalAlignment = VAlignment.Top
};
- //var panel = new PanelContainer Arcane delete
-
- // Arcane-start
- var panel = new BoxContainer
+ var panel = new BoxContainer // Arcane var panel = new PanelContainer -> var panel = new BoxContainer
{
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- HorizontalAlignment = HAlignment.Center,
+ Orientation = BoxContainer.LayoutOrientation.Vertical, // Arcane
+ HorizontalAlignment = HAlignment.Center, // Arcane
Children = { headerPanel, mainPanel }
};
- // Arcane-end
return panel;
}