From 0bf126da3436591a10061c302c68eb80b1e7f826 Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Sun, 12 Jul 2026 00:10:28 +0300 Subject: [PATCH 01/11] Reflector_V1 --- .../Components/DirectionalReflectComponent.cs | 33 +++++ .../Supermatter/DirectionalReflectSystem.cs | 123 ++++++++++++++++++ .../en-US/backmen/supermatter/supermatter.ftl | 11 ++ .../entities/supermatter/reflector.ftl | 5 + .../ru-RU/backmen/supermatter/supermatter.ftl | 11 ++ .../entities/supermatter/reflector.ftl | 5 + .../Entities/Supermatter/reflector.yml | 93 +++++++++++++ .../Recipes/Construction/reflector.yml | 65 +++++++++ .../Supermatter/reflector.rsi/meta.json | 15 +++ .../Supermatter/reflector.rsi/reflector.png | Bin 0 -> 1534 bytes 10 files changed, 361 insertions(+) create mode 100644 Content.Shared/Backmen/Supermatter/Components/DirectionalReflectComponent.cs create mode 100644 Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs create mode 100644 Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl create mode 100644 Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl create mode 100644 Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml create mode 100644 Resources/Prototypes/_Backmen/Recipes/Construction/reflector.yml create mode 100644 Resources/Textures/Backmen/Supermatter/reflector.rsi/meta.json create mode 100644 Resources/Textures/Backmen/Supermatter/reflector.rsi/reflector.png diff --git a/Content.Shared/Backmen/Supermatter/Components/DirectionalReflectComponent.cs b/Content.Shared/Backmen/Supermatter/Components/DirectionalReflectComponent.cs new file mode 100644 index 00000000000..458da1b04cc --- /dev/null +++ b/Content.Shared/Backmen/Supermatter/Components/DirectionalReflectComponent.cs @@ -0,0 +1,33 @@ +using Content.Shared.Weapons.Reflect; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Backmen.Supermatter.Components; + +/// +/// Redirects projectiles out through the facing side. +/// Shots from the back or sides are turned toward the output face; only direct hits into the output face are absorbed. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class DirectionalReflectComponent : Component +{ + /// + /// Projectile reflective types that this reflector accepts. + /// + [DataField] + public ReflectType Reflects = ReflectType.Energy; + + /// + /// Angular half-width of the output face that absorbs shots instead of redirecting them. + /// + [DataField] + public Angle FrontAbsorbAngle = Angle.FromDegrees(45); + + /// + /// Sound played when a projectile is redirected. + /// + [DataField] + public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier( + "/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg", + AudioParams.Default.WithVariation(0.05f)); +} diff --git a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs new file mode 100644 index 00000000000..c339eb9deac --- /dev/null +++ b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs @@ -0,0 +1,123 @@ +using System.Numerics; +using Content.Shared.Administration.Logs; +using Content.Shared.Backmen.Supermatter.Components; +using Content.Shared.Database; +using Content.Shared.Popups; +using Content.Shared.Projectiles; +using Content.Shared.Tag; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Reflect; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; + +namespace Content.Shared.Backmen.Supermatter; + +/// +/// Handles directional reflection of emitter bolts and other reflective projectiles. +/// +public sealed partial class DirectionalReflectSystem : EntitySystem +{ + [Dependency] private INetManager _net = default!; + [Dependency] private SharedPhysicsSystem _physics = default!; + [Dependency] private SharedTransformSystem _transform = default!; + [Dependency] private SharedAudioSystem _audio = default!; + [Dependency] private SharedPopupSystem _popup = default!; + [Dependency] private ISharedAdminLogManager _adminLogger = default!; + [Dependency] private TagSystem _tags = default!; + + public const string EmitterBoltTag = "EmitterBolt"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnProjectileReflectAttempt); + } + + private void OnProjectileReflectAttempt( + Entity ent, + ref ProjectileReflectAttemptEvent args) + { + if (args.Cancelled) + return; + + if (!TryReflectProjectile(ent, args.ProjUid)) + return; + + args.Cancelled = true; + } + + private bool TryReflectProjectile(Entity reflector, EntityUid projectileUid) + { + if (!CanReflect(projectileUid, reflector.Comp)) + return false; + + if (!TryComp(projectileUid, out var physics)) + return false; + + var velocity = _physics.GetMapLinearVelocity(projectileUid, component: physics); + if (velocity.LengthSquared() < 0.001f) + return false; + + var outputAngle = _transform.GetWorldRotation(reflector); + var incomingFrom = (-velocity).ToWorldAngle(); + var fromOutputFace = Math.Abs(Angle.ShortestDistance(outputAngle, incomingFrom).Degrees); + + // Only shots fired into the output face are absorbed. + if (fromOutputFace < reflector.Comp.FrontAbsorbAngle.Degrees) + return false; + + // Shots from the back or sides are redirected out through the output face. + var speed = velocity.Length(); + var newVelocity = outputAngle.ToWorldVec() * speed; + var difference = newVelocity - velocity; + + _physics.SetLinearVelocity(projectileUid, physics.LinearVelocity + difference, body: physics); + _transform.SetLocalRotation(projectileUid, outputAngle); + + PlayAudioAndPopup(reflector.Comp, reflector); + + if (TryComp(projectileUid, out var projectile)) + { + _adminLogger.Add( + LogType.BulletHit, + LogImpact.Medium, + $"{ToPrettyString(reflector)} directionally reflected {ToPrettyString(projectileUid)} from {ToPrettyString(projectile.Weapon)} shot by {projectile.Shooter}"); + + projectile.Shooter = reflector; + projectile.Weapon = reflector; + Dirty(projectileUid, projectile); + } + else + { + _adminLogger.Add( + LogType.BulletHit, + LogImpact.Medium, + $"{ToPrettyString(reflector)} directionally reflected {ToPrettyString(projectileUid)}"); + } + + return true; + } + + private bool CanReflect(EntityUid projectile, DirectionalReflectComponent comp) + { + if (_tags.HasTag(projectile, EmitterBoltTag)) + return (comp.Reflects & ReflectType.Energy) != 0; + + if (!TryComp(projectile, out var reflective)) + return false; + + return (comp.Reflects & reflective.Reflective) != 0; + } + + private void PlayAudioAndPopup(DirectionalReflectComponent comp, EntityUid reflector) + { + if (!_net.IsServer) + return; + + _popup.PopupEntity(Loc.GetString("directional-reflect-shot"), reflector); + _audio.PlayPvs(comp.SoundOnReflect, reflector); + } +} diff --git a/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl b/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl index 1a30affefbc..faa09ab0055 100644 --- a/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl +++ b/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl @@ -7,3 +7,14 @@ supermatter-safe-alert = Crystalline hyperstructure returning to safe operating supermatter-delamination-overmass = The Supermatter has Reached Critical Mass Falure. Singularity formation Imminent supermatter-delamination-default = The Supermatter has Reached Critical Integrity Falure. Emergency Causality Destabilization Field has been Activated. supermatter-seconds-before-delam = { $Seconds } Seconds Remain Before Delamination. + +directional-reflect-shot = The shot ricochets off the reflector! + +ent-Reflector = reflector + .desc = An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. + +ent-ReflectorFrame = reflector frame + .desc = An unfinished reflector assembly. Anchor it, then add a diamond and reinforced glass. + +construction-reflector = reflector + .desc = An angled mirror for redirecting emitter bolts. Requires 15 steel, 1 diamond, and 5 reinforced glass. diff --git a/Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl b/Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl new file mode 100644 index 00000000000..183d250c3bc --- /dev/null +++ b/Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl @@ -0,0 +1,5 @@ +ent-Reflector = reflector + .desc = An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. + +ent-ReflectorFrame = reflector frame + .desc = An unfinished reflector assembly. Anchor it, then add a diamond and reinforced glass. diff --git a/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl b/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl index 7201b9416b9..2399b247b33 100644 --- a/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl +++ b/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl @@ -16,3 +16,14 @@ supermatter-delam-tesla = ДЕЛАМИНАЦИЯ КРИСТАЛЛА НЕИЗБЕ supermatter-delam-cascade = ДЕЛАМИНАЦИЯ КРИСТАЛЛА НЕИЗБЕЖНА! Превышены пределы гармонической частоты, поле дестабилизации причинности не может быть активировано! supermatter-delam-cancel = Гиперструктура кристалла возвращается к безопасным рабочим параметрам. Предохранитель деактивирован. Целостность: { $integrity }%. supermatter-seconds-before-delam = Ожидаемое время до деламинации: { $seconds } секунд. + +directional-reflect-shot = Выстрел рикошетит от отражателя! + +ent-Reflector = отражатель + .desc = Угловое зеркало, перенаправляющее болты эмиттера с тыла и боков через лицевую сторону. + +ent-ReflectorFrame = каркас отражателя + .desc = Незавершённая сборка отражателя. Закрепите на полу, затем добавьте алмаз и укреплённое стекло. + +construction-reflector = отражатель + .desc = Угловое зеркало для перенаправления болтов эмиттера. Требуется 15 стали, 1 алмаз и 5 листов укреплённого стекла. diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl new file mode 100644 index 00000000000..2d747996e3f --- /dev/null +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl @@ -0,0 +1,5 @@ +ent-Reflector = отражатель + .desc = Угловое зеркало, перенаправляющее болты эмиттера с тыла и боков через лицевую сторону. + +ent-ReflectorFrame = каркас отражателя + .desc = Незавершённая сборка отражателя. Закрепите на полу, затем добавьте алмаз и укреплённое стекло. diff --git a/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml b/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml new file mode 100644 index 00000000000..3a5776c6c60 --- /dev/null +++ b/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml @@ -0,0 +1,93 @@ +- type: entity + id: ReflectorFrame + name: reflector frame + description: An unfinished reflector assembly. Anchor it, then add a diamond and reinforced glass. + parent: BaseStructureDynamic + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: Backmen/Supermatter/reflector.rsi + state: reflector + - type: InteractionOutline + - type: Rotatable + - type: Construction + graph: Reflector + node: frame + - type: Damageable + damageModifierSet: StructuralMetallic + - type: Injurable + damageContainer: StructuralInorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 150 + behaviors: + - !type:SpawnEntitiesBehavior + spawn: + SheetSteel1: + min: 10 + max: 15 + - !type:DoActsBehavior + acts: [ "Destruction" ] + +- type: entity + id: Reflector + name: reflector + description: An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. + parent: BaseStructure + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: Backmen/Supermatter/reflector.rsi + state: reflector + - type: InteractionOutline + - type: Rotatable + - type: Anchorable + - type: Appearance + - type: Construction + graph: Reflector + node: reflector + - type: DirectionalReflect + reflects: Energy + frontAbsorbAngle: 45 + - type: Damageable + damageModifierSet: StructuralMetallicStrong + - type: Injurable + damageContainer: StructuralInorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 300 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: MetalBreak + - !type:SpawnEntitiesBehavior + spawn: + SheetSteel1: + min: 10 + max: 15 + MaterialDiamond1: + min: 1 + max: 1 + SheetRGlass1: + min: 3 + max: 5 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.49,-0.49,0.49,0.49" + hard: true + density: 1000 + mask: + - MachineMask + layer: + - MachineLayer diff --git a/Resources/Prototypes/_Backmen/Recipes/Construction/reflector.yml b/Resources/Prototypes/_Backmen/Recipes/Construction/reflector.yml new file mode 100644 index 00000000000..f3dc9c7a587 --- /dev/null +++ b/Resources/Prototypes/_Backmen/Recipes/Construction/reflector.yml @@ -0,0 +1,65 @@ +- type: constructionGraph + id: Reflector + start: start + graph: + - node: start + edges: + - to: frame + completed: + - !type:SnapToGrid + southRotation: true + steps: + - material: Steel + amount: 15 + doAfter: 10 + + - node: frame + entity: ReflectorFrame + edges: + - to: reflector + conditions: + - !type:EntityAnchored + steps: + - material: Diamond + amount: 1 + doAfter: 3 + - material: ReinforcedGlass + amount: 5 + doAfter: 5 + - to: start + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 15 + - !type:DeleteEntity + steps: + - tool: Welding + doAfter: 5 + + - node: reflector + entity: Reflector + edges: + - to: frame + completed: + - !type:SpawnPrototype + prototype: MaterialDiamond1 + amount: 1 + - !type:SpawnPrototype + prototype: SheetRGlass1 + amount: 5 + steps: + - tool: Welding + doAfter: 8 + +- type: construction + id: Reflector + graph: Reflector + startNode: start + targetNode: reflector + category: construction-category-structures + objectType: Structure + placementMode: SnapgridCenter + canRotate: true + canBuildInImpassable: false + conditions: + - !type:TileNotBlocked diff --git a/Resources/Textures/Backmen/Supermatter/reflector.rsi/meta.json b/Resources/Textures/Backmen/Supermatter/reflector.rsi/meta.json new file mode 100644 index 00000000000..af3ad32623a --- /dev/null +++ b/Resources/Textures/Backmen/Supermatter/reflector.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "gttedmi and kesfox15", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "reflector", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Backmen/Supermatter/reflector.rsi/reflector.png b/Resources/Textures/Backmen/Supermatter/reflector.rsi/reflector.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e8556aff314cd212a53629c3d87bf20889cdbb GIT binary patch literal 1534 zcmVPx)xJg7oRCt{2TTO2pMHqhU#KBHKg4cp3ZKXg65+CB)NNq155C^16IS|Pb94a_e zDkt~>h#vqaZor93mV!{FN(e=Qa!Dd3mt6Qr#Ya_I+p*ov#&x~BwPP;_GTZ%_&)v0+ zgdfRLJf3-Qo_8dmjI#hkLkje3n}#%bykrAqG?&LFz{BJdfYo z(Up$wmftu&i%Sppl=+!6fQFThj){p0Zk=AQr(#kGD;*sZxh#skgMSN-w7B1MS>+k4 zavmH7h*zfwhEU(u#?`YwmMY^bsiCyjV!+mRQRQL0Wa3_XQ}0H z8uyq)adrU~pBL#&CRFxD{kD*wy97uiEjiP^^4IFOar5bCLuG4$vSP{v08}a!VF!5p zPOpztnm<|l-7d2|kDuv)mZ8IKHn-^eE!cThSseKIPc6Xd_qp{&4q`zn2s)ODRBl?L zrXR%fEhAnl93ND7h&zu3(dw;uscglAF z08?k?xyHOA$mn?Os9;$9N{1mL#0!=v+M11vLGUX%Ag-uEATB@C0kyj|06_j!9xv6y zBwUdCb7x#Tsu&i3v?rw#F`#-kS`?QR zFlv6L1DXd$EaJF!hQ-fxfMvB45fIhRu=uGC$ek?UpxJi+j)N$6hRN?cl`TH^V(2tB z2>kKqZ`9M^DEJ4u4URF!7-Nhv#(x{aeBIaI{p3J0KL<~KP&F4gDhcScbm8-MK6ctZWE=p5=OqsZz znMu^<^`p)Y0*O0-%zz}Z+-)k2stTl*0P(&^1ev0x0nPwv4U!JyM#;~N%%uYb;1TQ+tq{Kz~!E?a*3m37uu`VT&CS-eS3+I-Wq}sD^ZfObI zTU%0mG;D6H1ZOXUM=JD)hoE)h?W*3xZkL` z8iE`IG+ix2cQud%3ky=a01Grnc(0`-0b(b3(~EU$gw~b^rUOVqL1rDXX2pYMyf_eS z&*Gth=oTeLm=3V4wrfOnG)X(tY$>%1g0zf+t~vx(moYYY!5`(m_rap`pU ke+4He=~va0#4t4c1CD_C8t-uNi~s-t07*qoM6N<$f<&;&00000 literal 0 HcmV?d00001 From 0faa5293bafbea2cbbc9ea02f6bfc202d21e993e Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Sun, 12 Jul 2026 02:42:19 +0300 Subject: [PATCH 02/11] Reflector_minorfix --- .../Locale/ru-RU/backmen/supermatter/supermatter.ftl | 11 ----------- .../_backmen/entities/supermatter/reflector.ftl | 7 ++++++- .../_Backmen/Entities/Supermatter/reflector.yml | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl b/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl index 2399b247b33..7201b9416b9 100644 --- a/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl +++ b/Resources/Locale/ru-RU/backmen/supermatter/supermatter.ftl @@ -16,14 +16,3 @@ supermatter-delam-tesla = ДЕЛАМИНАЦИЯ КРИСТАЛЛА НЕИЗБЕ supermatter-delam-cascade = ДЕЛАМИНАЦИЯ КРИСТАЛЛА НЕИЗБЕЖНА! Превышены пределы гармонической частоты, поле дестабилизации причинности не может быть активировано! supermatter-delam-cancel = Гиперструктура кристалла возвращается к безопасным рабочим параметрам. Предохранитель деактивирован. Целостность: { $integrity }%. supermatter-seconds-before-delam = Ожидаемое время до деламинации: { $seconds } секунд. - -directional-reflect-shot = Выстрел рикошетит от отражателя! - -ent-Reflector = отражатель - .desc = Угловое зеркало, перенаправляющее болты эмиттера с тыла и боков через лицевую сторону. - -ent-ReflectorFrame = каркас отражателя - .desc = Незавершённая сборка отражателя. Закрепите на полу, затем добавьте алмаз и укреплённое стекло. - -construction-reflector = отражатель - .desc = Угловое зеркало для перенаправления болтов эмиттера. Требуется 15 стали, 1 алмаз и 5 листов укреплённого стекла. diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl index 2d747996e3f..e2e0dc85c89 100644 --- a/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl @@ -1,5 +1,10 @@ +directional-reflect-shot = Выстрел рикошетит от отражателя! + ent-Reflector = отражатель - .desc = Угловое зеркало, перенаправляющее болты эмиттера с тыла и боков через лицевую сторону. + .desc = Угловое зеркало, перенаправляющее болты эмиттера с тыла и боков через лицевую сторону. Говорят что первые версии были сделаны из холодильника СССП. ent-ReflectorFrame = каркас отражателя .desc = Незавершённая сборка отражателя. Закрепите на полу, затем добавьте алмаз и укреплённое стекло. + +construction-reflector = отражатель + .desc = Угловое зеркало для перенаправления болтов эмиттера. Требуется 15 стали, 1 алмаз и 5 листов укреплённого стекла. diff --git a/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml b/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml index 3a5776c6c60..024887ac6dd 100644 --- a/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml +++ b/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml @@ -35,7 +35,7 @@ - type: entity id: Reflector name: reflector - description: An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. + description: An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. It is said that the first versions were made from a CSSP refrigerator. parent: BaseStructure placement: mode: SnapgridCenter From 5d7e83573b999c917dc2df9bb8f4dab2fd4c2bca Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Sun, 12 Jul 2026 08:12:45 +0300 Subject: [PATCH 03/11] fix ?? --- Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs index c339eb9deac..dcc5cea43b0 100644 --- a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs +++ b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs @@ -75,7 +75,7 @@ private bool TryReflectProjectile(Entity reflector, var difference = newVelocity - velocity; _physics.SetLinearVelocity(projectileUid, physics.LinearVelocity + difference, body: physics); - _transform.SetLocalRotation(projectileUid, outputAngle); + _transform.SetWorldRotation(projectileUid, outputAngle); PlayAudioAndPopup(reflector.Comp, reflector); From a0a329df8000ee77693dfb69476b63205e7f87db Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Sun, 12 Jul 2026 09:47:33 +0300 Subject: [PATCH 04/11] Reflector Rsi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавление текстуры каркасу, небольшое изменение текстуры отражателя --- .../Entities/Supermatter/reflector.yml | 2 +- .../Supermatter/reflector.rsi/reflector.png | Bin 1534 -> 1544 bytes .../Supermatter/reflectorframe.rsi/meta.json | 15 +++++++++++++++ .../reflectorframe.rsi/reflector.png | Bin 0 -> 1346 bytes 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/meta.json create mode 100644 Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/reflector.png diff --git a/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml b/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml index 024887ac6dd..6698f381383 100644 --- a/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml +++ b/Resources/Prototypes/_Backmen/Entities/Supermatter/reflector.yml @@ -7,7 +7,7 @@ mode: SnapgridCenter components: - type: Sprite - sprite: Backmen/Supermatter/reflector.rsi + sprite: Backmen/Supermatter/reflectorframe.rsi state: reflector - type: InteractionOutline - type: Rotatable diff --git a/Resources/Textures/Backmen/Supermatter/reflector.rsi/reflector.png b/Resources/Textures/Backmen/Supermatter/reflector.rsi/reflector.png index e9e8556aff314cd212a53629c3d87bf20889cdbb..c64fd1d0c51673e4549b96fef74e1814d037d58e 100644 GIT binary patch delta 1513 zcmVPfFz31GSIoH4lBmA$CqBVZ?{eO>6Hk)<7|1|xglF>JJ z{`7r+|DD&VJS+^7f1K91`rK;(fTgakL`nDu8lf)9=~^>OI_VU3D*Bl%i$eL5eQdh@VE{o#9 z-lK*?E$;VRHhNlnMnIS+jX~~)NwZBoniR;fkA8K0( zv=vhw06?Wu5e|UI?;P}yN^=(~dbZ8%FXE>JPaC$xN z^&$uHAWVXe*+D8dDY`5K;`x>lGZ#FtfT!r;NI-K?r6$KOwF@|Ahxyw+fJ_7jDdB;| zeVs;;8?x@`!K#Tlte=ecpbDKSxR(%uY*+iT?f2d{J;tR*fMnB$TI-o z3L)SM@_#J7dbRAc{axuBYDOLwzb|`iZ{NVy=DN>dZ{J35Kb&Q4+xC5U*xFpj_Vx|# zHN)bkhQRFHJWSIJ3GpI5?w;By>-vR(koSNkt@`@m{1TgsNJps0P-jDc%>dD;euR0bIP@&ib3*6zlj#<;3Tj?K$f4n0(SKV0O0puw_=Hy zo)JIF4x68w1FE;9WpNb&!{(0a5J?l0W2t-0=eTnr-** zIDd#@XVCoalhOPOFNdDS`hmax{)757I4u6YZG$6>Fv18UjPT?`ShM@~hhH2>*684= z5vtY$howPyAFz@?4NcSBS|1O^R0o=-Neh)lKQ&0b5h^*8D5g460!q##7VRdkNYTq- z(QcxYnNrdqi^T5)i>VH-W~LIgdG&Dc{eM8>4j?NaNi5n;rCC*hR0t67i$stmTI%Bp zkk%mSFm9Io;_EBUv3vp9Y*sk~|FiuYKKa8g_fb?;}75C zYT$+dd9kQz8XU6|`p}?h8Z65~b8lBltO(7$U09aop7;oYri)xF^J0`50^VoymVcoG zCr>iT0Wc3x5Id}Q06d7^5Ae<`z*8L-X<+66?^~2gr2_k~Ozj0JLi)r^J|@o{FTlJP z{e%e7Kqi4UXj)~sQfc^X@HB8kz^L(GD$+{R;w-x$&|%K`oVL+*>mTy zzP2hQk|tzxV*_Uv7Nq*KaAsivn|~V{QhYS5udU+jx%1p>m=4(08@PD+66)15T817f zN7G8uL|DCA#>LB*aJLbyG)8c@QFAo}IS6RFT88dwAVn4yq;>%oXpZn6O-Ta8PVlrB z>*NTnFAq!ykfMT29I;l#y=J^T5bV$5p@HZY6-Ss3FwM4WM0GYvzfPy6)PFAs(qCp0 zydZjwnjtKXFdaaO3ZfX|k$22^hvquwe&k6Yy2&_LklqEl` zjWQaTH$sFo7(+oP@J?i?t{&C?ya^$?MK)p`kzt8Qs=6DrC%Xbwz;pm1B}qj2s7eBk z2=-UeKy-^Va96<7Q%3**YhpiNmnQc1ja%k6&9-YNss<654yf+bl?juED1#?qgSrdI z-fQ<0gVI1<0o|?@>oGP73`zqt1lsM#cX8=-`2PjR$LYt_lf;vt;XjXu>Hzr~`>VR@ P00000NkvXXu0mjfd$@6YmRlyvpB4!r z22>nD>PGE6kKfzTm5%O~-#9*tOAq&y`I$3-hLw(viHQkronEh}Vp0h!9UT+7EQ-B@ ze+!PZxZiVG<$oEgavmH7h*zfwhEU(u#?`YwmMY^bsiCyjV!+m zRQRQL0Wa3_XQ}0H8uyq)adrU~pBL#&CRFxD{kD*wy97uiEjiP^^4IFOar5bCLuG4$ zvSP{v08}a!VF!5pPOpztnm<|l-7d2|kDuv)mZ8IKHh;J1`z_dcR#_bQ_)jgs>G!$y zMGj&?DhN83iBxV{qNX3j^DQH0EO=xCPtcbm0nI^`nwVdD7jP_-`P(6YOauoh;gQYd zpAIzr5qT8+p~$hjdkZ_;o4&ICVHf?waFp$KyS@((JKLMs-Mz)F zGYWp@5}03Dgk@PFAzq{>{UhKN;T@g%1^+H0gJJP=_l{gH=dMK>$jHz}h=|GMay$wy zhQZIA0o8kdyMK4ecK`rWXXd%ayducxck!7FV({&T#)*6XIwj~7#4rD2WVLxoB%cq$nY~~z`ou90Q~y%PAn0# zbK;}yDEWmkpn5l26qgk+YJR2zng>QK;<$E(#m{trWwjF#5Y^7G_^A%aoh;y>*>?Yq zgMTP?hRN?cl`TH^V(2tB2>kKqZ`9M^DEJ4u4URF!7-Nhv#(x{aeBIaI{p3J0KL<~K zP&F4gDhcScbm8-MK6ct zZWE=p5=OqsZznMu^<^`p)Y0)L4+fXsj-vD|Gcjj9TymH_d-NCcUpr2)F3VE3oiqTyMS%mXc;;_eE%J; z2JR9dFBLUSgJYSY4-J~8!M1HQ5B86Bp`iarXQ2%P8MK2h<-bS zXdr_?>ohGZT&XmCHh3DiOTeh{UntVj(&So9vyseBK zC6Xp&dut2lmX@U2vv6){3ENv+Qh$6jY;LUM{Dq6$I!p)b>kVAKdIj}r87)H(rJ`x+ zXdA77f*b@iT`fa*HIM`g3sSoP3p7V~ucagbVkdahi*;*+ z)|LmR14u$aW*xC+#e-(NI1p^l;-P`)79~cQ4zR4YYeaQ4NjuYQDYXlNw11Zw1TTnQ zqh<&bBTNU7go0=d@yJap-i0~SI*dFBL^tUN3(~tlr+kNc8G%JE6M;n_8e|6{s8L3P zFg+ezLqR9-Zeys<9@X}|0U^3YHU@Glm8^hNXeI1lsM#PjTsV_| diff --git a/Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/meta.json b/Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/meta.json new file mode 100644 index 00000000000..af3ad32623a --- /dev/null +++ b/Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "gttedmi and kesfox15", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "reflector", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/reflector.png b/Resources/Textures/Backmen/Supermatter/reflectorframe.rsi/reflector.png new file mode 100644 index 0000000000000000000000000000000000000000..459b3b1bb98a9313a4a477210a685f211e4e84c2 GIT binary patch literal 1346 zcmV-I1-<%-P)Px({7FPXRCt{2TU&1%MHoGH0`ayYmvtmaC?KU(Q;4FxL{v!=^@6G*rAVN{0}n{8 zc&Sw0_yPO_{s8b)Bpy)21F;}N^rGC_3V0P zJhK7fBUzdq&G((N-|k#?<}*-79S0f_TICm`Ka^szm~;Kn$XRMTKU`1TEV-|F?Zf1c z(kh>A>85Ggm?C z2hx_<)&ej8=j{II^7>3LEz||!@!~T4z#}9#j2f& zp9MVpzFk0M^rzV;lgXOwRr6B=VCHiWlPw2f@-q*DydaS`^Ep)%QuqT0!Gi#505YkS zeS30Y^E+MOgAYHhm_N;Yj#|$Gas!#vN|}#uzxj$fs{8SufBRjf{AFXn;^Gpl^&F1A zaonj}UR)62$G}@>o$|feX=u%HoH)@PXhY*)O{azU-3a2Rc7fNrjydJ+E!+8t!0k)K zMm4|hETCx`o;+SIs|ynGd{eBVPSZ3tmHWs~4M6Jm-<HPyZfy+g@zL`hCV$`b33TyeAh->u|COzw>2u?C9%Cc5}U0{`D8?*WiBfANUQCI_jvSjyme_!S%$= zk~iBzX8V7Gp1^g$!h`$Jb)D(RIWaR=(sdmR5AL(-V=) zPCozg>(UEtal~RV<~%(z;yzxS$7i2@!kyU>1XX%`y8zi#ufEd7J+~7-e&7GRepKn9jsfl+lw>lAmiA6&B62LPHOFDC%Q@p$ zsmHeqDVFt60s}p4bqEo~p9|58P=fvY|>3H2~y5L8RXitQX{s11j}U17KN2hm1G! z#BOubv)Z40pX{&^9LX|8g4UXHAj-LUio9*>jLRgp9m znSoHL$M+3rZ8;18nEIK21ERI%u(Ts?)q3c8f`4SVNmN3mo{B+mXJqb5;+5ghyVZp07*qoM6N<$ Ef^kHcR{#J2 literal 0 HcmV?d00001 From 280929c99d01c653235193072be73ace6aa442e4 Mon Sep 17 00:00:00 2001 From: Zack Backmen Date: Sun, 12 Jul 2026 15:49:11 +0300 Subject: [PATCH 05/11] fix --- .../Backmen/Supermatter/ReflectorTest.cs | 124 ++++++++++++++++++ .../Supermatter/DirectionalReflectSystem.cs | 7 +- 2 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs diff --git a/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs b/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs new file mode 100644 index 00000000000..4a9eea28c48 --- /dev/null +++ b/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs @@ -0,0 +1,124 @@ +using System.Numerics; +using Content.IntegrationTests.Fixtures; +using Content.Shared.Backmen.Supermatter.Components; +using Content.Shared.Projectiles; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Backmen.Supermatter; + +/// +/// Directional reflectors redirect emitter bolts from the back and sides, but absorb direct front hits. +/// +[TestFixture] +public sealed class ReflectorTest : GameTest +{ + private static readonly EntProtoId Reflector = "Reflector"; + private static readonly EntProtoId EmitterBolt = "EmitterBolt"; + + public override PoolSettings PoolSettings => new() { Connected = false, Dirty = true }; + + [Test] + public async Task FrontHit_IsNotRedirected() + { + var map = await Pair.CreateTestMap(); + + await Server.WaitAssertion(() => + { + var entMan = Server.EntMan; + var physics = entMan.System(); + var xformSys = entMan.System(); + + var reflector = entMan.SpawnEntity(Reflector, map.MapCoords); + xformSys.SetWorldRotation(reflector, Angle.Zero); + + var bolt = entMan.SpawnEntity(EmitterBolt, map.MapCoords); + var boltPhysics = entMan.GetComponent(bolt); + physics.SetLinearVelocity(bolt, new Vector2(0f, -10f), body: boltPhysics); + + var projectile = entMan.GetComponent(bolt); + var attempt = new ProjectileReflectAttemptEvent(bolt, projectile, false); + entMan.EventBus.RaiseLocalEvent(reflector, ref attempt); + + Assert.That(attempt.Cancelled, Is.False, "Direct front hits should be absorbed, not redirected."); + }); + } + + [Test] + public async Task BackHit_IsRedirectedAlongOutputFace() + { + var map = await Pair.CreateTestMap(); + + await Server.WaitAssertion(() => + { + var entMan = Server.EntMan; + var physics = entMan.System(); + var xformSys = entMan.System(); + + var reflector = entMan.SpawnEntity(Reflector, map.MapCoords); + xformSys.SetWorldRotation(reflector, Angle.Zero); + + var bolt = entMan.SpawnEntity(EmitterBolt, map.MapCoords); + var boltPhysics = entMan.GetComponent(bolt); + physics.SetLinearVelocity(bolt, new Vector2(0f, 10f), body: boltPhysics); + + var projectile = entMan.GetComponent(bolt); + var attempt = new ProjectileReflectAttemptEvent(bolt, projectile, false); + entMan.EventBus.RaiseLocalEvent(reflector, ref attempt); + + Assert.That(attempt.Cancelled, Is.True, "Back hits should be redirected."); + + var redirected = physics.GetMapLinearVelocity(bolt, component: boltPhysics); + var outputDir = Angle.Zero.ToWorldVec(); + Assert.That(Vector2.Dot(Vector2.Normalize(redirected), outputDir), Is.GreaterThan(0.99f)); + Assert.That(redirected.Length(), Is.EqualTo(10f).Within(0.01f)); + Assert.That(projectile.Shooter, Is.EqualTo(reflector)); + }); + } + + [Test] + public async Task SideHit_IsRedirectedAlongOutputFace() + { + var map = await Pair.CreateTestMap(); + + await Server.WaitAssertion(() => + { + var entMan = Server.EntMan; + var physics = entMan.System(); + var xformSys = entMan.System(); + + var reflector = entMan.SpawnEntity(Reflector, map.MapCoords); + xformSys.SetWorldRotation(reflector, Angle.Zero); + + var bolt = entMan.SpawnEntity(EmitterBolt, map.MapCoords); + var boltPhysics = entMan.GetComponent(bolt); + physics.SetLinearVelocity(bolt, new Vector2(10f, 0f), body: boltPhysics); + + var projectile = entMan.GetComponent(bolt); + var attempt = new ProjectileReflectAttemptEvent(bolt, projectile, false); + entMan.EventBus.RaiseLocalEvent(reflector, ref attempt); + + Assert.That(attempt.Cancelled, Is.True, "Side hits should be redirected."); + + var redirected = physics.GetMapLinearVelocity(bolt, component: boltPhysics); + var outputDir = Angle.Zero.ToWorldVec(); + Assert.That(Vector2.Dot(Vector2.Normalize(redirected), outputDir), Is.GreaterThan(0.99f)); + }); + } + + [Test] + public async Task ReflectorPrototype_HasDirectionalReflectComponent() + { + await Server.WaitAssertion(() => + { + var entMan = Server.EntMan; + var reflector = entMan.SpawnEntity(Reflector, MapCoordinates.Nullspace); + + Assert.That(entMan.HasComponent(reflector), Is.True); + }); + } +} diff --git a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs index dcc5cea43b0..b18d69e0ee7 100644 --- a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs +++ b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs @@ -62,11 +62,12 @@ private bool TryReflectProjectile(Entity reflector, return false; var outputAngle = _transform.GetWorldRotation(reflector); - var incomingFrom = (-velocity).ToWorldAngle(); - var fromOutputFace = Math.Abs(Angle.ShortestDistance(outputAngle, incomingFrom).Degrees); + var outputDir = outputAngle.ToWorldVec().Normalized(); + var incomingDir = (-velocity).Normalized(); + var frontAlignment = Vector2.Dot(incomingDir, -outputDir); // Only shots fired into the output face are absorbed. - if (fromOutputFace < reflector.Comp.FrontAbsorbAngle.Degrees) + if (frontAlignment > Math.Cos(reflector.Comp.FrontAbsorbAngle.Theta)) return false; // Shots from the back or sides are redirected out through the output face. From 18190d76b6a949881ce7e3f3d94c55fab7bab1d1 Mon Sep 17 00:00:00 2001 From: Zack Backmen Date: Sun, 12 Jul 2026 16:11:03 +0300 Subject: [PATCH 06/11] fix --- .../Backmen/Supermatter/ReflectorTest.cs | 37 ++++++++++++++++++- .../Supermatter/DirectionalReflectSystem.cs | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs b/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs index 4a9eea28c48..d20c4a9c4d4 100644 --- a/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs +++ b/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs @@ -36,9 +36,10 @@ await Server.WaitAssertion(() => var reflector = entMan.SpawnEntity(Reflector, map.MapCoords); xformSys.SetWorldRotation(reflector, Angle.Zero); + // Hits the output-facing mirror surface. var bolt = entMan.SpawnEntity(EmitterBolt, map.MapCoords); var boltPhysics = entMan.GetComponent(bolt); - physics.SetLinearVelocity(bolt, new Vector2(0f, -10f), body: boltPhysics); + physics.SetLinearVelocity(bolt, new Vector2(0f, 10f), body: boltPhysics); var projectile = entMan.GetComponent(bolt); var attempt = new ProjectileReflectAttemptEvent(bolt, projectile, false); @@ -62,9 +63,10 @@ await Server.WaitAssertion(() => var reflector = entMan.SpawnEntity(Reflector, map.MapCoords); xformSys.SetWorldRotation(reflector, Angle.Zero); + // Typical emitter-behind setup: bolt travels through the back and should exit forward. var bolt = entMan.SpawnEntity(EmitterBolt, map.MapCoords); var boltPhysics = entMan.GetComponent(bolt); - physics.SetLinearVelocity(bolt, new Vector2(0f, 10f), body: boltPhysics); + physics.SetLinearVelocity(bolt, new Vector2(0f, -10f), body: boltPhysics); var projectile = entMan.GetComponent(bolt); var attempt = new ProjectileReflectAttemptEvent(bolt, projectile, false); @@ -110,6 +112,37 @@ await Server.WaitAssertion(() => }); } + [Test] + public async Task EmitterBehindReflector_IsRedirectedAlongOutputFace() + { + var map = await Pair.CreateTestMap(); + + await Server.WaitAssertion(() => + { + var entMan = Server.EntMan; + var physics = entMan.System(); + var xformSys = entMan.System(); + + var reflector = entMan.SpawnEntity(Reflector, map.MapCoords); + // Output faces east toward the containment field. + xformSys.SetWorldRotation(reflector, Angle.FromDegrees(90)); + + var bolt = entMan.SpawnEntity(EmitterBolt, map.MapCoords); + var boltPhysics = entMan.GetComponent(bolt); + physics.SetLinearVelocity(bolt, new Vector2(10f, 0f), body: boltPhysics); + + var projectile = entMan.GetComponent(bolt); + var attempt = new ProjectileReflectAttemptEvent(bolt, projectile, false); + entMan.EventBus.RaiseLocalEvent(reflector, ref attempt); + + Assert.That(attempt.Cancelled, Is.True, "Emitter-behind shots should be redirected forward."); + + var redirected = physics.GetMapLinearVelocity(bolt, component: boltPhysics); + var outputDir = Angle.FromDegrees(90).ToWorldVec(); + Assert.That(Vector2.Dot(Vector2.Normalize(redirected), outputDir), Is.GreaterThan(0.99f)); + }); + } + [Test] public async Task ReflectorPrototype_HasDirectionalReflectComponent() { diff --git a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs index b18d69e0ee7..1320f6254bc 100644 --- a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs +++ b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs @@ -64,7 +64,7 @@ private bool TryReflectProjectile(Entity reflector, var outputAngle = _transform.GetWorldRotation(reflector); var outputDir = outputAngle.ToWorldVec().Normalized(); var incomingDir = (-velocity).Normalized(); - var frontAlignment = Vector2.Dot(incomingDir, -outputDir); + var frontAlignment = Vector2.Dot(incomingDir, outputDir); // Only shots fired into the output face are absorbed. if (frontAlignment > Math.Cos(reflector.Comp.FrontAbsorbAngle.Theta)) From 258c5c6593ae50e251d6a04cb792b75681acb66b Mon Sep 17 00:00:00 2001 From: Zack Backmen Date: Sun, 12 Jul 2026 16:45:48 +0300 Subject: [PATCH 07/11] fix --- .../Backmen/Supermatter/ReflectorTest.cs | 3 +++ .../Supermatter/DirectionalReflectSystem.cs | 24 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs b/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs index d20c4a9c4d4..f38316c3579 100644 --- a/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs +++ b/Content.IntegrationTests/Tests/Backmen/Supermatter/ReflectorTest.cs @@ -78,6 +78,9 @@ await Server.WaitAssertion(() => var outputDir = Angle.Zero.ToWorldVec(); Assert.That(Vector2.Dot(Vector2.Normalize(redirected), outputDir), Is.GreaterThan(0.99f)); Assert.That(redirected.Length(), Is.EqualTo(10f).Within(0.01f)); + Assert.That( + xformSys.GetWorldRotation(bolt).Degrees, + Is.EqualTo((outputDir.ToWorldAngle() + projectile.Angle).Degrees).Within(0.1)); Assert.That(projectile.Shooter, Is.EqualTo(reflector)); }); } diff --git a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs index 1320f6254bc..049cde971c4 100644 --- a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs +++ b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs @@ -72,15 +72,27 @@ private bool TryReflectProjectile(Entity reflector, // Shots from the back or sides are redirected out through the output face. var speed = velocity.Length(); - var newVelocity = outputAngle.ToWorldVec() * speed; - var difference = newVelocity - velocity; - - _physics.SetLinearVelocity(projectileUid, physics.LinearVelocity + difference, body: physics); - _transform.SetWorldRotation(projectileUid, outputAngle); + var targetMapVelocity = outputDir * speed; + var projectileAngle = TryComp(projectileUid, out var projectile) + ? projectile.Angle + : Angle.Zero; + var projectileRotation = outputDir.ToWorldAngle() + projectileAngle; + + // Match gun firing so sprite direction and velocity stay aligned. + var finalLinear = physics.LinearVelocity + targetMapVelocity - velocity; + _physics.SetLinearVelocity(projectileUid, finalLinear, body: physics); + _physics.SetAngularVelocity(projectileUid, 0f, body: physics); + + // Push the bolt past the reflector so collision resolution does not skew the trajectory. + var reflectorPos = _transform.GetWorldPosition(reflector); + _transform.SetWorldPositionRotation( + projectileUid, + reflectorPos + outputDir * 0.55f, + projectileRotation); PlayAudioAndPopup(reflector.Comp, reflector); - if (TryComp(projectileUid, out var projectile)) + if (projectile != null) { _adminLogger.Add( LogType.BulletHit, From ad6cb4abb26fb5d7e85fba71294a8390109035d4 Mon Sep 17 00:00:00 2001 From: Zack Backmen Date: Sun, 12 Jul 2026 17:58:16 +0300 Subject: [PATCH 08/11] Update ProjectileSystem.cs --- Content.Server/Projectiles/ProjectileSystem.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index 40888bc24df..e825a282c24 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -56,11 +56,11 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St damageRequired -= _damageableSystem.GetTotalDamage((target, damageableComponent)); damageRequired = FixedPoint2.Max(damageRequired, FixedPoint2.Zero); } - var deleted = Deleted(target); + // start-backmen: reflector if (_damageableSystem.TryChangeDamage((target, damageableComponent), ev.Damage, out var damage, component.IgnoreResistances, origin: component.Shooter) && Exists(component.Shooter)) { - if (!deleted) + if (Exists(target)) { _color.RaiseEffect(Color.Red, new List { target }, Filter.Pvs(target, entityManager: EntityManager)); } @@ -76,13 +76,14 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St component.ProjectileSpent = true; } - if (!deleted) + if (Exists(target)) { _guns.PlayImpactSound(target, damage, component.SoundHit, component.ForceSound); if (!args.OurBody.LinearVelocity.IsLengthZero()) _sharedCameraRecoil.KickCamera(target, args.OurBody.LinearVelocity.Normalized()); } + // end-backmen: reflector if (component.DeleteOnCollide && component.ProjectileSpent) QueueDel(uid); From 7e0eb4f4d4409f32274f73fd5707e5b23cd963b1 Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Wed, 15 Jul 2026 01:49:23 +0300 Subject: [PATCH 09/11] fix --- Content.Server/Projectiles/ProjectileSystem.cs | 2 +- .../Backmen/Supermatter/DirectionalReflectSystem.cs | 3 --- Resources/Locale/en-US/backmen/supermatter/supermatter.ftl | 2 -- .../prototypes/_backmen/entities/supermatter/reflector.ftl | 5 ----- .../prototypes/_backmen/entities/supermatter/reflector.ftl | 2 -- 5 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index e825a282c24..998cac719e3 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -58,7 +58,7 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St } // start-backmen: reflector - if (_damageableSystem.TryChangeDamage((target, damageableComponent), ev.Damage, out var damage, component.IgnoreResistances, origin: component.Shooter) && Exists(component.Shooter)) + if (_damageableSystem.TryChangeDamage((target, damageableComponent), ev.Damage, out var damage, component.IgnoreResistances, origin: component.Shooter, seedEntity: uid) && Exists(component.Shooter)) { if (Exists(target)) { diff --git a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs index 049cde971c4..b9c111687e5 100644 --- a/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs +++ b/Content.Shared/Backmen/Supermatter/DirectionalReflectSystem.cs @@ -129,8 +129,5 @@ private void PlayAudioAndPopup(DirectionalReflectComponent comp, EntityUid refle { if (!_net.IsServer) return; - - _popup.PopupEntity(Loc.GetString("directional-reflect-shot"), reflector); - _audio.PlayPvs(comp.SoundOnReflect, reflector); } } diff --git a/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl b/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl index faa09ab0055..de312c2699a 100644 --- a/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl +++ b/Resources/Locale/en-US/backmen/supermatter/supermatter.ftl @@ -8,8 +8,6 @@ supermatter-delamination-overmass = The Supermatter has Reached Critical Mass Fa supermatter-delamination-default = The Supermatter has Reached Critical Integrity Falure. Emergency Causality Destabilization Field has been Activated. supermatter-seconds-before-delam = { $Seconds } Seconds Remain Before Delamination. -directional-reflect-shot = The shot ricochets off the reflector! - ent-Reflector = reflector .desc = An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. diff --git a/Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl b/Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl deleted file mode 100644 index 183d250c3bc..00000000000 --- a/Resources/Locale/en-US/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl +++ /dev/null @@ -1,5 +0,0 @@ -ent-Reflector = reflector - .desc = An angled mirror that redirects emitter bolts from the back and sides out through its facing direction. - -ent-ReflectorFrame = reflector frame - .desc = An unfinished reflector assembly. Anchor it, then add a diamond and reinforced glass. diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl index e2e0dc85c89..491664efaae 100644 --- a/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/supermatter/reflector.ftl @@ -1,5 +1,3 @@ -directional-reflect-shot = Выстрел рикошетит от отражателя! - ent-Reflector = отражатель .desc = Угловое зеркало, перенаправляющее болты эмиттера с тыла и боков через лицевую сторону. Говорят что первые версии были сделаны из холодильника СССП. From b4dc1c2f9d7fc48b63342d7fdb3cf0c47069c8e3 Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Thu, 16 Jul 2026 16:21:52 +0300 Subject: [PATCH 10/11] Port supermatter comsol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена консоль мониторинга суперматреии. Оригинальный пр (https://github.com/Goob-Station/Goob-Station/pull/5788 https://github.com/Goob-Station/Goob-Station/pull/6808). Так же была работа по адаптированию к нашему билду с улучшением определённых частей кода. --- .../SupermatterConsoleBoundUserInterface.cs | 42 +++ .../Consoles/SupermatterConsoleWindow.xaml | 34 +++ .../Consoles/SupermatterConsoleWindow.xaml.cs | 188 +++++++++++++ .../Consoles/SupermatterEntryContainer.xaml | 106 +++++++ .../SupermatterEntryContainer.xaml.cs | 238 ++++++++++++++++ .../Consoles/SupermatterGasBarContainer.xaml | 16 ++ .../SupermatterGasBarContainer.xaml.cs | 36 +++ .../Consoles/SupermatterConsoleSystem.cs | 258 ++++++++++++++++++ .../SharedSupermatterConsoleSystem.cs | 5 + .../Consoles/SupermatterConsoleComponent.cs | 107 ++++++++ .../Monitor/SupermatterStatusType.cs | 16 ++ .../supermatter/supermatter-console.ftl | 44 +++ .../computers/supermatter_console.ftl | 8 + .../supermatter/supermatter-console.ftl | 44 +++ .../computers/supermatter_console.ftl | 8 + .../Catalog/Fills/Lockers/heads.yml | 2 + .../Computers/supermatter_console.yml | 94 +++++++ .../Objects/Devices/tablets.rsi/meta.json | 10 + .../tablets.rsi/supermatterconsole.png | Bin 0 -> 713 bytes .../Objects/Devices/tablets.rsi/tablet.png | Bin 0 -> 348 bytes .../Supermatter/computers.rsi/computer.png | Bin 0 -> 1872 bytes .../computers.rsi/generic_keyboard.png | Bin 0 -> 1201 bytes .../computers.rsi/generic_panel_open.png | Bin 0 -> 405 bytes .../Supermatter/computers.rsi/meta.json | 73 +++++ .../computers.rsi/supermatter-0.png | Bin 0 -> 1100 bytes .../computers.rsi/supermatter-1.png | Bin 0 -> 1144 bytes .../computers.rsi/supermatter-2.png | Bin 0 -> 1167 bytes .../computers.rsi/supermatter-3.png | Bin 0 -> 1115 bytes .../computers.rsi/supermatter-4.png | Bin 0 -> 1122 bytes .../computers.rsi/supermatter-5.png | Bin 0 -> 712 bytes .../computers.rsi/supermatter-6.png | Bin 0 -> 1241 bytes .../computers.rsi/supermatter_keys.png | Bin 0 -> 563 bytes .../Interface/Supermatter/supermatter.png | Bin 0 -> 3070 bytes 33 files changed, 1329 insertions(+) create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml create mode 100644 Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml.cs create mode 100644 Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs create mode 100644 Content.Shared/Backmen/Supermatter/Consoles/SharedSupermatterConsoleSystem.cs create mode 100644 Content.Shared/Backmen/Supermatter/Consoles/SupermatterConsoleComponent.cs create mode 100644 Content.Shared/Backmen/Supermatter/Monitor/SupermatterStatusType.cs create mode 100644 Resources/Locale/en-US/backmen/supermatter/supermatter-console.ftl create mode 100644 Resources/Locale/en-US/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl create mode 100644 Resources/Locale/ru-RU/backmen/supermatter/supermatter-console.ftl create mode 100644 Resources/Locale/ru-RU/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl create mode 100644 Resources/Prototypes/_Backmen/Entities/Structures/Machines/Computers/supermatter_console.yml create mode 100644 Resources/Textures/Backmen/Objects/Devices/tablets.rsi/meta.json create mode 100644 Resources/Textures/Backmen/Objects/Devices/tablets.rsi/supermatterconsole.png create mode 100644 Resources/Textures/Backmen/Objects/Devices/tablets.rsi/tablet.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/computer.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/generic_keyboard.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/generic_panel_open.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/meta.json create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-0.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-1.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-2.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-3.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-4.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-5.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-6.png create mode 100644 Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter_keys.png create mode 100644 Resources/Textures/Interface/Supermatter/supermatter.png diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs b/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..f6087d35963 --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs @@ -0,0 +1,42 @@ +using Content.Shared.Backmen.Supermatter.Consoles; + +namespace Content.Client.Backmen.Supermatter.Consoles; + +public sealed class SupermatterConsoleBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private SupermatterConsoleWindow? _menu; + + public SupermatterConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = new SupermatterConsoleWindow(this, Owner); + _menu.OpenCentered(); + _menu.OnClose += Close; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + var castState = (SupermatterConsoleBoundInterfaceState) state; + _menu?.UpdateUI(castState.Supermatters, castState.FocusData); + } + + public void SendFocusChangeMessage(NetEntity? netEntity) => + SendMessage(new SupermatterConsoleFocusChangeMessage(netEntity)); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml b/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml new file mode 100644 index 00000000000..f817172483c --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs b/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs new file mode 100644 index 00000000000..9423cd6dfd8 --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs @@ -0,0 +1,188 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Client.Message; +using Content.Client.UserInterface.Controls; +using Content.Shared.Backmen.Supermatter.Consoles; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.Backmen.Supermatter.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class SupermatterConsoleWindow : FancyWindow +{ + private readonly IEntityManager _entManager; + + private readonly EntityUid? _owner; + private NetEntity? _trackedEntity; + + private SupermatterConsoleEntry[]? _supermatters; + + public event Action? SendFocusChangeMessageAction; + + private bool _autoScrollActive; + private bool _autoScrollAwaitsUpdate; + + public SupermatterConsoleWindow(SupermatterConsoleBoundUserInterface userInterface, EntityUid? owner) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + + _owner = owner; + SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage; + } + + public void UpdateUI(SupermatterConsoleEntry[] supermatters, SupermatterFocusData? focusData) + { + if (_owner == null + || !_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + if (_trackedEntity != focusData?.NetEntity) + { + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + focusData = null; + } + + _supermatters = supermatters; + + var supermattersCount = _supermatters.Length; + + while (SupermattersTable.ChildCount > supermattersCount) + SupermattersTable.RemoveChild(SupermattersTable.GetChild(SupermattersTable.ChildCount - 1)); + + for (var index = 0; index < _supermatters.Length; index++) + { + var entry = _supermatters[index]; + UpdateUIEntry(entry, index, SupermattersTable, console, focusData); + } + + if (supermattersCount == 0) + { + var label = new RichTextLabel + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + }; + + label.SetMarkup(Loc.GetString("supermatter-console-window-no-supermatters")); + SupermattersTable.AddChild(label); + } + + if (_autoScrollAwaitsUpdate) + { + _autoScrollActive = true; + _autoScrollAwaitsUpdate = false; + } + } + + private void UpdateUIEntry( + SupermatterConsoleEntry entry, + int index, + Control table, + SupermatterConsoleComponent console, + SupermatterFocusData? focusData = null) + { + if (index >= table.ChildCount) + { + var newEntryContainer = new SupermatterEntryContainer(entry.NetEntity); + + newEntryContainer.FocusButton.OnButtonUp += _ => + { + _trackedEntity = _trackedEntity == newEntryContainer.NetEntity + ? null + : newEntryContainer.NetEntity; + + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + }; + + table.AddChild(newEntryContainer); + } + + var tableChild = table.GetChild(index); + + if (tableChild is not SupermatterEntryContainer) + { + table.RemoveChild(tableChild); + UpdateUIEntry(entry, index, table, console, focusData); + return; + } + + var entryContainer = (SupermatterEntryContainer) tableChild; + entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData); + } + + protected override void FrameUpdate(FrameEventArgs args) => + AutoScrollToFocus(); + + private void AutoScrollToFocus() + { + if (!_autoScrollActive) + return; + + var scroll = SupermattersTable.Parent as ScrollContainer; + if (scroll == null + || !TryGetVerticalScrollbar(scroll, out var vScrollbar) + || !TryGetNextScrollPosition(out var nextScrollPosition)) + return; + + vScrollbar.ValueTarget = nextScrollPosition.Value; + + if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget)) + _autoScrollActive = false; + } + + private static bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar) + { + vScrollBar = null; + + foreach (var child in scroll.Children) + { + if (child is not VScrollBar castChild) + continue; + + vScrollBar = castChild; + return true; + } + + return false; + } + + private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition) + { + nextScrollPosition = null; + + var scroll = SupermattersTable.Parent as ScrollContainer; + if (scroll == null) + return false; + + var container = scroll.Children.ElementAt(0) as BoxContainer; + if (container == null || !container.Children.Any()) + return false; + + if (!container.Children.Any(x => x.Height > 0)) + return false; + + nextScrollPosition = 0; + + foreach (var control in container.Children) + { + if (control is not SupermatterEntryContainer entry) + continue; + + if (entry.NetEntity == _trackedEntity) + return true; + + nextScrollPosition += control.Height; + } + + nextScrollPosition = null; + return false; + } +} diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml b/Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml new file mode 100644 index 00000000000..d4dcb80ae48 --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs b/Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs new file mode 100644 index 00000000000..d4fa2c6f972 --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs @@ -0,0 +1,238 @@ +using System.Linq; +using System.Numerics; +using Content.Client.Atmos.EntitySystems; +using Content.Client.Stylesheets; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Prototypes; +using Content.Shared.Backmen.Supermatter.Consoles; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Backmen.Supermatter.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class SupermatterEntryContainer : BoxContainer +{ + public NetEntity NetEntity; + + private readonly IEntityManager _entManager; + private readonly Dictionary _engineDictionary; + + public SupermatterEntryContainer(NetEntity uid) + { + RobustXamlLoader.Load(this); + + _entManager = IoCManager.Resolve(); + var cache = IoCManager.Resolve(); + + NetEntity = uid; + + var normalFont = new VectorFont(cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + var monoFont = new VectorFont(cache.GetResource("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf"), 10); + + IntegrityLabel.FontOverride = normalFont; + PowerLabel.FontOverride = normalFont; + RadiationLabel.FontOverride = normalFont; + MolesLabel.FontOverride = normalFont; + TemperatureLabel.FontOverride = normalFont; + TemperatureLimitLabel.FontOverride = normalFont; + WasteLabel.FontOverride = normalFont; + AbsorptionLabel.FontOverride = normalFont; + + IntegrityBarLabel.FontOverride = monoFont; + PowerBarLabel.FontOverride = monoFont; + RadiationBarLabel.FontOverride = monoFont; + MolesBarLabel.FontOverride = monoFont; + TemperatureBarLabel.FontOverride = monoFont; + TemperatureLimitBarLabel.FontOverride = monoFont; + WasteBarLabel.FontOverride = monoFont; + AbsorptionBarLabel.FontOverride = monoFont; + + var red = StyleNano.DangerousRedFore; + var orange = StyleNano.ConcerningOrangeFore; + var green = StyleNano.GoodGreenFore; + var turqoise = Color.FromHex("#009893"); + + _engineDictionary = new Dictionary + { + { "integrity", new EngineBarEntry(IntegrityBarLabel, IntegrityBar, IntegrityBarBorder, 0.9f, 0.1f, red, orange, green) }, + { "power", new EngineBarEntry(PowerBarLabel, PowerBar, PowerBarBorder, 0.9f, 0.1f, green, orange, red) }, + { "radiation", new EngineBarEntry(RadiationBarLabel, RadiationBar, RadiationBarBorder, 0.1f, 0.9f, green, orange, red) }, + { "moles", new EngineBarEntry(MolesBarLabel, MolesBar, MolesBarBorder, 0.5f, 0.5f, green, orange, red) }, + { "temperature", new EngineBarEntry(TemperatureBarLabel, TemperatureBar, TemperatureBarBorder, 0.5f, 0.5f, turqoise, green, red) }, + { "waste", new EngineBarEntry(WasteBarLabel, WasteBar, WasteBarBorder, 0.5f, 0.5f, green, orange, red) }, + }; + } + + public void UpdateEntry(SupermatterConsoleEntry entry, bool isFocus, SupermatterFocusData? focusData = null) + { + NetEntity = entry.NetEntity; + + SupermatterNameLabel.Text = Loc.GetString("supermatter-console-window-label-sm", ("name", entry.EntityName)); + FocusContainer.Visible = isFocus; + + if (!isFocus || focusData == null) + return; + + _engineDictionary["integrity"].Value = focusData.Value.Integrity; + _engineDictionary["power"].Value = focusData.Value.Power; + _engineDictionary["radiation"].Value = focusData.Value.Radiation; + _engineDictionary["moles"].Value = focusData.Value.AbsorbedMoles; + _engineDictionary["temperature"].Value = focusData.Value.Temperature; + _engineDictionary["waste"].Value = focusData.Value.WasteMultiplier; + + var powerBar = _engineDictionary["power"]; + var powerPrefix = powerBar.Value switch { >= 1000 => "G", >= 1 => "M", _ => "" }; + var powerMultiplier = powerBar.Value switch { >= 1000 => 0.001, >= 1 => 1, _ => 1000 }; + powerBar.Label.Text = Loc.GetString("supermatter-console-window-label-power-bar", + ("power", (powerBar.Value * powerMultiplier).ToString("0.000")), + ("prefix", powerPrefix)); + + var temperatureLimit = focusData.Value.TemperatureLimit; + TemperatureBar.MaxValue = Math.Max(temperatureLimit, 1f); + TemperatureLimitBarLabel.Text = Loc.GetString("supermatter-console-window-label-temperature-bar", + ("temperature", temperatureLimit.ToString("0.00"))); + + AbsorptionBarLabel.Text = Loc.GetString("supermatter-console-window-label-absorption-bar", + ("absorption", focusData.Value.AbsorptionRatio.ToString("0"))); + + foreach (var bar in _engineDictionary) + { + var current = bar.Value; + UpdateEngineBar(current.Bar, current.Border, current.Value, current.LeftSize, current.RightSize, + current.LeftColor, current.MiddleColor, current.RightColor); + + if (bar.Key == "power") + continue; + + current.Label.Text = Loc.GetString("supermatter-console-window-label-" + bar.Key + "-bar", + (bar.Key, current.Value.ToString("0.00"))); + } + + var atmosphereSystem = _entManager.System(); + var gases = focusData.Value.GasStorage.Keys + .Select(atmosphereSystem.GetGas) + .OrderByDescending(gas => GetStoredGas(gas, focusData)); + + var index = 0; + foreach (var gas in gases) + { + var stored = GetStoredGas(gas, focusData); + var value = focusData.Value.AbsorbedMoles > 0f + ? stored / focusData.Value.AbsorbedMoles * 100f + : 0f; + + UpdateGasBar(index, GasTable, Loc.GetString(gas.Name), gas.Color, value); + index++; + } + } + + /// + /// Looks up stored moles using the gas prototype ID string (PR #6808), not a numeric index. + /// + private static float GetStoredGas(GasPrototype gas, SupermatterFocusData? focusData) + { + if (focusData is null) + return 0f; + + if (!Enum.TryParse(gas.ID, out Gas id)) + return 0f; + + return focusData.Value.GasStorage.GetValueOrDefault(id); + } + + private static void UpdateEngineBar( + ProgressBar bar, + PanelContainer border, + float value, + float leftSize, + float rightSize, + Color leftColor, + Color middleColor, + Color rightColor) + { + var clamped = Math.Clamp(value, bar.MinValue, bar.MaxValue); + var normalized = clamped / Math.Max(bar.MaxValue, 1f); + var leftHsv = Color.ToHsv(leftColor); + var middleHsv = Color.ToHsv(middleColor); + var rightHsv = Color.ToHsv(rightColor); + + var minColor = new Vector4(0, 0, 0, 0); + var maxColor = new Vector4(1, 1, 1, 1); + Color finalColor; + + if (normalized <= leftSize) + { + normalized /= leftSize; + var calcColor = Vector4.Lerp(leftHsv, middleHsv, normalized); + finalColor = Color.FromHsv(Vector4.Clamp(calcColor, minColor, maxColor)); + } + else + { + normalized = (normalized - leftSize) / rightSize; + var calcColor = Vector4.Lerp(middleHsv, rightHsv, normalized); + finalColor = Color.FromHsv(Vector4.Clamp(calcColor, minColor, maxColor)); + } + + bar.ForegroundStyleBoxOverride ??= new StyleBoxFlat(); + border.PanelOverride ??= new StyleBoxFlat(); + + ((StyleBoxFlat) bar.ForegroundStyleBoxOverride).BackgroundColor = finalColor; + ((StyleBoxFlat) border.PanelOverride).BackgroundColor = finalColor; + bar.Value = clamped; + } + + private void UpdateGasBar(int index, Control table, string name, Color color, float value) + { + if (index >= table.ChildCount) + table.AddChild(new SupermatterGasBarContainer()); + + var tableChild = table.GetChild(index); + + if (tableChild is not SupermatterGasBarContainer) + { + table.RemoveChild(tableChild); + UpdateGasBar(index, table, name, color, value); + return; + } + + ((SupermatterGasBarContainer) tableChild).UpdateEntry(name, color, value); + } + + private sealed class EngineBarEntry + { + public readonly Label Label; + public readonly ProgressBar Bar; + public readonly PanelContainer Border; + public float Value; + public readonly float LeftSize; + public readonly float RightSize; + public readonly Color LeftColor; + public readonly Color MiddleColor; + public readonly Color RightColor; + + public EngineBarEntry( + Label label, + ProgressBar bar, + PanelContainer border, + float leftSize, + float rightSize, + Color leftColor, + Color middleColor, + Color rightColor) + { + Label = label; + Bar = bar; + Border = border; + LeftSize = leftSize; + RightSize = rightSize; + LeftColor = leftColor; + MiddleColor = middleColor; + RightColor = rightColor; + } + } +} diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml b/Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml new file mode 100644 index 00000000000..1d119ceeab4 --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml.cs b/Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml.cs new file mode 100644 index 00000000000..88c1e4f6420 --- /dev/null +++ b/Content.Client/Backmen/Supermatter/Consoles/SupermatterGasBarContainer.xaml.cs @@ -0,0 +1,36 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Backmen.Supermatter.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class SupermatterGasBarContainer : BoxContainer +{ + public SupermatterGasBarContainer() + { + RobustXamlLoader.Load(this); + + var cache = IoCManager.Resolve(); + var normalFont = new VectorFont(cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + var monoFont = new VectorFont(cache.GetResource("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf"), 10); + + GasLabel.FontOverride = normalFont; + GasBarLabel.FontOverride = monoFont; + } + + public void UpdateEntry(string name, Color color, float value) + { + GasBar.Value = value; + GasLabel.Text = name + ":"; + GasBarLabel.Text = Loc.GetString("supermatter-console-window-label-gas-bar", ("gas", value.ToString("0.00"))); + + GasBar.ForegroundStyleBoxOverride ??= new StyleBoxFlat(); + GasBarBorder.PanelOverride ??= new StyleBoxFlat(); + + ((StyleBoxFlat) GasBar.ForegroundStyleBoxOverride).BackgroundColor = color; + ((StyleBoxFlat) GasBarBorder.PanelOverride).BackgroundColor = color; + } +} diff --git a/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs b/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs new file mode 100644 index 00000000000..c4d0b3853a3 --- /dev/null +++ b/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs @@ -0,0 +1,258 @@ +using System.Linq; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Backmen.Supermatter.Components; +using Content.Shared.Backmen.Supermatter.Consoles; +using Content.Shared.Backmen.Supermatter.Monitor; +using Content.Shared.Radiation.Components; +using Robust.Server.GameObjects; + +namespace Content.Server.Backmen.Supermatter.Consoles; + +public sealed partial class SupermatterConsoleSystem : SharedSupermatterConsoleSystem +{ + [Dependency] private UserInterfaceSystem _userInterfaceSystem = default!; + [Dependency] private SharedAppearanceSystem _appearance = default!; + [Dependency] private AtmosphereSystem _atmosphere = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnConsoleInit); + SubscribeLocalEvent(OnConsoleParentChanged); + SubscribeLocalEvent(OnFocusChangedMessage); + SubscribeLocalEvent(OnGridSplit); + } + + private void OnConsoleInit(EntityUid uid, SupermatterConsoleComponent component, ComponentInit args) => + InitializeConsole(uid, component); + + private void OnConsoleParentChanged(EntityUid uid, SupermatterConsoleComponent component, EntParentChangedMessage args) => + InitializeConsole(uid, component); + + private void OnFocusChangedMessage(EntityUid uid, SupermatterConsoleComponent component, SupermatterConsoleFocusChangeMessage args) => + component.FocusSupermatter = args.FocusSupermatter; + + private void OnGridSplit(ref GridSplitEvent args) + { + var allGrids = args.NewGrids.ToList(); + + if (!allGrids.Contains(args.Grid)) + allGrids.Add(args.Grid); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (entXform.GridUid == null || !allGrids.Contains(entXform.GridUid.Value)) + continue; + + InitializeConsole(ent, entConsole); + } + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var supermatterEntriesForEachGrid = new Dictionary(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (entXform.GridUid == null) + continue; + + if (!supermatterEntriesForEachGrid.TryGetValue(entXform.GridUid.Value, out var supermatterEntries)) + { + supermatterEntries = GetSupermatterStateData(entXform.GridUid.Value).ToArray(); + supermatterEntriesForEachGrid[entXform.GridUid.Value] = supermatterEntries; + } + + var highestStatus = SupermatterStatusType.Inactive; + + foreach (var entry in supermatterEntries) + { + if (entry.EntityStatus > highestStatus) + highestStatus = entry.EntityStatus; + } + + if (TryComp(ent, out var entAppearance)) + _appearance.SetData(ent, SupermatterConsoleVisuals.ComputerLayerScreen, (int) highestStatus, entAppearance); + + UpdateUIState(ent, supermatterEntries, entConsole, entXform); + } + } + + private void UpdateUIState( + EntityUid uid, + SupermatterConsoleEntry[] supermatterStateData, + SupermatterConsoleComponent component, + TransformComponent xform) + { + if (!_userInterfaceSystem.IsUiOpen(uid, SupermatterConsoleUiKey.Key)) + return; + + if (xform.GridUid == null) + return; + + var gridUid = xform.GridUid.Value; + + // ИСПРАВЛЕНИЕ: Полная проверка перед использованием + EntityUid? focusEntity = null; + if (component.FocusSupermatter != null) + { + var entity = GetEntity(component.FocusSupermatter.Value); + if (Exists(entity)) + focusEntity = entity; + } + + var focusSupermatterData = GetFocusSupermatterData(focusEntity, gridUid); + + _userInterfaceSystem.SetUiState(uid, + SupermatterConsoleUiKey.Key, + new SupermatterConsoleBoundInterfaceState(supermatterStateData, focusSupermatterData)); + } + + private List GetSupermatterStateData(EntityUid gridUid) + { + var supermatterStateData = new List(); + + var querySupermatters = AllEntityQuery(); + while (querySupermatters.MoveNext(out var ent, out var entSupermatter, out var entXform)) + { + if (entXform.GridUid != gridUid || !entXform.Anchored) + continue; + + supermatterStateData.Add(new SupermatterConsoleEntry( + GetNetEntity(ent), + MetaData(ent).EntityName, + GetStatus(entSupermatter))); + } + + return supermatterStateData; + } + + private SupermatterFocusData? GetFocusSupermatterData(EntityUid? focusSupermatter, EntityUid gridUid) + { + // ИСПРАВЛЕНИЕ: Полная проверка всех условий + if (focusSupermatter == null) + return null; + + if (!Exists(focusSupermatter.Value)) + return null; + + if (!TryComp(focusSupermatter.Value, out var focusSupermatterXform)) + return null; + + if (!focusSupermatterXform.Anchored) + return null; + + if (focusSupermatterXform.GridUid != gridUid) + return null; + + if (!TryComp(focusSupermatter.Value, out var sm)) + return null; + + if (!TryComp(focusSupermatter.Value, out var radiationComp)) + return null; + + var gasStorage = GetGasStorage(focusSupermatter.Value, sm); + var temperature = GetTemperature(focusSupermatter.Value); + var wasteMultiplier = GetWasteMultiplier(focusSupermatter.Value, sm); + var tempThreshold = Atmospherics.T0C + sm.HeatPenaltyThreshold; + + return new SupermatterFocusData( + GetNetEntity(focusSupermatter.Value), + GetIntegrity(sm), + sm.Power, + radiationComp.Intensity, + gasStorage.Values.Sum(), + temperature, + tempThreshold * sm.DynamicHeatResistance, + wasteMultiplier, + sm.GasEfficiency * 100f, + gasStorage); + } + + /// + /// Returns moles of the given gas around the supermatter, without consuming the mixture. + /// + public float GetGas(EntityUid uid, Gas gas) + { + var mixture = _atmosphere.GetContainingMixture(uid, true, true); + return mixture?.GetMoles(gas) ?? 0f; + } + + private Dictionary GetGasStorage(EntityUid uid, BkmSupermatterComponent sm) + { + var gasStorage = new Dictionary(sm.GasStorage.Count); + + foreach (var gas in sm.GasStorage.Keys) + gasStorage[gas] = GetGas(uid, gas) * sm.GasEfficiency; + + return gasStorage; + } + + private float GetTemperature(EntityUid uid) + { + var mixture = _atmosphere.GetContainingMixture(uid, true, true); + return mixture?.Temperature ?? 0f; + } + + private float GetWasteMultiplier(EntityUid uid, BkmSupermatterComponent sm) + { + var mixture = _atmosphere.GetContainingMixture(uid, true, true); + if (mixture == null || mixture.TotalMoles <= 0f) + return 0.5f; + + var totalMoles = mixture.TotalMoles; + var heatModifier = 0f; + + foreach (var (gas, data) in sm.GasDataFields) + heatModifier += GetGas(uid, gas) / totalMoles * data.HeatPenalty; + + return Math.Max(heatModifier, 0.5f); + } + + public float GetIntegrity(BkmSupermatterComponent sm) + { + var integrity = sm.Damage / sm.ExplosionPoint; + integrity = (float) Math.Round(100 - integrity * 100, 2); + return integrity < 0 ? 0 : integrity; + } + + public SupermatterStatusType GetStatus(BkmSupermatterComponent sm) + { + if (sm.Delamming) + return SupermatterStatusType.Delaminating; + + var integrity = GetIntegrity(sm); + + if (integrity <= 5 || sm.Damage >= sm.DamageEmergencyThreshold) + return SupermatterStatusType.Emergency; + + if (integrity <= 25 || sm.Damage >= sm.DamageWarningThreshold) + return SupermatterStatusType.Danger; + + if (integrity <= 50 || sm.Damage > sm.DamageArchived) + return SupermatterStatusType.Warning; + + if (sm.Power >= sm.PowerPenaltyThreshold) + return SupermatterStatusType.Caution; + + if (sm.Power > 0) + return SupermatterStatusType.Normal; + + return SupermatterStatusType.Inactive; + } + + private void InitializeConsole(EntityUid uid, SupermatterConsoleComponent component) + { + // ИСПРАВЛЕНИЕ: Используем TryComp вместо Transform() + if (!TryComp(uid, out var xform) || xform.GridUid == null) + return; + + Dirty(uid, component); + } +} diff --git a/Content.Shared/Backmen/Supermatter/Consoles/SharedSupermatterConsoleSystem.cs b/Content.Shared/Backmen/Supermatter/Consoles/SharedSupermatterConsoleSystem.cs new file mode 100644 index 00000000000..d940e411254 --- /dev/null +++ b/Content.Shared/Backmen/Supermatter/Consoles/SharedSupermatterConsoleSystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Backmen.Supermatter.Consoles; + +public abstract partial class SharedSupermatterConsoleSystem : EntitySystem +{ +} diff --git a/Content.Shared/Backmen/Supermatter/Consoles/SupermatterConsoleComponent.cs b/Content.Shared/Backmen/Supermatter/Consoles/SupermatterConsoleComponent.cs new file mode 100644 index 00000000000..ef82b3c5084 --- /dev/null +++ b/Content.Shared/Backmen/Supermatter/Consoles/SupermatterConsoleComponent.cs @@ -0,0 +1,107 @@ +using Content.Shared.Atmos; +using Content.Shared.Backmen.Supermatter.Monitor; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Backmen.Supermatter.Consoles; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedSupermatterConsoleSystem))] +public sealed partial class SupermatterConsoleComponent : Component +{ + /// + /// The current entity of interest (selected via the console UI) + /// + [ViewVariables] + public NetEntity? FocusSupermatter; +} + +[Serializable, NetSerializable] +public struct SupermatterFocusData +{ + public NetEntity NetEntity; + public float Integrity; + public float Power; + public float Radiation; + public float AbsorbedMoles; + public float Temperature; + public float TemperatureLimit; + public float WasteMultiplier; + public float AbsorptionRatio; + public Dictionary GasStorage; + + public SupermatterFocusData( + NetEntity netEntity, + float integrity, + float power, + float radiation, + float absorbedMoles, + float temperature, + float temperatureLimit, + float wasteMultiplier, + float absorptionRatio, + Dictionary gasStorage) + { + NetEntity = netEntity; + Integrity = integrity; + Power = power; + Radiation = radiation; + AbsorbedMoles = absorbedMoles; + Temperature = temperature; + TemperatureLimit = temperatureLimit; + WasteMultiplier = wasteMultiplier; + AbsorptionRatio = absorptionRatio; + GasStorage = gasStorage; + } +} + +[Serializable, NetSerializable] +public sealed class SupermatterConsoleBoundInterfaceState : BoundUserInterfaceState +{ + public SupermatterConsoleEntry[] Supermatters; + public SupermatterFocusData? FocusData; + + public SupermatterConsoleBoundInterfaceState(SupermatterConsoleEntry[] supermatters, SupermatterFocusData? focusData) + { + Supermatters = supermatters; + FocusData = focusData; + } +} + +[Serializable, NetSerializable] +public struct SupermatterConsoleEntry +{ + public NetEntity NetEntity; + public string EntityName; + public SupermatterStatusType EntityStatus; + + public SupermatterConsoleEntry(NetEntity entity, string entityName, SupermatterStatusType status) + { + NetEntity = entity; + EntityName = entityName; + EntityStatus = status; + } +} + +[Serializable, NetSerializable] +public sealed class SupermatterConsoleFocusChangeMessage : BoundUserInterfaceMessage +{ + public NetEntity? FocusSupermatter; + + public SupermatterConsoleFocusChangeMessage(NetEntity? focusSupermatter) + { + FocusSupermatter = focusSupermatter; + } +} + +[NetSerializable, Serializable] +public enum SupermatterConsoleVisuals +{ + ComputerLayerScreen, +} + +[Serializable, NetSerializable] +public enum SupermatterConsoleUiKey +{ + Key, +} diff --git a/Content.Shared/Backmen/Supermatter/Monitor/SupermatterStatusType.cs b/Content.Shared/Backmen/Supermatter/Monitor/SupermatterStatusType.cs new file mode 100644 index 00000000000..a5a2419ee84 --- /dev/null +++ b/Content.Shared/Backmen/Supermatter/Monitor/SupermatterStatusType.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Backmen.Supermatter.Monitor; + +[Serializable, NetSerializable] +public enum SupermatterStatusType : sbyte +{ + Error = -1, + Inactive = 0, + Normal = 1, + Caution = 2, + Warning = 3, + Danger = 4, + Emergency = 5, + Delaminating = 6, +} diff --git a/Resources/Locale/en-US/backmen/supermatter/supermatter-console.ftl b/Resources/Locale/en-US/backmen/supermatter/supermatter-console.ftl new file mode 100644 index 00000000000..fbf1d4fbfec --- /dev/null +++ b/Resources/Locale/en-US/backmen/supermatter/supermatter-console.ftl @@ -0,0 +1,44 @@ +supermatter-console-window-title = Supermatter Monitoring Console +supermatter-console-window-station-name = [color=white][font size=14]{$stationName}[/font][/color] +supermatter-console-window-unknown-location = Unknown location +supermatter-console-window-no-supermatters = [font size=16][color=white]No supermatter detected[/font] + +supermatter-console-window-label-sm = {CAPITALIZE($name)} + +supermatter-console-window-label-alert-types = Supermatter status: +supermatter-console-window-error-status = Error +supermatter-console-window-inactive-status = Inactive +supermatter-console-window-normal-status = Normal +supermatter-console-window-caution-status = Caution +supermatter-console-window-warning-status = Warning +supermatter-console-window-danger-status = Danger +supermatter-console-window-emergency-status = Emergency +supermatter-console-window-delaminating-status = Delaminating + +supermatter-console-window-label-integrity = Integrity: +supermatter-console-window-label-integrity-bar = {$integrity}% + +supermatter-console-window-label-power = Internal Energy: +supermatter-console-window-label-power-bar = {$power} {$prefix}eV + +supermatter-console-window-label-radiation = Radiation Emission: +supermatter-console-window-label-radiation-bar = {$radiation} rads + +supermatter-console-window-label-moles = Absorbed Moles: +supermatter-console-window-label-moles-bar = {$moles} Moles + +supermatter-console-window-label-temperature = Temperature: +supermatter-console-window-label-temperature-limit = Temperature Limit: +supermatter-console-window-label-temperature-bar = {$temperature} K + +supermatter-console-window-label-waste = Waste Multiplier: +supermatter-console-window-label-waste-bar = {$waste} x + +supermatter-console-window-label-absorption = Absorption Ratio: +supermatter-console-window-label-absorption-bar = {$absorption}% + +supermatter-console-window-label-gas = Unknown gas +supermatter-console-window-label-gas-bar = {$gas}% + +supermatter-console-window-flavor-left = ⚠ Do not approach the crystal +supermatter-console-window-flavor-right = v1.1 diff --git a/Resources/Locale/en-US/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl b/Resources/Locale/en-US/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl new file mode 100644 index 00000000000..e041bb3a53b --- /dev/null +++ b/Resources/Locale/en-US/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl @@ -0,0 +1,8 @@ +ent-ComputerSupermatter = supermatter monitoring console + .desc = Used to monitor the status of supermatter crystals. + +ent-SupermatterComputerCircuitboard = supermatter monitoring console board + .desc = A computer printed circuit board for a supermatter monitoring console. + +ent-HandheldSupermatterConsole = handheld supermatter console + .desc = A portable console that lets the user monitor supermatter crystal status. diff --git a/Resources/Locale/ru-RU/backmen/supermatter/supermatter-console.ftl b/Resources/Locale/ru-RU/backmen/supermatter/supermatter-console.ftl new file mode 100644 index 00000000000..fea6b845f07 --- /dev/null +++ b/Resources/Locale/ru-RU/backmen/supermatter/supermatter-console.ftl @@ -0,0 +1,44 @@ +supermatter-console-window-title = Консоль мониторинга суперматерии +supermatter-console-window-station-name = [color=white][font size=14]{$stationName}[/font][/color] +supermatter-console-window-unknown-location = Неизвестное местоположение +supermatter-console-window-no-supermatters = [font size=16][color=white]Суперматерия не обнаружена[/font] + +supermatter-console-window-label-sm = {CAPITALIZE($name)} + +supermatter-console-window-label-alert-types = Статус суперматерии: +supermatter-console-window-error-status = Ошибка +supermatter-console-window-inactive-status = Неактивна +supermatter-console-window-normal-status = Норма +supermatter-console-window-caution-status = Внимание +supermatter-console-window-warning-status = Предупреждение +supermatter-console-window-danger-status = Опасность +supermatter-console-window-emergency-status = Авария +supermatter-console-window-delaminating-status = Деламинация + +supermatter-console-window-label-integrity = Целостность: +supermatter-console-window-label-integrity-bar = {$integrity}% + +supermatter-console-window-label-power = Внутренняя энергия: +supermatter-console-window-label-power-bar = {$power} {$prefix}eV + +supermatter-console-window-label-radiation = Излучение: +supermatter-console-window-label-radiation-bar = {$radiation} рад + +supermatter-console-window-label-moles = Поглощённые моли: +supermatter-console-window-label-moles-bar = {$moles} моль + +supermatter-console-window-label-temperature = Температура: +supermatter-console-window-label-temperature-limit = Лимит температуры: +supermatter-console-window-label-temperature-bar = {$temperature} K + +supermatter-console-window-label-waste = Множитель отходов: +supermatter-console-window-label-waste-bar = {$waste} x + +supermatter-console-window-label-absorption = Коэффициент поглощения: +supermatter-console-window-label-absorption-bar = {$absorption}% + +supermatter-console-window-label-gas = Неизвестный газ +supermatter-console-window-label-gas-bar = {$gas}% + +supermatter-console-window-flavor-left = ⚠ Не приближайтесь к кристаллу +supermatter-console-window-flavor-right = v1.1 diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl new file mode 100644 index 00000000000..a1ae85bc105 --- /dev/null +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/backmen/entities/structures/machines/computers/supermatter_console.ftl @@ -0,0 +1,8 @@ +ent-ComputerSupermatter = консоль мониторинга суперматерии + .desc = Используется для мониторинга состояния кристаллов суперматерии. + +ent-SupermatterComputerCircuitboard = плата консоли мониторинга суперматерии + .desc = Печатная плата компьютера для консоли мониторинга суперматерии. + +ent-HandheldSupermatterConsole = портативная консоль суперматерии + .desc = Портативная консоль для мониторинга состояния кристалла суперматерии. diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 68312049a8e..d6b85707eff 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -176,6 +176,8 @@ - id: ClothingHeadCage # backmen edit - id: PrinterDocFlatpack # Corvax-Printer - id: SupermatterFlatpack # backmen edit + - id: SupermatterComputerCircuitboard # backmen: supermatter console + - id: HandheldSupermatterConsole # backmen: supermatter console - id: MetalFoamGrenade # Hardsuit table, used for suit storage as well diff --git a/Resources/Prototypes/_Backmen/Entities/Structures/Machines/Computers/supermatter_console.yml b/Resources/Prototypes/_Backmen/Entities/Structures/Machines/Computers/supermatter_console.yml new file mode 100644 index 00000000000..40793ceaff1 --- /dev/null +++ b/Resources/Prototypes/_Backmen/Entities/Structures/Machines/Computers/supermatter_console.yml @@ -0,0 +1,94 @@ +# Supermatter monitoring console — ported from Goob-Station PR #5788 / #6808 + +- type: entity + parent: BaseComputerAiAccess + id: ComputerSupermatter + name: supermatter monitoring console + description: Used to monitor the status of supermatter crystals. + components: + - type: Computer + board: SupermatterComputerCircuitboard + - type: Sprite + sprite: Backmen/Supermatter/computers.rsi + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + state: supermatter-0 + - map: ["computerLayerKeys"] + state: supermatter_keys + - map: [ "enum.WiresVisualLayers.MaintenancePanel" ] + state: generic_panel_open + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#0f704b" + - type: GenericVisualizer + visuals: + enum.ComputerVisuals.Powered: + computerLayerScreen: + True: { visible: true, shader: unshaded } + False: { visible: false } + computerLayerKeys: + True: { visible: true, shader: unshaded } + False: { visible: true, shader: shaded } + enum.SupermatterConsoleVisuals.ComputerLayerScreen: + computerLayerScreen: + 0: { state: supermatter-0 } + 1: { state: supermatter-1 } + 2: { state: supermatter-2 } + 3: { state: supermatter-3 } + 4: { state: supermatter-4 } + 5: { state: supermatter-5 } + 6: { state: supermatter-6 } + enum.WiresVisuals.MaintenancePanelState: + enum.WiresVisualLayers.MaintenancePanel: + True: { visible: false } + False: { visible: true } + - type: SupermatterConsole + - type: ActivatableUI + singleUser: true + key: enum.SupermatterConsoleUiKey.Key + - type: UserInterface + interfaces: + enum.SupermatterConsoleUiKey.Key: + type: SupermatterConsoleBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface + +- type: entity + parent: BaseComputerCircuitboard + id: SupermatterComputerCircuitboard + name: supermatter monitoring console board + description: A computer printed circuit board for a supermatter monitoring console. + components: + - type: Sprite + state: cpu_engineering + - type: ComputerBoard + prototype: ComputerSupermatter + +- type: entity + id: HandheldSupermatterConsole + name: handheld supermatter console + description: A portable console that lets the user monitor supermatter crystal status. + parent: BaseHandheldComputer + components: + - type: Sprite + sprite: Backmen/Objects/Devices/tablets.rsi + layers: + - state: tablet + - state: supermatterconsole + shader: unshaded + - type: ActivatableUI + inHandsOnly: true + singleUser: true + key: enum.SupermatterConsoleUiKey.Key + - type: SupermatterConsole + - type: UserInterface + interfaces: + enum.SupermatterConsoleUiKey.Key: + type: SupermatterConsoleBoundUserInterface + - type: Item + size: Small diff --git a/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/meta.json b/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/meta.json new file mode 100644 index 00000000000..a5ac58d0b39 --- /dev/null +++ b/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/meta.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Base tablet from Space Station 14. Supermatter console icon from Goob-Station PR #5788.", + "size": { "x": 32, "y": 32 }, + "states": [ + { "name": "tablet" }, + { "name": "supermatterconsole" } + ] +} diff --git a/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/supermatterconsole.png b/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/supermatterconsole.png new file mode 100644 index 0000000000000000000000000000000000000000..c3350e8f33544e489a9ab4a7cb6c822104397d1b GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVDaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheoCO|{#S9F52SJ!|$HeTnKn?AgArU1JzCKpT`MG+DDfvmMdKI|^K-CNk zHue<-iOJciB??KY>6v-9>hE{&S69ePu~iQ@^)>JLw)`r|23QrX=bnrWhOQrWhNin420}7^N8|DM76- z$xK7opH~bG24H~bgJkp#^$b8x2C=RDi!xJzt^iqSXJ`YKM-j8p2RQ`EVRjG^pkIM3 zc*uZ)2_CwsdB7ko2Zo)_lo{N>a6j$o;uw#jxfdgfh{R4cgL znsJ=z$(LOR&Rq1kpll#5^uoOf%Ab8~U=Pk*$=PTX+PTu^%QboFyt=akR{0HARA3IG5A literal 0 HcmV?d00001 diff --git a/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/tablet.png b/Resources/Textures/Backmen/Objects/Devices/tablets.rsi/tablet.png new file mode 100644 index 0000000000000000000000000000000000000000..417d44fbcc00fadef9961a926bc30d7b2b4ca088 GIT binary patch literal 348 zcmV-i0i*tjP)VQ!P3~mSL=$rgU=3lvZhEZKp4=i@``Xxr1 zIl$S?7i`JPm(MZ8VPY^fq=LP>HlsKk!vP?J4NWZ>Xa#^C_zwz6oDP72J2$UU-EtLm zEetUX2Y`b3-09vhj~_Bam!EB)JVS=5b!T8WpCq9hQ-mWDK+BBs^s;0>@YS*rb@u598=jNV!?@4lRT6Mpd zo7{Z&eBbvw-#!1nFTnpCyXY@%et*6iN~c9}m%dbWAdnAjet*8FcMy&42H5R3QiLBOWK_Mp+-fGh8xGgZgc4?mZER}etI z0;yD5@j9^XY5hjuE4%`1kl*TD^++!#VRJYQy=HScg?LML|BFM=qDslsRzV0D5`{5x#Q+3$d6f5g4DFLQn4?hKE8} z+`4Z}7aIi9^H!@B7K;UY_wF^M-6v0;NQ4$gmns3fckec=7jn6rZUnH9&zrw8-cb=i zBbUpSG;r#*r3O_3Ha0f!^yyPW4A|J%C`o>OeH~V-71?ao5dEZ07Gwl?eF4PhrX&Ma zR#pr*!0QWOepcj`HyaWGWE$$}9jxj!RE*|BM@-SJ*0w|dnTEdn>;uybxHobaXU|`j zO+z$-!EQt%VN*#62D@>cia_QVAXqGh@5F2lr{;JdOlpp9g0pLw>_yz;69klmBfS3OkcM5qA8c>sY~uK$NT5Nz-0G(>SZn4cBl^#v5Ofl+N2LqUL9y$=AB<70XvU4U}SYB0J0 zjFKXRlxsc1(_rLYK+ykDA>6GyJQ-^$M`o2RkXn4U})Au0WgLpMUG^ z_W%@DagFWs*u9YX@Kk+u&2myOTyD4-W4{=;5GB4M<&`m45b)IyuzpKodPBN7S2 zf7mPC=Uqf`5x_!xZfe^}AkJ5k&t$FywVWb5Dw02JY4rmDv*N={(9SQ!VrV{egt^1Zrw(xN`?;gJQ**p?&-IK@bEi#A1lfOyJ(gU8YG%!(-FWkJ#JS7 zlvCncas=><=Gzqk<(wjOML?;{?u0Rb*JiE=ppU4<2T}C(okEkxgKRd7)zwvne6=EA zb#)clY!*!(5BmB}L3|KZ*sBMwa{1z8>gdb_PM^7iufO^PONj*b@82)`y;>2lwzh@_ zw;QL=T*9qe-)Od_770nBZvv+0mDNf#l2d)(h-^jy1uv?5kNa%=Q^mc?@?;=+4)&f zdq>N(1Xc7p_j({eQN7MOId{G?)wHl>m>RbmU5(v`ENvYRJFP1Ws5Lr3` z=$e0C=XreLng*jY6Y%;1%!cSlc%kzZ^!1%m$mso3q!^2dCA@R-3Ugjx{B2p+`A4ZF zcYaZmfLgl~J-vfc!c>ohU&ek^ZL8Frzt%rj)a(EX&QFeyp?~0lZiJ{3@F4NLX{Z-T7?W-^#dd1`(JB+%YG-yx0EFS+*WN& z9rQDAW-=L>Q!Jg6<6{cjcu-D(#3-$Zepea9-9~x&_~K{I(QUJIZrL3iHt4) z?TyXN*T{{E%ONlIq4g7%Ab5vb`bOP3LuoG$8fSw?K9&VK;0>jegbk3m2H0000< KMNUMnLSTaOop+%C literal 0 HcmV?d00001 diff --git a/Resources/Textures/Backmen/Supermatter/computers.rsi/generic_keyboard.png b/Resources/Textures/Backmen/Supermatter/computers.rsi/generic_keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..24428eb4e3682534d7583f152aaa364ea79f9115 GIT binary patch literal 1201 zcmV;i1Wx;jP)K#q!=MZbds*kp>cf=k9+W*ag;=o$S|0=v zZK*z3`_z{?iqdUDDU3=PMY@c{(5z!ugRZ&Nqh6WF(W|H%A05#g zj25R(hgHkd&sG6U6htF9#sC1|YxH7p;0&7l0is-MFnUbhzNRQ!NiAb&_y$qtCu z0Kkcb6TreogU(`7|I^M=JGll>oB?t^qT2IKQv5#E+0T7e&ZIO_S-ZQ!6p^aE!%+s% z! zH1`^$i1&oFG>#nml1g7is2`6v`2&g_Veg*3Sp0nf9*^7bd5_19CVv2J?HySC_c>8# z_Q=}`Tjqj9rTE|tK>ZU~Ni8G4zD|^-pFY9aA1+`x+ONBU$78XY;b?zhAaWU3uUu5< ztO*%-8=I;2m%0HO+^>-8B){E5kLqk`{Uy`!|MJ>yw=|*t34|kKC?tv-sA78h)Je-a z;xkgMaAb^9FUwFj0LO8tX)W#uiz&x(wsfQ#HLWgNIz~`80MGM^8NltGz)jUfc(FwVX^ca!~)Mr4G zKY-wY5K`VGQr;wj2SR9T@4(e77cmgItQ+hEL!r`NU|V~KLdWb4a4S^orl|Bp*aXIu9QFOrtxdplSf40h+mg zm`p4g>Ns`=)Om{cgVwVz001-76N)xQ(I9}rKPxe!T-+uT^%)EXgF%V+2BR01_nlV zPZ!6KinzD84EdT2M2>wtl=o;sq4`%Gs|qn zIdGF`?m#}ZdL8&{1e0N|GBIsM%58kV%qJ?{HL#Ud;9JApKTYf{%rbkr1S3+S=FAmB{^%Y kXHCll=>ThEae2TylPRI>R|8u;Fq|1YUHx3vIVCg!0QoDTu>b%7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Backmen/Supermatter/computers.rsi/meta.json b/Resources/Textures/Backmen/Supermatter/computers.rsi/meta.json new file mode 100644 index 00000000000..651e5dbcea8 --- /dev/null +++ b/Resources/Textures/Backmen/Supermatter/computers.rsi/meta.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Generic sprites taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894. generic_panel_open made by Errant, commit https://github.com/space-wizards/space-station-14/pull/32273. Supermatter computer sprites by AftrLite(Github).", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "computer", + "directions": 4 + }, + { + "name": "generic_keyboard", + "directions": 4 + }, + { + "name": "generic_panel_open", + "directions": 4 + }, + { + "name": "supermatter_keys", + "directions": 4 + }, + { + "name": "supermatter-0", + "directions": 4 + }, + { + "name": "supermatter-1", + "directions": 4 + }, + { + "name": "supermatter-2", + "directions": 4 + }, + { + "name": "supermatter-3", + "directions": 4 + }, + { + "name": "supermatter-4", + "directions": 4 + }, + { + "name": "supermatter-5", + "directions": 4 + }, + { + "name": "supermatter-6", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-0.png b/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-0.png new file mode 100644 index 0000000000000000000000000000000000000000..d8301a402318693e64ed748dfe0e4af16e4801fa GIT binary patch literal 1100 zcmV-S1he~zP)EX>4Tx04R}tkvmAkP!xv$rb+0Yt2!am~cfm=~MM^w3DYS_3z~O%U_xy)*&jo~Lm1$N_4A6Aj zOef+}F25>;UJ*bLBITUSEF+m&%)&Rm?x~vMF3NlP*Zmm-YQbVaKq8JY!?cMvh-Wr! zgY!Odn3ZLf_?&pcqydQ^xvqHp#<}FOzZ+&}ul&_p#%&PJrMuaHV(rwI(q8NqVEB zMUH^MZQ$a%qbYm9ey74#I1W72#bI^lp%OUj2wZ`{J2<^4$ z-M#mHSGe~8hG7_nVg4%Crbfs2==ipVtIKl$z+gCvU6+oc0eL63^+LKIl&2jlNE3)c^40DyjX9BUW%hU!O^S^>&YtGt$MUrg2tP!Xbn z#bozEIlf2LR(*9Yj4$YS$E)MTwr?OPLA)< z&A*FSn=pEEJ^26lrnLqrUxP*SS6;&m!!QiPFbu;m48t(Yufg%X((E(oj3x7)V6{~* zg%OfTlzma}W&gm}CS?tPM@40QMHU;#?gy=1;YM>=m=c8Nx8(zNA+rJmMJOQ;&kJ(N z3kW5cA`j0Ca!ZVuOg`kd?vay|EaO`z-wbh~s;7`g%ug>KGeHn>uv*dY8-OwSNe`KfPG9-dy)F zdsDj7Ta!{OT=opZFbu;m4D1fBj-=8xgh>Hq)$4rN$LW=%~1DgXcg2mk;800000 S(o>TF0000w$ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-1.png b/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b74452db5242807b77dca604e08cb1bc45f20699 GIT binary patch literal 1144 zcmV-;1c&>HP)EX>4Tx04R}tkvmAkP!xv$rb+0Yt2!am~cfm=~MM^w3DYS_3z~O%U_xy)*&jo~Lm1$N_4A6Aj zOef+}F25>;UJ*bLBITUSEF+m&%)&Rm?x~vMF3NlP*Zmm-YQbVaKq8JY!?cMvh-Wr! zgY!Odn3ZLf_?&pcqydQ^xvqHp#<}FOzZ+&}ul&_p#%&PJrMuaHV(rwI(q8NqVEB zMUH^MZQ$a%qbYm98+_5Xb*MVI+uzt2F88 zOlYsBNRz_4B2v;(r}jMokC3$CB`8iQXplmEE#NNC5F~-kIVQjM;c#ZA5%uhm? z^@f?9FNd841VIo4LHMdzy9OQKqqCbDt~zZ1z-T;)+~?zq!4hXTH8h%Qt&sU^1&hHF zZ;#JA{-3_%!bsYv4!oW(513(y9n7>xEuP`Xxx;JPIz`Z^iPhh(Q+oi-f0N5^} z(Ohd^wo4~mf*BRvjWDn<45pu-EL`7T0sw~nX=Gf@o$J4<)DfW7TIaR+_-eL|03CBw zu$mpNl;e9;ZPhoQmGKqB{&aJ_n!Tgis>5~(SDiLS<4NK@3@of>?>lSI-Ux^Yjd?vj zH|q%ChK?6u+udF(2XK<}N$Y@vBS7~=;0h><^S@E`N@U@5DU<9qbu;d^9E89iPPe*OHtsR26n)d1~d zuxP%@V^|ObK@bE%5ClOG1VQ*`aD1;c`%EUfq}~&(w(6xYLcED`Ec$N7U-;OhtO4+- zsIIT-Vgu>@V6-dTXs!#31m*dC{eVL--WIoA%H0TR@mXwlr%nORha&0o@K~~1Jdj~y zB;K((C0WW!9RcxEZ!{ z6d3lWpM}Uuw8!&+8@{jl0^*V>c7@jW_!x}z@j z==|v}w;bRy3;#Xh_#R!n+)|f%xhc}69$mcLX0mwWe}vv1pRL7UY5FxsQ@W$KdH)v3 zOO_xAf*=TjApEEN0p^-*+&w}3-T(jq4rN$LW=%~1DgXcg2mk;800000(o>TF0000< KMNUMnLSTY48XYD8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-2.png b/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-2.png new file mode 100644 index 0000000000000000000000000000000000000000..8096e8fc4ae698ac235a7a4d4e7b69a0b04ddbd2 GIT binary patch literal 1167 zcmV;A1aSL_P)EX>4Tx04R}tkvmAkP!xv$rb+0Yt2!am~cfm=~MM^w3DYS_3z~O%U_xy)*&jo~Lm1$N_4A6Aj zOef+}F25>;UJ*bLBITUSEF+m&%)&Rm?x~vMF3NlP*Zmm-YQbVaKq8JY!?cMvh-Wr! zgY!Odn3ZLf_?&pcqydQ^xvqHp#<}FOzZ+&}ul&_p#%&PJrMuaHV(rwI(q8NqVEB zMUH^MZQ$a%qbYm9m596vzKL5u{Ry3{{tI zY=N$wJ9I%+bVH?fV`ff20UzPImM_6@>cE0j=39Yme1;-XB!c7{sDtC+5aN)eckd@f z4o+l0|KGDsYy$*A5ClQ|uNeC>E!Sb^4`;Z$y#@e`#*@%!a}mrp;j5ERqiZ zrp-`qY$6u`)EgU^HanI5+Ei2ze9yqpn|}Q=(C)SX0K@(?l+WK?D2gh31Skbic}>n= z&vp^8m{)=n*me;fJ?mL=qb%29)n+aD&Bf;o`_tflJ^MtpS%Yb_pguYEJp=36=iV_W zHUdJGN?w=W0`D_jKLc%4O0tySTRd%rLdc;hebeXVk|m&J>Sge*MI$z)%k-EBdq zA0A7!S&M9tq$LvpM&n6l1|^~0ZAJDd(~?>Mekue&WHa>C@C{f%|NKE}0ovVGP_bxG z9*Bbbo}m?eT3tXEAuqCOJP@-zeu!az`ZWq02e-#hfhc@oU4W;i&2l{l#03{^NQ+h% z5D!b_JqrNaD9m&0iD0=7yL^3O9oA!)uTQ!40kO`)zegEX>4Tx04R}tkvmAkP!xv$rb+0Yt2!am~cfm=~MM^w3DYS_3z~O%U_xy)*&jo~Lm1$N_4A6Aj zOef+}F25>;UJ*bLBITUSEF+m&%)&Rm?x~vMF3NlP*Zmm-YQbVaKq8JY!?cMvh-Wr! zgY!Odn3ZLf_?&pcqydQ^xvqHp#<}FOzZ+&}ul&_p#%&PJrMuaHV(rwI(q8NqVEB zMUH^MZQ$a%qbYm9o?m5QhJDIwNKx7G|O` z6Ud00g_*e|$RtRp*f1!d$jFaSF);;+X9CPTB7TArF`1}9v+C-sQ*51b(zN@{WMto6 zytn`E-No4h1VIo4LHJ+!($*LTZn;{|uv{+z0BMvq`gwaCKG z`TeZ%!|{A)&`A%8HUqLlf$Wgf?7+A2D}XL)YWp!O>Naw? z?RSpZ^&*6UTQ0o6cKEUbP%lTazwoN;B>UI~4G2&NOzp^Cpdbi> zAP9mW2!bF8f*>e_fm@C}2f}17+S1rZEIeltBB(`_Tj?!;=R2c|K+c=g9(xM_j4Q$r z&I5)50M-BLZHqw8t2Kf?7z>a_X)S^+$oaNH?5!9JK!wmDqH!(cJio-zm)-(Uy^oin zZK2vUxqr}GfT!n&3lVIgI&YGFMoT~y!4^X~4;U0j_uHgCf<7vbe+FFm(Ut%`J>Ri< z4~PWkbLflF63|_r`)v4a5u1C@1!3TpyT`loeA<+|$2)6(2yvUEe;)}0w|sq>m*>;Q z+CC1VIo4;UDD>FtBWM8uDNy0000EWmrjO hO-%qQ00008000000002eQEX>4Tx04R}tkvmAkP!xv$rb+0Yt2!am~cfm=~MM^w3DYS_3z~O%U_xy)*&jo~Lm1$N_4A6Aj zOef+}F25>;UJ*bLBITUSEF+m&%)&Rm?x~vMF3NlP*Zmm-YQbVaKq8JY!?cMvh-Wr! zgY!Odn3ZLf_?&pcqydQ^xvqHp#<}FOzZ+&}ul&_p#%&PJrMuaHV(rwI(q8NqVEB zMUH^MZQ$a%qbYm9bRk6vzMm6cr@{3n$2e zqIT^8vUNbp)C+X!#FPtEy}&nh%F;U^bx0=;kTp@nk`r`cXhc=XUFy^rOaLdC@9XVH#I-A_#&Y2!bGp|CBG0)@#N~sN(nl o000hUSV?A0O#mtY000O800000007cclK=n!07*qoM6N<$f|g|l!vFvP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-5.png b/Resources/Textures/Backmen/Supermatter/computers.rsi/supermatter-5.png new file mode 100644 index 0000000000000000000000000000000000000000..21a0631e777b6fd549df0ff793494318903a7070 GIT binary patch literal 712 zcmV;(0yq7MP)Px#1ZP1_K>z@;j|==^1poj7SV=@dRCr$Pnz3#aF%X9LP{c_T>GJ}dL`_EpU4&BR z1!!ofcmc!#sGg)N{*XRQajk9SwC{z~$o zY42f^1fFj1J7rW!@^m>&-Ss zUw=tXn)V)DSqt^6Ka<<105PV~`Sy$NNg9K5PVM_wAYukgO z72w5Z-HX}A=<9ET37IenFTT%gW5oJ91K^z?WC02FW*eigzYQN`#4kZFW*Z~czgGp| z*a!y!)^^d?-v&YRLHF7Bdb5oY>z~};Wnd5ZaQ?LUwZqmu6ZgFR`r|lzKxr7bVVOrl zJ#=NbuU`lugb+dqA%qY@hyieJ*E|*{XDrEQf*eFnAqe3m%21L6!09Zv!X<-%Z^jUw z6b~Ue0GzmT>|Cl6Ec15sol0^5%yEfI@R_$nq>^0$z7od5XWrUWk^=yK06FV$C_eK! zlZWI0*nQuj1~P=uXUg59ksJWB5C&l^!pY}UU)B-`gD@6{nmi;2fZN(blwc}8@cxUD zp=2!qYq@L%e-vEdk)O6F!3-o5_88JNEwq&7%-P2qATF0000Px#1ZP1_K>z@;j|==^1poj9X-PyuRCr$Poj+(4K^Vn17Dg28ENnzU5)rIy6dM&m zK_XZv2v!zam`V#P5wWtc(O766IgJEaC9d5JVpPAjewQwh4(v|%-@Kbix5V0~ zf6gZuTLs2VeSemFK9`I8w)G}XZb=TF-k9q43${@mwZX>;uWc7T1_Y@f%6k-`AjtVe%rG)bFl z2e6k>bvzH7_2`f7*_1Zd4qz{%>Uc~F1Awf(_j)F6%zQN0&aq`w9nS+QFS~frE7qN(}}GBVAlXU#vSjo23RvVINWz8!Te7`RKRLZ0eLV1ih^$Kt5N}72LZVC@I-bhc8Fi*WA%qY@2qA5rxXQ zVFy2i#|#3LAcF`(YXbmvLd+mQsslY#(;&1q08k{v3{Q#2bR2+00H zTwiGgAi`+q)1j`NV}Z?j+vo8?LYX+Wn1(_J0oTthuFp3Jg#n=R0QxuqnrjDDCvTD= zWnl3w((Q);*U$Bk0jW!209amtnUCh$0W%!?wAub@RKSKEQzLje0@y%=V*P{bBbT0k zO#3zvodZw6cqYJIQDzDX?ile*fV-m1RExq2XbX7j1aF_j0=L$90kQMM{AwCB{Z>>6 zA%qY@2qAoP5_h^%ci3ILrSLy-XBd}#E26$!(DcL1#O zDUb>YGarfssxR7zR{-dI2%@Y^JQQ?3WinQ))Hb{VV5c28KXe7a`J8szUIAWe;RVK%{3*xi`=Xdfh2*h$eX}j}*cK|r;pb|n0V*#ff zR6?7Dv0edy^KlEx>1b*Q9uG z!4GZ&4<^TC0|kQ>Gle-s+E@fS4qjg~efv^tvu#f0zw4g8Eqi;q>O0@HvaXI3?zg=i zr}SNS*S=U?t}mGTJf~JLH>&UpADi(tyS$kl#p-i^YRs4&uzEK4F5g>cf6nESkH32E zW^-Qt>4|0{?(Y)}j7}Xq{r!pIJ!k1Hrq5HheB8#A6L9La^yXjD#~cI?$|%Rh+O58P z+u-|-tgnm?`|lfzNZJSr3I;9~-Ip!3#d_mIi)Tsij$Tb)A~rp}WX?RPm$J%!bv^Iy z+wYY*61!)*rSy?KQEq287*rTPHVTweUer6YcWLntjmp%Y@F{?=0a{f`UbiZLI-oSpRM5xyS!Ysr`KSswqqLL?5KAloFTQa{O_)SL@yF zhpgQ~pPX3y>G+d^hc{$yTE$%Ueqz$s`zaAbS9$S;+m zBmGB0&z@vgDDVH0c2ZwkVd7)!<=lSS45z{vu54o}c$liaVEW-eR+E4G8;th|=U9a? y0KF^t(`Ti=PEky4lHmeZVD$2(MtG+A`Z8z%*&IL&0+)g(gD6i|KbLh*2~7Y7*y!Q_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Supermatter/supermatter.png b/Resources/Textures/Interface/Supermatter/supermatter.png new file mode 100644 index 0000000000000000000000000000000000000000..e6abc877170b5182be5ef336332c4f69f4720882 GIT binary patch literal 3070 zcmVEX>4Tx04R}tkv&MmP!xqvQ?()$hjtKg$WWc^q9Wo{s#pXIrLEAagUL((ph-iL z;^HW{799LptU9+0Yt2!cN#?t+t|i*kxkDNhl#~f7t3AD%7#ijO&n2Fjq-(@ z%L?Z$&T6&J+V|uy3>LJN4A*ImB7r3&k%9;rbyQG=g(&SBDJIf%9{2E%IQ|s5WO7x& z$gzMLR7j2={11N5)+|m_CX>@2HM@dakSAh-}000TqNkl)f#EA@w? zoR};M;#%4NP4PfX3jV7^-&wC)(_?&?3<_d0@GA|gzZ!{gr2NKGa1s9TQC}(g@lH%e z5m$BK-6zWACx-?j_m^)pez;ce#J6o_Bzp7ICtOK6M%@Z|@w#Z!uJ%%?=|BwCFYN`O z=z%*UQC}^3-_}}OYrcz8e;B3Zc*NeZt6XMyY*9`bbx5^fm-4k`c<`^gsja#&VbhDz zSAR4#VNH9tLHo|-GQ(p-jomvr4?HBqGUOn7I<0^Gv-c5PTnVG}WyJmCtbby(w)*(-SfU0Vknn2Z5YHM2Q zXzruEJB+HSxaK=iRn6jpP;uRyfGE5RRj6pHhHJi)u7+L|TMUXKh|wetyLsh0(9lrN zV6cm4)`fB3`F-@Jy{LfiL;%AyaOW79rip1fBDq!#iQ+B8HL}~X89K@sH*`ZsH6(@^ zN4G!BA9lpD<+(nDp2)NXozV^`ih{##@~8X4xXM>E+`JFpNsAAz`A&pxBFE*sy2_8a zsglLNycZS5}F%1GX;tNdi=&A>ffgZ#a~MidcesRlE>>k-;RmG6c3OEG z%b#jUd`AQLjs=nCBbcU%Yi=BdX+*d%EW;KGCwHUdmPOX-hE7p&wpB}x29Vz6=%<#^ z^Pi!s7MZUFb13j8~^(B3=D ziwEa%ez5V90|Ff(1UEJq{)H!V8r26{SaL@WhyU^h zx0k0z2&#?Mf$r@)5kgpJLa7H$)tJ3{H61(N#2-+2c(a|<3>Y3%u3QINdV<`xIGy%m zK~$k)mG@*LtM;F!s5qOC_xJ%=yfh^;UvuCLLWD6*N2CsXv27dv-Y~BnnaA+R zX+{k-x+5~t&iRC*wBw=SI~qW?SPb$@UURR->n+p$x5cZJ4|}TO5AHIuBiEvC8@Oe zg0%U9QMQsLsg#s@X!8Zhb~$PF1zA*<%2meeP#uv z%6oV+H9q~@7IZ_$lR1srnpRZRQpu7#a!4tN#Wmka&xtVA2U@Vj*wGCuGELR+WKN^i z7sQh}6*2ciRP#CX)+)MME&!=lZ%R*{Y9=j7<4|oMnQ3ukr8}tF-yNy{Gjmf&DTs~e z+%-3jqT*~?8oFq043U-YKsR(+eL=F)9i$Y*()G@_(3|$s(|C@%pE1#g%t)%V@pT|B zUg3#V&186{vT$V7Y9@-aBM;BawH(sC<(MblAJ_YZDJk_}m9d_jtqrLpCO6SVn)eDuL7+^diBokeN9c)w)SP|%0WiA|~9CV1-Z zFG!u0h&`{I!KOVZIZIh=KS1|!KlXShnR9Hor)yLnXu}p`M~FA+I&bpE$NBtlK|LK` zjgSi-p84`K1@w; z_M9ZCc|WUuF+!a4^n^A}1(@ob@}w{9#go!S^3-_jW;=uZ1Ni!N9)C?^_fd`3P%gPC zeiD=IOi#8G2#vD-6^;JBL4pIrw2$0GnYRxIK9t#SF-5$`+D8YeJD<-VU+&=C`9Yjh zQ|asLB`GP1zP?_9p}=o6fbg&&5SBc=+0Ju68=~#3b+Wx&RY33g26O`g$qMUVvZ1Ob zb3D1!H#RXiWH2;pRkgv8*ETgZMw4*Qp4~U(XSLu44{iKc(Ty1Y1wG53VwFsE-v9sr M07*qoM6N<$g5534(f|Me literal 0 HcmV?d00001 From 4a7a3dd3f984e8b3712bc3e9e8aab06a9aa72f87 Mon Sep 17 00:00:00 2001 From: GTTEDMI Date: Thu, 16 Jul 2026 17:35:41 +0300 Subject: [PATCH 11/11] Fix Supermatter consol --- .../Supermatter/Consoles/SupermatterConsoleSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs b/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs index c4d0b3853a3..c4b1deeb4b6 100644 --- a/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs +++ b/Content.Server/Backmen/Supermatter/Consoles/SupermatterConsoleSystem.cs @@ -142,8 +142,7 @@ private List GetSupermatterStateData(EntityUid gridUid) if (!Exists(focusSupermatter.Value)) return null; - if (!TryComp(focusSupermatter.Value, out var focusSupermatterXform)) - return null; + var focusSupermatterXform = Transform(focusSupermatter.Value); if (!focusSupermatterXform.Anchored) return null; @@ -250,7 +249,8 @@ public SupermatterStatusType GetStatus(BkmSupermatterComponent sm) private void InitializeConsole(EntityUid uid, SupermatterConsoleComponent component) { // ИСПРАВЛЕНИЕ: Используем TryComp вместо Transform() - if (!TryComp(uid, out var xform) || xform.GridUid == null) + var xform = Transform(uid); + if (xform.GridUid == null) return; Dirty(uid, component);