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