From 5d1298c875403f78752016fa3ebd832d2bf5cac7 Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 03:36:28 +0400 Subject: [PATCH 01/12] =?UTF-8?q?add(src):=20=D0=9E=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D0=B7=D0=B5=D1=80=D0=BA=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B8=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 182 ++++++++++++++++++ Content.Client/ADT/Mirror/MirrorSystem.cs | 15 ++ Content.Shared/ADT/Mirror/MirrorComponent.cs | 22 +++ .../ADT/Mirror/ReflectionComponent.cs | 8 + 4 files changed, 227 insertions(+) create mode 100644 Content.Client/ADT/Mirror/MirrorOverlay.cs create mode 100644 Content.Client/ADT/Mirror/MirrorSystem.cs create mode 100644 Content.Shared/ADT/Mirror/MirrorComponent.cs create mode 100644 Content.Shared/ADT/Mirror/ReflectionComponent.cs diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs new file mode 100644 index 00000000000..09f2a248636 --- /dev/null +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -0,0 +1,182 @@ +using System.Numerics; +using Content.Shared.ADT.Mirror; +using Content.Shared.Humanoid; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Graphics; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Prototypes; +using DrawDepth = Content.Shared.DrawDepth.DrawDepth; +using static Robust.Client.GameObjects.SpriteComponent; +using Content.Shared.Stealth.Components; + +namespace Content.Client.ADT.Mirror; + +public sealed partial class MirrorOverlay : Overlay +{ + private static readonly ProtoId StencilClearShader = "StencilClear"; + private static readonly ProtoId StencilMaskShader = "StencilMask"; + private static readonly ProtoId StencilEqualDrawShader = "StencilEqualDraw"; + + [Dependency] private IEntityManager _entityManager = default!; + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private IEyeManager _eyeMan = default!; + private SpriteSystem _sprite = default!; + private TransformSystem _transform = default!; + private ContainerSystem _container = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + + public MirrorOverlay() + { + IoCManager.InjectDependencies(this); + + ZIndex = (int)DrawDepth.BelowMobs; + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + _sprite ??= _entityManager.System(); + _transform ??= _entityManager.System(); + _container ??= _entityManager.System(); + + return base.BeforeDraw(args); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var eye = args.Viewport.Eye; + if (eye == null) + return; + + var mapId = args.MapId; + var worldAabb = args.WorldAABB; + + var mirrors = _entityManager.AllEntityQueryEnumerator(); + var mirrorData = new List<(MirrorComponent Component, Vector2 Position, Angle Rotation)>(); + while (mirrors.MoveNext(out var uid, out var component, out var sprite, out var transform)) + { + if (transform.MapID == mapId) + { + var position = _sprite.GetSpriteWorldPosition((uid, sprite, transform)); + var rotation = _transform.GetWorldRotation(transform) + sprite.Rotation; + mirrorData.Add((component, position, rotation)); + } + } + + if (mirrorData.Count == 0) + return; + + var worldHandle = args.WorldHandle; + + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_prototypeManager.Index(StencilClearShader).Instance()); + worldHandle.DrawRect(worldAabb, Color.White); + + // Сама отрисовка начинается тут + // Каждое зеркало делает свою маску и рисует сущности, которые может + var mirrorEntities = _entityManager.AllEntityQueryEnumerator(); + while (mirrorEntities.MoveNext(out var uid, out var mirror, out var sprite, out var transform)) + { + if (transform.MapID != mapId) + continue; + + worldHandle.UseShader(_prototypeManager.Index(StencilMaskShader).Instance()); + + _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, _transform.GetWorldRotation(transform), + _transform.GetWorldPosition(transform)); + + worldHandle.UseShader(_prototypeManager.Index(StencilEqualDrawShader).Instance()); + RenderEntities(worldAabb, eye, worldHandle, mapId, + (mirror, _sprite.GetSpriteWorldPosition((uid, sprite, transform)), + _transform.GetWorldRotation(transform) + sprite.Rotation)); + + worldHandle.UseShader(_prototypeManager.Index(StencilClearShader).Instance()); + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.DrawRect(worldAabb, Color.White); + } + + worldHandle.UseShader(null); + } + + private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHandle, MapId mapId, + (MirrorComponent Component, Vector2 Position, Angle Rotation) mirrorData) + { + var entities = _entityManager.AllEntityQueryEnumerator(); + while (entities.MoveNext(out var uid, out _, out var sprite, out var transform)) + { + if (_entityManager.HasComponent(uid) || transform.MapID != mapId) + continue; + + if (_entityManager.HasComponent(uid)) + continue; + + var (mirror, mirrorPosition, mirrorRotation) = mirrorData; + var sourcePosition = _transform.GetWorldPosition(transform); + if (!worldAabb.Contains(sourcePosition) || _container.IsEntityInContainer(uid)) + continue; + + var normalAngle = mirrorRotation + Angle.FromDegrees(mirror.DirRotation); + var normal = normalAngle.ToVec().Normalized(); + var entitySide = Vector2.Dot(sourcePosition - mirrorPosition, normal); + var viewerSide = Vector2.Dot(eye.Position.Position - mirrorPosition, normal); + if (entitySide * viewerSide <= 0f) + continue; + + if (mirror.FadeFactor > 0 && + Vector2.Distance(sourcePosition, mirrorPosition) >= mirror.GatherOffset + 1f / mirror.FadeFactor) + { + continue; + } + + var color = sprite.Color; + var newColor = GetTransparentColor(uid, color, mirrorPosition, mirror.ToleratedDistance, mirror.FadeFactor); + _sprite.SetColor(uid, newColor); + + var offsetSourcePosition = sourcePosition + normal * mirror.GatherOffset; + var reflectedPosition = offsetSourcePosition - 2f * Vector2.Dot(offsetSourcePosition - mirrorPosition, normal) * normal; + var reflectedFacing = normalAngle * 2f - _transform.GetWorldRotation(transform); + + // Этот ебучий слой ломал вообще всё + // Не убирайте этот фикс + var hiddenStencilLayers = new List<(ISpriteLayer Layer, bool Visible)>(); + if (_sprite.LayerMapTryGet((uid, sprite), HumanoidVisualLayers.StencilMask, out var stencilMaskLayer, false)) + { + var stencilMask = sprite[stencilMaskLayer]; + hiddenStencilLayers.Add((stencilMask, stencilMask.Visible)); + stencilMask.Visible = false; + + if (stencilMaskLayer > 0) + { + var stencilClear = sprite[stencilMaskLayer - 1]; + hiddenStencilLayers.Add((stencilClear, stencilClear.Visible)); + stencilClear.Visible = false; + } + } + + _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, reflectedFacing, + reflectedPosition - normal * mirror.ReflectionOffset); + + foreach (var (layer, visible) in hiddenStencilLayers) + layer.Visible = visible; + + worldHandle.UseShader(_prototypeManager.Index(StencilEqualDrawShader).Instance()); + _sprite.SetColor(uid, color); + } + } + + private Color GetTransparentColor(EntityUid uid, Color originalColor, Vector2 mirrorPos, float toleratedDistance, float fadeFactorMod) + { + var dist = (_transform.GetWorldPosition(uid) - mirrorPos).Length(); + + var fadeFactor = MathF.Max(dist - toleratedDistance, 0f); + return originalColor.WithAlpha(Math.Clamp(originalColor.A - fadeFactor * fadeFactorMod, 0f, 1f)); + } + + protected override void DisposeBehavior() + { + base.DisposeBehavior(); + } +} diff --git a/Content.Client/ADT/Mirror/MirrorSystem.cs b/Content.Client/ADT/Mirror/MirrorSystem.cs new file mode 100644 index 00000000000..efdd4370938 --- /dev/null +++ b/Content.Client/ADT/Mirror/MirrorSystem.cs @@ -0,0 +1,15 @@ +using Robust.Client.Graphics; + +namespace Content.Client.ADT.Mirror; + +public sealed partial class MirrorSystem : EntitySystem +{ + [Dependency] private IOverlayManager _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _overlay.AddOverlay(new MirrorOverlay()); + } +} diff --git a/Content.Shared/ADT/Mirror/MirrorComponent.cs b/Content.Shared/ADT/Mirror/MirrorComponent.cs new file mode 100644 index 00000000000..2b408957484 --- /dev/null +++ b/Content.Shared/ADT/Mirror/MirrorComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ADT.Mirror; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class MirrorComponent : Component +{ + [DataField, AutoNetworkedField] + public float DirRotation = 90f; + + [DataField, AutoNetworkedField] + public float GatherOffset = 1f; + + [DataField, AutoNetworkedField] + public float ReflectionOffset = 0.2f; + + [DataField, AutoNetworkedField] + public float FadeFactor = 1f; + + [DataField, AutoNetworkedField] + public float ToleratedDistance = 1f; +} diff --git a/Content.Shared/ADT/Mirror/ReflectionComponent.cs b/Content.Shared/ADT/Mirror/ReflectionComponent.cs new file mode 100644 index 00000000000..2bb5985f4bf --- /dev/null +++ b/Content.Shared/ADT/Mirror/ReflectionComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ADT.Mirror; + +[RegisterComponent, NetworkedComponent] +public sealed partial class MirrorReflectionComponent : Component +{ +} From bec51871c14530eb5906024b320ae702dd0b8092 Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 03:36:51 +0400 Subject: [PATCH 02/12] =?UTF-8?q?add(proto):=20=D0=9A=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B=20=D0=B2=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D1=82=D0=B8=D0=BF=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Resources/Prototypes/ADT/Body/Species/ipc.yml | 1 + Resources/Prototypes/Entities/Mobs/base.yml | 3 ++- .../Entities/Structures/Wallmounts/Misc/mirror.yml | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/ADT/Body/Species/ipc.yml b/Resources/Prototypes/ADT/Body/Species/ipc.yml index db837ecd8a4..d49ef904f9b 100644 --- a/Resources/Prototypes/ADT/Body/Species/ipc.yml +++ b/Resources/Prototypes/ADT/Body/Species/ipc.yml @@ -238,6 +238,7 @@ - type: AnimatedEmotes - type: Posing - type: SoftWalk + - type: MirrorReflection - type: entity parent: OrganBaseOrganic diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 15bd3b1a873..88e0f46390c 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -77,6 +77,7 @@ - type: MobMover - type: MovementSpeedModifier - type: LagCompensation + - type: MirrorReflection # ADT-Tweak - type: entity save: false @@ -299,4 +300,4 @@ # ADT-Tweak start bloodRefreshAmount: 0.3 bleedReductionAmount: 0.3 - # ADT-Tweak end \ No newline at end of file + # ADT-Tweak end diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml index a07661c4a06..3c93f851bf9 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml @@ -35,6 +35,11 @@ - !type:PlaySoundBehavior sound: collection: MetalGlassBreak + # ADT-Tweak-start + - type: Mirror + reflectionOffset: 0.3 + toleratedDistance: 1.2 + # ADT-Tweak-end - type: entity parent: Mirror @@ -43,3 +48,7 @@ components: - type: Sprite state: rectangle-mirror + # ADT-Tweak-start + - type: Mirror + reflectionOffset: 0.4 + # ADT-Tweak-end From 20e1e9ab58ed0b3a934af14ecf8a1174f4bf888b Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 03:52:57 +0400 Subject: [PATCH 03/12] =?UTF-8?q?fix(src):=20=D0=A4=D0=B8=D0=BA=D1=81=20?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BB=D0=B5=D1=8F,=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D1=8E=D1=89=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B2=D1=81=D0=B5=20=D1=81=D1=83=D1=89=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 09f2a248636..0adebb10e6f 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -27,7 +27,7 @@ public sealed partial class MirrorOverlay : Overlay private TransformSystem _transform = default!; private ContainerSystem _container = default!; - public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; public MirrorOverlay() { From 642a26dabc0cc1acb226f421cb2bbf86a918c2ae Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 03:53:28 +0400 Subject: [PATCH 04/12] =?UTF-8?q?tweak(src):=20=D0=9E=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D1=82=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=20=D0=BC=D0=B0=D0=BB=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B7=D1=80=D0=B0=D1=87=D0=BD=D1=8B=D0=BC=D0=B8,=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D1=8F=D1=82=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B2=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 0adebb10e6f..8856577c7d6 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -172,7 +172,7 @@ private Color GetTransparentColor(EntityUid uid, Color originalColor, Vector2 mi var dist = (_transform.GetWorldPosition(uid) - mirrorPos).Length(); var fadeFactor = MathF.Max(dist - toleratedDistance, 0f); - return originalColor.WithAlpha(Math.Clamp(originalColor.A - fadeFactor * fadeFactorMod, 0f, 1f)); + return originalColor.WithAlpha(Math.Clamp(originalColor.A - fadeFactor * fadeFactorMod, 0f, 0.9f)); } protected override void DisposeBehavior() From 9defef7e72f2952eee58f474afed38913f030ea6 Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 03:53:42 +0400 Subject: [PATCH 05/12] =?UTF-8?q?tweak(proto):=20=D0=9E=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BE=D0=B1=D1=8B?= =?UTF-8?q?=D1=87=D0=BD=D0=BE=D0=BC=20=D0=B7=D0=B5=D1=80=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=20=D1=81=D1=82=D0=B0=D0=BB=D0=BE=20=D1=87=D1=83=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BD=D0=B8=D0=B6=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml index 3c93f851bf9..48a3f4b5b45 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml @@ -37,7 +37,7 @@ collection: MetalGlassBreak # ADT-Tweak-start - type: Mirror - reflectionOffset: 0.3 + reflectionOffset: 0.35 toleratedDistance: 1.2 # ADT-Tweak-end From 351d8f61f321a82bdfdbe99cd09d253d6d803b7c Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 03:55:53 +0400 Subject: [PATCH 06/12] =?UTF-8?q?add(src):=20=D0=9E=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=B2=D0=B8=D0=B4=D0=B8=D0=BC=D1=8B=D1=85=20=D1=81=D1=83?= =?UTF-8?q?=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B5=D0=B9=20(=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B5=D1=80=D1=81=D0=BA=D0=BE=D0=B5,=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BA=D0=B0=20=D1=87=D1=82=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 4 ++-- Content.Shared/ADT/Mirror/ReflectionComponent.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 8856577c7d6..6388a255d96 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -105,12 +105,12 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa (MirrorComponent Component, Vector2 Position, Angle Rotation) mirrorData) { var entities = _entityManager.AllEntityQueryEnumerator(); - while (entities.MoveNext(out var uid, out _, out var sprite, out var transform)) + while (entities.MoveNext(out var uid, out var reflection, out var sprite, out var transform)) { if (_entityManager.HasComponent(uid) || transform.MapID != mapId) continue; - if (_entityManager.HasComponent(uid)) + if (!reflection.ReflectIfInvisible && _entityManager.HasComponent(uid)) continue; var (mirror, mirrorPosition, mirrorRotation) = mirrorData; diff --git a/Content.Shared/ADT/Mirror/ReflectionComponent.cs b/Content.Shared/ADT/Mirror/ReflectionComponent.cs index 2bb5985f4bf..78d91a7a8d5 100644 --- a/Content.Shared/ADT/Mirror/ReflectionComponent.cs +++ b/Content.Shared/ADT/Mirror/ReflectionComponent.cs @@ -2,7 +2,9 @@ namespace Content.Shared.ADT.Mirror; -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class MirrorReflectionComponent : Component { + [DataField, AutoNetworkedField] + public bool ReflectIfInvisible = false; } From ddaa3b0bb726040e628dfd055f2d0eea95c7d1dd Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 17:30:54 +0400 Subject: [PATCH 07/12] =?UTF-8?q?add(src):=20=D0=A0=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=20=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B8?= =?UTF-8?q?=D1=85/=D0=BD=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D1=85?= =?UTF-8?q?=20=D0=BE=D1=82=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ADT/Mirror/CanBeSeenInMirrorsEvent.cs | 7 +++++++ Content.Client/ADT/Mirror/MirrorOverlay.cs | 5 ++++- Content.Client/ADT/Mirror/MirrorSystem.cs | 15 +++++++++++++++ Content.Shared/ADT/Mirror/ReflectionComponent.cs | 3 +++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Content.Client/ADT/Mirror/CanBeSeenInMirrorsEvent.cs diff --git a/Content.Client/ADT/Mirror/CanBeSeenInMirrorsEvent.cs b/Content.Client/ADT/Mirror/CanBeSeenInMirrorsEvent.cs new file mode 100644 index 00000000000..97e67c8804b --- /dev/null +++ b/Content.Client/ADT/Mirror/CanBeSeenInMirrorsEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Client.ADT.Mirror; + +[ByRefEvent] +public record struct CanBeSeenInMirrorsEvent() +{ + public bool Cancelled = false; +} diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 6388a255d96..67e628f8650 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -110,7 +110,10 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa if (_entityManager.HasComponent(uid) || transform.MapID != mapId) continue; - if (!reflection.ReflectIfInvisible && _entityManager.HasComponent(uid)) + if (!reflection.Active) + continue; + + if (!reflection.ReflectIfInvisible && _entityManager.TryGetComponent(uid, out var stealth) && stealth.Enabled) continue; var (mirror, mirrorPosition, mirrorRotation) = mirrorData; diff --git a/Content.Client/ADT/Mirror/MirrorSystem.cs b/Content.Client/ADT/Mirror/MirrorSystem.cs index efdd4370938..b2c82ab3522 100644 --- a/Content.Client/ADT/Mirror/MirrorSystem.cs +++ b/Content.Client/ADT/Mirror/MirrorSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.ADT.Mirror; using Robust.Client.Graphics; namespace Content.Client.ADT.Mirror; @@ -12,4 +13,18 @@ public override void Initialize() _overlay.AddOverlay(new MirrorOverlay()); } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + var ev = new CanBeSeenInMirrorsEvent(); + RaiseLocalEvent(uid, ref ev); + + comp.Active = !ev.Cancelled; + } + } } diff --git a/Content.Shared/ADT/Mirror/ReflectionComponent.cs b/Content.Shared/ADT/Mirror/ReflectionComponent.cs index 78d91a7a8d5..219860c8f37 100644 --- a/Content.Shared/ADT/Mirror/ReflectionComponent.cs +++ b/Content.Shared/ADT/Mirror/ReflectionComponent.cs @@ -7,4 +7,7 @@ public sealed partial class MirrorReflectionComponent : Component { [DataField, AutoNetworkedField] public bool ReflectIfInvisible = false; + + [ViewVariables] + public bool Active = true; } From ce390b91102b92bb9cfa81a2922a82bf8c650f11 Mon Sep 17 00:00:00 2001 From: _kote Date: Mon, 7 Sep 2026 17:32:00 +0400 Subject: [PATCH 08/12] =?UTF-8?q?style(proto):=20=D0=B0=D0=BF=D0=B4=D0=B5?= =?UTF-8?q?=D0=B9=D1=82=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Resources/Prototypes/Entities/Mobs/base.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 88e0f46390c..2c8a51e8e3b 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -77,7 +77,7 @@ - type: MobMover - type: MovementSpeedModifier - type: LagCompensation - - type: MirrorReflection # ADT-Tweak + - type: MirrorReflection # ADT-Tweak: functional mirrors - type: entity save: false From 9a81d150d8617f5bce3b76866c3269237c1af95c Mon Sep 17 00:00:00 2001 From: _kote Date: Tue, 8 Sep 2026 02:37:33 +0400 Subject: [PATCH 09/12] =?UTF-8?q?refactor(src):=20=D0=92=D1=8B=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=20=D0=BA=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=83=D1=8E=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 61 +++++++++++++--------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 67e628f8650..9118c823d20 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -6,10 +6,8 @@ using Robust.Shared.Enums; using Robust.Shared.Graphics; using Robust.Shared.Map; -using Robust.Shared.Physics; using Robust.Shared.Prototypes; using DrawDepth = Content.Shared.DrawDepth.DrawDepth; -using static Robust.Client.GameObjects.SpriteComponent; using Content.Shared.Stealth.Components; namespace Content.Client.ADT.Mirror; @@ -107,41 +105,18 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa var entities = _entityManager.AllEntityQueryEnumerator(); while (entities.MoveNext(out var uid, out var reflection, out var sprite, out var transform)) { - if (_entityManager.HasComponent(uid) || transform.MapID != mapId) - continue; - - if (!reflection.Active) - continue; - - if (!reflection.ReflectIfInvisible && _entityManager.TryGetComponent(uid, out var stealth) && stealth.Enabled) - continue; - var (mirror, mirrorPosition, mirrorRotation) = mirrorData; var sourcePosition = _transform.GetWorldPosition(transform); - if (!worldAabb.Contains(sourcePosition) || _container.IsEntityInContainer(uid)) - continue; - var normalAngle = mirrorRotation + Angle.FromDegrees(mirror.DirRotation); var normal = normalAngle.ToVec().Normalized(); - var entitySide = Vector2.Dot(sourcePosition - mirrorPosition, normal); - var viewerSide = Vector2.Dot(eye.Position.Position - mirrorPosition, normal); - if (entitySide * viewerSide <= 0f) - continue; - if (mirror.FadeFactor > 0 && - Vector2.Distance(sourcePosition, mirrorPosition) >= mirror.GatherOffset + 1f / mirror.FadeFactor) - { + if (!CanReflect(uid, reflection, transform, mirrorData, normal, mapId, eye, worldAabb)) continue; - } var color = sprite.Color; var newColor = GetTransparentColor(uid, color, mirrorPosition, mirror.ToleratedDistance, mirror.FadeFactor); _sprite.SetColor(uid, newColor); - var offsetSourcePosition = sourcePosition + normal * mirror.GatherOffset; - var reflectedPosition = offsetSourcePosition - 2f * Vector2.Dot(offsetSourcePosition - mirrorPosition, normal) * normal; - var reflectedFacing = normalAngle * 2f - _transform.GetWorldRotation(transform); - // Этот ебучий слой ломал вообще всё // Не убирайте этот фикс var hiddenStencilLayers = new List<(ISpriteLayer Layer, bool Visible)>(); @@ -159,6 +134,10 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa } } + var offsetSourcePosition = sourcePosition + normal * mirror.GatherOffset; + var reflectedPosition = offsetSourcePosition - 2f * Vector2.Dot(offsetSourcePosition - mirrorPosition, normal) * normal; + var reflectedFacing = normalAngle * 2f - _transform.GetWorldRotation(transform); + _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, reflectedFacing, reflectedPosition - normal * mirror.ReflectionOffset); @@ -170,6 +149,36 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa } } + private bool CanReflect(EntityUid uid, MirrorReflectionComponent reflection, TransformComponent transform, + (MirrorComponent, Vector2, Angle) mirrorData, Vector2 normal, + MapId mapId, IEye eye, Box2 worldAabb) + { + if (_entityManager.HasComponent(uid) || transform.MapID != mapId) + return false; + + if (!reflection.Active) + return false; + + if (!reflection.ReflectIfInvisible && _entityManager.TryGetComponent(uid, out var stealth) && stealth.Enabled) + return false; + + var sourcePosition = _transform.GetWorldPosition(transform); + if (!worldAabb.Contains(sourcePosition) || _container.IsEntityInContainer(uid)) + return false; + + var (mirror, mirrorPosition, _) = mirrorData; + + var entitySide = Vector2.Dot(sourcePosition - mirrorPosition, normal); + var viewerSide = Vector2.Dot(eye.Position.Position - mirrorPosition, normal); + if (entitySide * viewerSide <= 0f) + return false; + + if (mirror.FadeFactor > 0 && Vector2.Distance(sourcePosition, mirrorPosition) >= mirror.GatherOffset + 1f / mirror.FadeFactor) + return false; + + return true; + } + private Color GetTransparentColor(EntityUid uid, Color originalColor, Vector2 mirrorPos, float toleratedDistance, float fadeFactorMod) { var dist = (_transform.GetWorldPosition(uid) - mirrorPos).Length(); From e201ad613a1e5dc395f4c782bcf2440e7710e104 Mon Sep 17 00:00:00 2001 From: _kote Date: Tue, 8 Sep 2026 03:35:13 +0400 Subject: [PATCH 10/12] =?UTF-8?q?refactor(src):=20=D0=9E=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=8B=D0=B3=D0=BB=D1=8F?= =?UTF-8?q?=D0=B4=D0=B8=D1=82=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20"=D0=BE?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=B6=D1=91=D0=BD=D0=BD=D1=8B=D0=BC"=20(?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D1=80=D0=B0=D1=87=D0=B8?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=20=D1=81=D0=BF=D1=80=D0=B0=D0=B9=D1=82?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 9118c823d20..17ad856829f 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -138,8 +138,27 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa var reflectedPosition = offsetSourcePosition - 2f * Vector2.Dot(offsetSourcePosition - mirrorPosition, normal) * normal; var reflectedFacing = normalAngle * 2f - _transform.GetWorldRotation(transform); + var dir = (reflectedFacing + eye.Rotation).GetCardinalDir(); + var scale = sprite.Scale; + + switch (dir) + { + case Direction.West: + dir = Direction.East; + break; + case Direction.East: + dir = Direction.West; + break; + default: + break; + } + + _sprite.SetScale(uid, new Vector2(-scale.X, scale.Y)); + _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, reflectedFacing, - reflectedPosition - normal * mirror.ReflectionOffset); + reflectedPosition - normal * mirror.ReflectionOffset, dir); + + _sprite.SetScale(uid, scale); foreach (var (layer, visible) in hiddenStencilLayers) layer.Visible = visible; @@ -149,6 +168,11 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa } } + private void RenderEntity() + { + + } + private bool CanReflect(EntityUid uid, MirrorReflectionComponent reflection, TransformComponent transform, (MirrorComponent, Vector2, Angle) mirrorData, Vector2 normal, MapId mapId, IEye eye, Box2 worldAabb) From 405543b6442cc38b8942c4d17aed8cc344c0ff9f Mon Sep 17 00:00:00 2001 From: _kote Date: Tue, 8 Sep 2026 05:40:13 +0400 Subject: [PATCH 11/12] =?UTF-8?q?stryle(src):=20=D0=A3=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D1=91=D0=BD=20=D0=BF=D1=83=D1=81=D1=82=D0=BE=D0=B9=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 17ad856829f..98a0b5acdbd 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -168,11 +168,6 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa } } - private void RenderEntity() - { - - } - private bool CanReflect(EntityUid uid, MirrorReflectionComponent reflection, TransformComponent transform, (MirrorComponent, Vector2, Angle) mirrorData, Vector2 normal, MapId mapId, IEye eye, Box2 worldAabb) From 3c5e578eaa062f9ead2876dc413725ca2bf6c9f4 Mon Sep 17 00:00:00 2001 From: _kote Date: Thu, 10 Sep 2026 20:39:58 +0400 Subject: [PATCH 12/12] =?UTF-8?q?fix(src):=20=D0=A4=D0=B8=D0=BA=D1=81=20?= =?UTF-8?q?=D0=B7=D0=B5=D1=80=D0=BA=D0=B0=D0=BB=D0=B0=20"=D1=81=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D1=83"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/ADT/Mirror/MirrorOverlay.cs | 45 ++++++++++++++-------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/Content.Client/ADT/Mirror/MirrorOverlay.cs b/Content.Client/ADT/Mirror/MirrorOverlay.cs index 98a0b5acdbd..af1ff00ae2e 100644 --- a/Content.Client/ADT/Mirror/MirrorOverlay.cs +++ b/Content.Client/ADT/Mirror/MirrorOverlay.cs @@ -136,29 +136,42 @@ private void RenderEntities(Box2 worldAabb, IEye eye, DrawingHandleWorld worldHa var offsetSourcePosition = sourcePosition + normal * mirror.GatherOffset; var reflectedPosition = offsetSourcePosition - 2f * Vector2.Dot(offsetSourcePosition - mirrorPosition, normal) * normal; - var reflectedFacing = normalAngle * 2f - _transform.GetWorldRotation(transform); - var dir = (reflectedFacing + eye.Rotation).GetCardinalDir(); var scale = sprite.Scale; - switch (dir) + if ((normalAngle + eye.Rotation + mirror.DirRotation).GetCardinalDir() is not Direction.South) { - case Direction.West: - dir = Direction.East; - break; - case Direction.East: - dir = Direction.West; - break; - default: - break; - } + var reflectedFacing = normalAngle * 2f - _transform.GetWorldRotation(transform); + var dir = (reflectedFacing + eye.Rotation).GetCardinalDir(); - _sprite.SetScale(uid, new Vector2(-scale.X, scale.Y)); + switch (dir) + { + case Direction.West: + dir = Direction.East; + break; + case Direction.East: + dir = Direction.West; + break; + default: + break; + } - _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, reflectedFacing, - reflectedPosition - normal * mirror.ReflectionOffset, dir); + _sprite.SetScale(uid, new Vector2(-scale.X, scale.Y)); - _sprite.SetScale(uid, scale); + _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, reflectedFacing, + reflectedPosition - normal * mirror.ReflectionOffset, dir); + + _sprite.SetScale(uid, scale); + } + else + { + _sprite.SetScale(uid, new Vector2(scale.X, -scale.Y)); + + _sprite.RenderSprite((uid, sprite), worldHandle, eye.Rotation, _transform.GetWorldRotation(transform), + reflectedPosition - normal * mirror.ReflectionOffset); + + _sprite.SetScale(uid, scale); + } foreach (var (layer, visible) in hiddenStencilLayers) layer.Visible = visible;