diff --git a/Content.Server/_White/ExperimentalSyndicateTeleporter/ExperimentalSyndicateTeleporter.cs b/Content.Server/_White/ExperimentalSyndicateTeleporter/ExperimentalSyndicateTeleporter.cs new file mode 100644 index 0000000000..5faa4afe2d --- /dev/null +++ b/Content.Server/_White/ExperimentalSyndicateTeleporter/ExperimentalSyndicateTeleporter.cs @@ -0,0 +1,169 @@ +using System.Linq; +using System.Numerics; +using Content.Server._White.Other; +using Content.Server.Body.Systems; +using Content.Server.Popups; +using Content.Server.Pulling; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.Examine; +using Content.Shared.Interaction.Events; +using Content.Shared.Maps; +using Content.Shared.Popups; +using Content.Shared.Pulling.Components; +using Robust.Server.Audio; +using Robust.Server.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server._White.ExperimentalSyndicateTeleporter; +public sealed class ExperimentalSyndicateTeleporter : EntitySystem +{ + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; + [Dependency] private readonly MapSystem _mapSystem = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly PullingSystem _pullingSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnExamine); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var component)) + { + if (component.Uses >= 4) + continue; + + component.ChargeCooldown += _timing.FrameTime; + + if (component.ChargeCooldown <= component.NextRechargeAttempt) + continue; + + if (_random.Next(0, 10) != 0) + { + component.ChargeCooldown = TimeSpan.Zero; + continue; + } + + component.Uses++; + } + } + + private void OnUse(EntityUid uid, ExperimentalSyndicateTeleporterComponent component, UseInHandEvent args) + { + if (component.Uses <= 0) + { + _popupSystem.PopupEntity(Loc.GetString("experimental-syndicate-teleporter-end-uses"), args.User, args.User, PopupType.Medium); + return; + } + + if (component.NextUse > _timing.CurTime) + { + _popupSystem.PopupEntity(Loc.GetString("experimental-syndicate-teleporter-cooldown"), args.User, args.User, PopupType.Medium); + return; + } + + if (!TryComp(args.User, out var xform)) + return; + + if (TryComp(args.User, out var pullable) && pullable.BeingPulled) + { + _pullingSystem.TryStopPull(pullable); + } + + if (TryComp(args.User, out var pulling) + && pulling.Pulling != null && + TryComp(pulling.Pulling.Value, out var subjectPulling)) + { + _pullingSystem.TryStopPull(subjectPulling); + } + + var oldCoords = xform.Coordinates; + + var random = _random.Next(component.MinTeleportRange, component.MaxTeleportRange); + var offset = xform.LocalRotation.ToWorldVec().Normalized(); + var direction = xform.LocalRotation.GetDir().ToVec(); + var newOffset = offset + direction * random; + + var coords = xform.Coordinates.Offset(newOffset).SnapToGrid(EntityManager); + + if (TryCheckWall(coords)) + { + EmergencyTeleportation(args.User, xform, component, oldCoords); + return; + } + + SoundAndEffects(component, coords, oldCoords); + + _transform.SetCoordinates(args.User, coords); + + component.Uses--; + component.NextUse = _timing.CurTime + component.Cooldown; + } + + private void OnExamine(EntityUid uid, ExperimentalSyndicateTeleporterComponent component, ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("experimental-syndicate-teleporter-examine", ("uses", component.Uses))); + } + + private void EmergencyTeleportation(EntityUid uid, TransformComponent xform, ExperimentalSyndicateTeleporterComponent component, EntityCoordinates oldCoords) + { + var offset = xform.LocalRotation.ToWorldVec().Normalized(); + var newOffset = offset + VectorRandomDirection(component, offset, component.EmergencyLength); + + var coords = xform.Coordinates.Offset(newOffset).SnapToGrid(EntityManager); + + SoundAndEffects(component, coords, oldCoords); + + _transform.SetCoordinates(uid, coords); + + component.Uses--; + component.NextUse = _timing.CurTime + component.Cooldown; + + if (TryCheckWall(coords)) + { + _bodySystem.GibBody(uid, true, splatModifier: 3F); + } + } + + private void SoundAndEffects(ExperimentalSyndicateTeleporterComponent component, EntityCoordinates coords, EntityCoordinates oldCoords) + { + _audio.PlayPvs(component.TeleportSound, coords); + _audio.PlayPvs(component.TeleportSound, oldCoords); + + _entManager.SpawnEntity(component.ExpSyndicateTeleportInEffect, coords); + _entManager.SpawnEntity(component.ExpSyndicateTeleportOutEffect, oldCoords); + } + + private bool TryCheckWall(EntityCoordinates coords) + { + if (!coords.TryGetTileRef(out var tile)) + return false; + + if (!TryComp(tile.Value.GridUid, out var mapGridComponent)) + return false; + + var anchoredEntities = _mapSystem.GetAnchoredEntities(tile.Value.GridUid, mapGridComponent, coords); + + return anchoredEntities.Any(HasComp); + } + + private Vector2 VectorRandomDirection(ExperimentalSyndicateTeleporterComponent component, Vector2 offset, int length) + { + var randomRotation = _random.Next(0, component.RandomRotations.Count); + return Angle.FromDegrees(component.RandomRotations[randomRotation]).RotateVec(offset.Normalized() * length); + } +} diff --git a/Content.Server/_White/ExperimentalSyndicateTeleporter/ExperimentalSyndicateTeleporterComponent.cs b/Content.Server/_White/ExperimentalSyndicateTeleporter/ExperimentalSyndicateTeleporterComponent.cs new file mode 100644 index 0000000000..a070a3c0f9 --- /dev/null +++ b/Content.Server/_White/ExperimentalSyndicateTeleporter/ExperimentalSyndicateTeleporterComponent.cs @@ -0,0 +1,45 @@ +using System.Numerics; +using System.Threading; +using Robust.Shared.Audio; + +namespace Content.Server._White.ExperimentalSyndicateTeleporter; + +[RegisterComponent] +public sealed partial class ExperimentalSyndicateTeleporterComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public int Uses = 4; + + [ViewVariables(VVAccess.ReadWrite)] + public int MinTeleportRange = 3; + + [ViewVariables(VVAccess.ReadWrite)] + public int MaxTeleportRange = 8; + + [ViewVariables(VVAccess.ReadWrite)] + public int EmergencyLength = 3; + + [ViewVariables(VVAccess.ReadWrite)] + public List RandomRotations = new() {90, -90}; + + [ViewVariables(VVAccess.ReadOnly)] + public string? ExpSyndicateTeleportInEffect = "ExpSyndicateTeleporterInEffect"; + + [ViewVariables(VVAccess.ReadOnly)] + public string? ExpSyndicateTeleportOutEffect = "ExpSyndicateTeleporterOutEffect"; + + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/White/Devices/expsyndicateteleport.ogg"); + + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Cooldown = TimeSpan.FromSeconds(5); + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan NextUse = TimeSpan.Zero; + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan NextRechargeAttempt = TimeSpan.FromSeconds(1); + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan ChargeCooldown = TimeSpan.Zero; +} diff --git a/Resources/Audio/White/Devices/expsyndicateteleport.ogg b/Resources/Audio/White/Devices/expsyndicateteleport.ogg new file mode 100644 index 0000000000..37a5b3839c Binary files /dev/null and b/Resources/Audio/White/Devices/expsyndicateteleport.ogg differ diff --git a/Resources/Locale/ru-RU/experimentalsyndicateteleporter/experimentalsyndicateteleporter.ftl b/Resources/Locale/ru-RU/experimentalsyndicateteleporter/experimentalsyndicateteleporter.ftl new file mode 100644 index 0000000000..c838994f98 --- /dev/null +++ b/Resources/Locale/ru-RU/experimentalsyndicateteleporter/experimentalsyndicateteleporter.ftl @@ -0,0 +1,8 @@ +ent-ExperimentalSyndicateTeleporter = экспериментальный телепортер синдиката + .desc = Телепортер синдиката, при использовании перемещает на 3-8 метров вперед. В случае телепортации в стену, использует экстренную телепортацию. Имеет 4 заряда и автоматически заряжается. +experimental-syndicate-teleporter-examine = Доступные заряды телепортера: [color=fuchsia]{ $uses }[/color]. +experimental-syndicate-teleporter-end-uses = Заряды телепортера закончились! +experimental-syndicate-teleporter-cooldown = Телепортер перезаряжается! + +uplink-experimentalsyndicateteleporter = Экспериментальный телепортер синдиката +uplink-experimentalsyndicateteleporter-desc = Телепортер синдиката, при использовании перемещает на 3-8 метров вперед. В случае телепортации в стену, использует экстренную телепортацию. Имеет 4 заряда и автоматически заряжается. \ No newline at end of file diff --git a/Resources/Prototypes/_White/Catalog/uplink.yml b/Resources/Prototypes/_White/Catalog/uplink.yml index dc7cc6e146..6a1d363a5d 100644 --- a/Resources/Prototypes/_White/Catalog/uplink.yml +++ b/Resources/Prototypes/_White/Catalog/uplink.yml @@ -250,3 +250,15 @@ Telecrystal: 1 categories: - UplinkImplants + +- type: listing + id: UplinkExperimentalSyndicateTeleporter + name: uplink-experimentalsyndicateteleporter + description: uplink-experimentalsyndicateteleporter-desc + icon: { sprite: /Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi, state: icon } + productEntity: ExperimentalSyndicateTeleporter + cost: + Telecrystal: 8 + categories: + - UplinkUtility + saleLimit: 2 \ No newline at end of file diff --git a/Resources/Prototypes/_White/Entities/Objects/Devices/experimentalsyndicateteleporter.yml b/Resources/Prototypes/_White/Entities/Objects/Devices/experimentalsyndicateteleporter.yml new file mode 100644 index 0000000000..40e8974223 --- /dev/null +++ b/Resources/Prototypes/_White/Entities/Objects/Devices/experimentalsyndicateteleporter.yml @@ -0,0 +1,51 @@ +- type: entity + id: ExperimentalSyndicateTeleporter + parent: BaseItem + name: Experimental Syndicate Teleporter + description: Syndicate teleporter, when used, moves 3-8 meters forward. In case of teleportation into a wall, uses emergency teleportation. Has 4 charge. + components: + - type: Sprite + sprite: /Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi + layers: + - state: icon + - type: ExperimentalSyndicateTeleporter + +- type: entity + id: ExpSyndicateTeleporterInEffect + name: Experimental Syndicate Teleporter In Effect + components: + - type: TimedDespawn + lifetime: 0.6 + - type: EvaporationSparkle + - type: Transform + noRot: true + anchored: true + - type: Sprite + layers: + - sprite: White/Objects/Devices/experimentalsyndicateteleporter.rsi + state: in + shader: unshaded + netsync: false + drawdepth: Effects + - type: PointLight + color: "#008DFE" + +- type: entity + id: ExpSyndicateTeleporterOutEffect + name: Experimental Syndicate Teleporter Out Effect + components: + - type: TimedDespawn + lifetime: 0.6 + - type: EvaporationSparkle + - type: Transform + noRot: true + anchored: true + - type: Sprite + layers: + - sprite: White/Objects/Devices/experimentalsyndicateteleporter.rsi + state: out + shader: unshaded + netsync: false + drawdepth: Effects + - type: PointLight + color: "#008DFE" \ No newline at end of file diff --git a/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/icon.png b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/icon.png new file mode 100644 index 0000000000..106f3f415c Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/icon.png differ diff --git a/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/in.png b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/in.png new file mode 100644 index 0000000000..456e592a33 Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/in.png differ diff --git a/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/inhand-left.png b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/inhand-left.png new file mode 100644 index 0000000000..1b5ed5d077 Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/inhand-left.png differ diff --git a/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/inhand-right.png b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/inhand-right.png new file mode 100644 index 0000000000..ff383c0354 Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/inhand-right.png differ diff --git a/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/meta.json b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/meta.json new file mode 100644 index 0000000000..0831b486e9 --- /dev/null +++ b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/meta.json @@ -0,0 +1,122 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Base sprite taken by tg station, remade by CaypenNow", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "in", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "out", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/out.png b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/out.png new file mode 100644 index 0000000000..6483dbcc3b Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/experimentalsyndicateteleporter.rsi/out.png differ