diff --git a/Content.Server/Implants/Components/ScramImplantComponent.cs b/Content.Server/Implants/Components/ScramImplantComponent.cs
new file mode 100644
index 0000000000..88c433abfb
--- /dev/null
+++ b/Content.Server/Implants/Components/ScramImplantComponent.cs
@@ -0,0 +1,26 @@
+using Content.Server.Implants;
+using Robust.Shared.Audio;
+
+namespace Content.Server.Implants.Components;
+
+///
+/// Randomly teleports entity when triggered.
+///
+[RegisterComponent]
+public sealed partial class ScramImplantComponent : Component
+{
+ ///
+ /// Up to how far to teleport the user
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float TeleportRadius = 100f;
+
+ ///
+ /// How many times to check for a valid tile to teleport to
+ ///
+ [DataField, ViewVariables(VVAccess.ReadOnly)]
+ public int TeleportAttempts = 20;
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
+}
diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs
index 0b1e9d46b1..7f6f6fd045 100644
--- a/Content.Server/Implants/SubdermalImplantSystem.cs
+++ b/Content.Server/Implants/SubdermalImplantSystem.cs
@@ -1,6 +1,7 @@
using Content.Server.Cuffs;
using Content.Server.Forensics;
using Content.Server.Humanoid;
+using Content.Server.Implants.Components;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
using Content.Shared.Cuffs.Components;
@@ -8,8 +9,16 @@ using Content.Shared.Humanoid;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Content.Shared.Interaction;
+using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Preferences;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Map;
+using Robust.Shared.Maths;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Random;
+using System.Numerics;
namespace Content.Server.Implants;
@@ -17,18 +26,27 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
{
[Dependency] private readonly CuffableSystem _cuffable = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly StoreSystem _store = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
+ private EntityQuery _physicsQuery;
+
public override void Initialize()
{
base.Initialize();
+ _physicsQuery = GetEntityQuery();
+
SubscribeLocalEvent(OnFreedomImplant);
SubscribeLocalEvent>(OnStoreRelay);
SubscribeLocalEvent(OnActivateImplantEvent);
+ SubscribeLocalEvent(OnScramImplant);
SubscribeLocalEvent(OnDnaScramblerImplant);
}
@@ -72,6 +90,52 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
args.Handled = true;
}
+ private void OnScramImplant(EntityUid uid, SubdermalImplantComponent component, UseScramImplantEvent args)
+ {
+ if (component.ImplantedEntity is not { } ent)
+ return;
+
+ if (!TryComp(uid, out var implant))
+ return;
+
+ var xform = Transform(ent);
+ var entityCoords = xform.Coordinates.ToMap(EntityManager);
+
+ // try to find a valid position to teleport to, teleport to whatever works if we can't
+ var targetCoords = new MapCoordinates();
+ for (var i = 0; i < implant.TeleportAttempts; i++)
+ {
+ var distance = implant.TeleportRadius * MathF.Sqrt(_random.NextFloat()); // to get an uniform distribution
+ targetCoords = entityCoords.Offset(_random.NextAngle().ToVec() * distance);
+
+ // prefer teleporting to grids
+ if (!_mapManager.TryFindGridAt(targetCoords, out var gridUid, out var grid))
+ continue;
+
+ // the implant user probably does not want to be in your walls
+ var valid = true;
+ foreach (var entity in grid.GetAnchoredEntities(targetCoords))
+ {
+ if (!_physicsQuery.TryGetComponent(entity, out var body))
+ continue;
+
+ if (body.BodyType != BodyType.Static ||
+ !body.Hard ||
+ (body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
+ continue;
+
+ valid = false;
+ break;
+ }
+ if (valid)
+ break;
+ }
+ _xform.SetWorldPosition(ent, targetCoords.Position);
+ _audio.PlayPvs(implant.TeleportSound, ent);
+
+ args.Handled = true;
+ }
+
private void OnDnaScramblerImplant(EntityUid uid, SubdermalImplantComponent component, UseDnaScramblerImplantEvent args)
{
if (component.ImplantedEntity is not { } ent)
diff --git a/Content.Shared/Implants/Components/SubdermalImplantComponent.cs b/Content.Shared/Implants/Components/SubdermalImplantComponent.cs
index 5edc26ead3..09ef05e48a 100644
--- a/Content.Shared/Implants/Components/SubdermalImplantComponent.cs
+++ b/Content.Shared/Implants/Components/SubdermalImplantComponent.cs
@@ -80,6 +80,11 @@ public sealed partial class OpenUplinkImplantEvent : InstantActionEvent
}
+public sealed partial class UseScramImplantEvent : InstantActionEvent
+{
+
+}
+
public sealed partial class UseDnaScramblerImplantEvent : InstantActionEvent
{
diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl
index ac97c0d1a3..50823a5897 100644
--- a/Resources/Locale/en-US/store/uplink-catalog.ftl
+++ b/Resources/Locale/en-US/store/uplink-catalog.ftl
@@ -149,6 +149,9 @@ uplink-storage-implanter-desc = Hide goodies inside of yourself with new bluespa
uplink-freedom-implanter-name = Freedom Implanter
uplink-freedom-implanter-desc = Get away from those nasty sec officers with this three use implant!
+uplink-scram-implanter-name = Scram Implanter
+uplink-scram-implanter-desc = A 3-use implant which teleports you within a large radius. Attempts to teleport you onto an unobstructed tile. May sometimes fail to do so. Life insurance not included.
+
uplink-dna-scrambler-implanter-name = DNA Scrambler Implanter
uplink-dna-scrambler-implanter-desc = A single use implant that can be activated to modify your DNA and give you a completely new look.
diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml
index 6200d8b381..8151cf422e 100644
--- a/Resources/Prototypes/Actions/types.yml
+++ b/Resources/Prototypes/Actions/types.yml
@@ -123,6 +123,22 @@
state: icon
event: !type:ActivateImplantEvent
+- type: entity
+ id: ActionActivateScramImplant
+ name: SCRAM!
+ description: Randomly teleports you within a large distance.
+ noSpawn: true
+ components:
+ - type: InstantAction
+ charges: 2
+ useDelay: 5
+ itemIconStyle: BigAction
+ priority: -20
+ icon:
+ sprite: Structures/Specific/anomaly.rsi
+ state: anom4
+ event: !type:UseScramImplantEvent
+
- type: entity
id: ActionActivateDnaScramblerImplant
name: Scramble DNA
diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml
index 5c1bc63de1..36c3c1435f 100644
--- a/Resources/Prototypes/Catalog/uplink_catalog.yml
+++ b/Resources/Prototypes/Catalog/uplink_catalog.yml
@@ -555,6 +555,17 @@
categories:
- UplinkImplants
+- type: listing
+ id: UplinkScramImplanter
+ name: uplink-scram-implanter-name
+ description: uplink-scram-implanter-desc
+ icon: { sprite: /Textures/Structures/Specific/anomaly.rsi, state: anom4 }
+ productEntity: ScramImplanter
+ cost:
+ Telecrystal: 6 # it's a gamble that may kill you easily so 6 TC per 2 uses, second one more of a backup
+ categories:
+ - UplinkImplants
+
- type: listing
id: UplinkDnaScramblerImplant
name: uplink-dna-scrambler-implanter-name
diff --git a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml
index f35e72de8c..400bfc5c0f 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml
@@ -188,6 +188,14 @@
- type: Implanter
implant: EmpImplant
+- type: entity
+ id: ScramImplanter
+ name: scram implanter
+ parent: BaseImplantOnlyImplanterSyndi
+ components:
+ - type: Implanter
+ implant: ScramImplant
+
- type: entity
id: DnaScramblerImplanter
name: DNA scrambler implanter
diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
index 6caa5322af..d369d03996 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
@@ -177,6 +177,19 @@
range: 1.75
energyConsumption: 50000
disableDuration: 10
+
+- type: entity
+ parent: BaseSubdermalImplant
+ id: ScramImplant
+ name: scram implant
+ description: This implant randomly teleports the user within a large radius when activated.
+ noSpawn: true
+ components:
+ - type: SubdermalImplant
+ implantAction: ActionActivateScramImplant
+ - type: TriggerImplantAction
+ - type: ScramImplant
+ teleportAttempts: 10 # small amount of risk of being teleported into space and lets you teleport off shuttles
- type: entity
parent: BaseSubdermalImplant