diff --git a/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorComponent.cs b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorComponent.cs
new file mode 100644
index 00000000000000..c891e460a5bbc1
--- /dev/null
+++ b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorComponent.cs
@@ -0,0 +1,12 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.tx
+namespace Content.Server.SS220.ReactiveTeleportArmor
+{
+ ///
+ /// Intermediate component to work with TeleportOnDamageComponent
+ ///
+ [RegisterComponent]
+ public sealed partial class ReactiveTeleportArmorComponent : Component
+ {
+
+ }
+}
diff --git a/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorSystem.cs b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorSystem.cs
new file mode 100644
index 00000000000000..bbfe5a3ece13b3
--- /dev/null
+++ b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorSystem.cs
@@ -0,0 +1,177 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+using Robust.Shared.Random;
+using Content.Shared.Damage;
+using Content.Shared.Movement.Pulling.Systems;
+using Content.Shared.Movement.Pulling.Components;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Map.Components;
+using System.Numerics;
+using Content.Shared.Physics;
+using Robust.Shared.Collections;
+using Content.Shared.Clothing;
+using Content.Shared.Clothing.EntitySystems;
+using Content.Server.Explosion.EntitySystems;
+using Content.Shared.Item;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
+using Timer = Robust.Shared.Timing.Timer;
+
+namespace Content.Server.SS220.ReactiveTeleportArmor
+{
+ internal class ReactiveTeleportArmorSystem : EntitySystem
+ {
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+ [Dependency] private readonly PullingSystem _pullingSystem = default!;
+ [Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
+ [Dependency] private readonly SharedMapSystem _mapSystem = default!;
+ [Dependency] private readonly ExplosionSystem _explosion = default!;
+ [Dependency] private readonly SharedItemSystem _item = default!;
+ [Dependency] private readonly ClothingSystem _clothing = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+ private EntityQuery _physicsQuery;
+ private HashSet> _targetGrids = [];
+
+ public override void Initialize()
+ {
+ _physicsQuery = GetEntityQuery();
+
+ base.Initialize();
+ SubscribeLocalEvent(TeleporWhenDamaged);
+ SubscribeLocalEvent(OnEquip);
+ SubscribeLocalEvent(OnUnequip);
+ SubscribeLocalEvent(ToggleDone);
+ }
+
+ private void OnEquip(Entity ent, ref ClothingGotEquippedEvent args)
+ {
+ EnsureComp(args.Wearer, out var comp);
+ comp.SavedUid = ent;
+ }
+
+ private void OnUnequip(Entity ent, ref ClothingGotUnequippedEvent args)
+ {
+ RemComp(args.Wearer);
+ }
+
+ private void ToggleDone(Entity ent, ref ItemToggledEvent args)
+ {
+ var prefix = args.Activated ? "on" : null;
+ _item.SetHeldPrefix(ent, prefix);
+ _clothing.SetEquippedPrefix(ent, prefix);
+ }
+
+ private void TeleporWhenDamaged(Entity ent, ref DamageChangedEvent args)
+ {
+ if (!TryComp(ent, out var armor))
+ return;
+
+ var xform = Transform(ent.Owner);
+ var targetCoords = SelectRandomTileInRange(xform, armor.TeleportRadius);
+
+ if (!args.DamageIncreased || args.DamageDelta == null)
+ return;
+ ///teleport entity if taken damage && coord = !null && armor is on && !cooldown
+ if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold && targetCoords != null && _toggle.IsActivated(ent.Comp.SavedUid) && !ent.Comp.OnCoolDown)
+ {
+ ent.Comp.OnCoolDown = true;
+
+ // We need stop the user from being pulled so they don't just get "attached" with whoever is pulling them.
+ // This can for example happen when the user is cuffed and being pulled.
+ if (TryComp(ent.Owner, out var pull) && _pullingSystem.IsPulled(ent.Owner, pull))
+ _pullingSystem.TryStopPull(ent.Owner, pull);
+
+ if (_random.Prob(ent.Comp.TeleportChance))
+ {
+ _xform.SetCoordinates(ent.Owner, targetCoords.Value);
+ _audio.PlayPvs(armor.TeleportSound, ent.Owner);
+ SelectRandomTileInRange(xform, armor.TeleportRadius);
+ }
+ else
+ {
+ _explosion.TriggerExplosive(ent.Comp.SavedUid);
+ }
+ Timer.Spawn(ent.Comp.CoolDownTime, () => ent.Comp.OnCoolDown = false);
+ }
+ }
+
+ private EntityCoordinates? SelectRandomTileInRange(TransformComponent userXform, float radius)
+ {
+ var userCoords = userXform.Coordinates.ToMap(EntityManager, _xform);
+ _targetGrids.Clear();
+ _lookupSystem.GetEntitiesInRange(userCoords, radius, _targetGrids);
+ Entity? targetGrid = null;
+
+ if (_targetGrids.Count == 0)
+ return null;
+
+ // Give preference to the grid the entity is currently on.
+ // This does not guarantee that if the probability fails that the owner's grid won't be picked.
+ // In reality the probability is higher and depends on the number of grids.
+ if (userXform.GridUid != null && TryComp(userXform.GridUid, out var gridComp))
+ {
+ var userGrid = new Entity(userXform.GridUid.Value, gridComp);
+ if (_random.Prob(0.5f))
+ {
+ _targetGrids.Remove(userGrid);
+ targetGrid = userGrid;
+ }
+ }
+
+ if (targetGrid == null)
+ targetGrid = _random.GetRandom().PickAndTake(_targetGrids);
+
+ EntityCoordinates? targetCoords = null;
+
+ var valid = false;
+
+ var range = (float)Math.Sqrt(radius);
+ var box = Box2.CenteredAround(userCoords.Position, new Vector2(range, range));
+ var tilesInRange = _mapSystem.GetTilesEnumerator(targetGrid.Value.Owner, targetGrid.Value.Comp, box, false);
+ var tileList = new ValueList();
+
+ while (tilesInRange.MoveNext(out var tile))
+ {
+ tileList.Add(tile.GridIndices);
+ }
+
+ while (tileList.Count != 0)
+ {
+ var tile = tileList.RemoveSwap(_random.Next(tileList.Count));
+ valid = true;
+ foreach (var entity in _mapSystem.GetAnchoredEntities(targetGrid.Value.Owner, targetGrid.Value.Comp,
+ tile))
+ {
+ if (!_physicsQuery.TryGetComponent(entity, out var body))
+ continue;
+
+ if (body.BodyType != BodyType.Static ||
+ !body.Hard ||
+ (body.CollisionLayer & (int)CollisionGroup.MobMask) == 0)
+ continue;
+
+ valid = false;
+ break;
+ }
+
+ if (valid)
+ {
+ targetCoords = new EntityCoordinates(targetGrid.Value.Owner,
+ _mapSystem.TileCenterToVector(targetGrid.Value, tile));
+ break;
+ }
+ }
+
+ if (!valid || _targetGrids.Count != 0) // if we don't do the check here then PickAndTake will blow up on an empty set.
+ targetGrid = _random.GetRandom().PickAndTake(_targetGrids);
+
+ return targetCoords;
+
+ }
+ }
+}
diff --git a/Content.Server/SS220/ReactiveTeleportArmor/TeleportOnDamageComponent.cs b/Content.Server/SS220/ReactiveTeleportArmor/TeleportOnDamageComponent.cs
new file mode 100644
index 00000000000000..91959708df33ea
--- /dev/null
+++ b/Content.Server/SS220/ReactiveTeleportArmor/TeleportOnDamageComponent.cs
@@ -0,0 +1,41 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Robust.Shared.Audio;
+using Content.Shared.FixedPoint;
+
+namespace Content.Server.SS220.ReactiveTeleportArmor;
+
+///
+/// Randomly teleports entity when damaged.
+///
+[RegisterComponent]
+public sealed partial class TeleportOnDamageComponent : Component
+{
+ ///
+ /// Up to how far to teleport the user
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float TeleportRadius = 30f;
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
+
+ ///
+ /// How much damage of any type it takes to wake this entity.
+ ///
+ [DataField]
+ public FixedPoint2 WakeThreshold = FixedPoint2.New(4);
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float TeleportChance = .9f;
+
+ [ViewVariables]
+ public EntityUid SavedUid;
+
+ public bool OnCoolDown = false;
+
+ [DataField]
+ public TimeSpan CoolDownTime = TimeSpan.FromSeconds(5);
+}
+
+
diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
index e8d967f4d01579..a085671a8d0aa4 100644
--- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
+++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
@@ -252,6 +252,7 @@
- id: ClothingHeadsetAltScience
- id: EncryptionKeyBinary
- id: ClothingWristWatchGold # SS220 Wrist Watch
+ - id: ClothingOuterReactiveArmor # SS220
- type: entity
id: LockerResearchDirectorFilled
@@ -272,6 +273,7 @@
- id: ClothingHeadsetAltScience
- id: EncryptionKeyBinary
- id: ClothingWristWatchGold # SS220 Wrist Watch
+ - id: ClothingOuterReactiveArmor # SS220
- type: entity
id: LockerHeadOfSecurityFilledHardsuit
diff --git a/Resources/Prototypes/SS220/Entities/Clothing/OuterClothing/reactive_armor_rd.yml b/Resources/Prototypes/SS220/Entities/Clothing/OuterClothing/reactive_armor_rd.yml
new file mode 100644
index 00000000000000..85b56277ff4a00
--- /dev/null
+++ b/Resources/Prototypes/SS220/Entities/Clothing/OuterClothing/reactive_armor_rd.yml
@@ -0,0 +1,42 @@
+- type: entity
+ parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing, BaseToggleClothing]
+ id: ClothingOuterReactiveArmor
+ name: "name1" # да-да, сделаю #.ftl
+ description: "Desc2." # да............ точно сделаю, кстати всем ревьюевирам дарова #.ftl
+ components:
+ - type: Sprite
+ sprite: Clothing/OuterClothing/Armor/captain_carapace.rsi #спрайтеры будут ныть как бляди если это будут спрайты с 13
+ - type: Clothing
+ sprite: Clothing/OuterClothing/Armor/captain_carapace.rsi #инфа по борщу не БУДЕТ
+ - type: Armor
+ modifiers:
+ coefficients:
+ Blunt: 0.5
+ Slash: 0.5
+ Piercing: 0.6
+ Heat: 0.5
+ Caustic: 0.9
+ - type: HeldSpeedModifier
+ damageCoefficient: 0.65
+ - type: ToggleClothing
+ action: ActionToggleReactiveArmor
+ - type: GroupExamine
+ - type: ReactiveTeleportArmor
+ - type: Explosive
+ explosionType: Default
+ maxIntensity: 2
+ totalIntensity: 1.5
+ intensitySlope: 1
+ canCreateVacuum: false
+ deleteAfterExplosion: false
+ repeatable: true
+
+
+- type: entity
+ id: ActionToggleReactiveArmor
+ name: Переключение реактивной брони #.ftl
+ description: Включает или выключает реактивную броню. #.ftl
+ components:
+ - type: InstantAction
+ itemIconStyle: BigItem
+ event: !type:ToggleActionEvent
\ No newline at end of file
diff --git a/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml b/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml
index 6ad456102bb32c..c39ce2790807c7 100644
--- a/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml
+++ b/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml
@@ -18,3 +18,10 @@
sprite:
sprite: SS220/Objects/Weapons/Guns/multiphase_energy_gun.rsi
state: icon
+
+- type: stealTargetGroup
+ id: ClothingOuterReactiveArmor
+ name: experimental reactive armor
+ sprite:
+ sprite: Clothing/OuterClothing/Hardsuits/rd.rsi #да-да
+ state: icon
\ No newline at end of file