diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 8487401ecd..f1fceb2054 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -22,7 +22,6 @@ public sealed class HandTeleporterSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnDoAfter); } diff --git a/Content.Server/Teleportation/PortalSystem.cs b/Content.Server/Teleportation/PortalSystem.cs index dabcefa9b9..2ca10c6082 100644 --- a/Content.Server/Teleportation/PortalSystem.cs +++ b/Content.Server/Teleportation/PortalSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.Mind.Components; +using Content.Server.Ghost.Components; +using Content.Server.Mind.Components; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Teleportation.Systems; @@ -14,7 +15,7 @@ public sealed class PortalSystem : SharedPortalSystem protected override void LogTeleport(EntityUid portal, EntityUid subject, EntityCoordinates source, EntityCoordinates target) { - if (HasComp(subject)) + if (HasComp(subject) && !HasComp(subject)) _adminLogger.Add(LogType.Teleport, LogImpact.Low, $"{ToPrettyString(subject):player} teleported via {ToPrettyString(portal)} from {source} to {target}"); } } diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index 9b0290ba2c..89aab9766e 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -38,7 +38,8 @@ public sealed class HandTeleporterComponent : Component /// /// Delay for creating the portals in seconds. /// - [DataField("portalCreationDelay")] public float PortalCreationDelay = 2.5f; + [DataField("portalCreationDelay")] + public float PortalCreationDelay = 1.0f; } [Serializable, NetSerializable] diff --git a/Content.Shared/Teleportation/Components/PortalComponent.cs b/Content.Shared/Teleportation/Components/PortalComponent.cs index 70cf119593..e663057f74 100644 --- a/Content.Shared/Teleportation/Components/PortalComponent.cs +++ b/Content.Shared/Teleportation/Components/PortalComponent.cs @@ -28,4 +28,23 @@ public sealed class PortalComponent : Component /// [DataField("maxRandomRadius")] public float MaxRandomRadius = 7.0f; + + /// + /// If false, this portal will fail to teleport and fizzle out if attempting to send an entity to a different map + /// + /// + /// Shouldn't be able to teleport people to centcomm or the eshuttle from the station + /// + [DataField("canTeleportToOtherMaps")] + public bool CanTeleportToOtherMaps = false; + + /// + /// Maximum distance that portals can teleport to, in all cases. Mostly this matters for linked portals. + /// Null means no restriction on distance. + /// + /// + /// Obviously this should strictly be larger than (or null) + /// + [DataField("maxTeleportRadius")] + public float? MaxTeleportRadius; } diff --git a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs index 3bb0aac29b..59c6eee604 100644 --- a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs +++ b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs @@ -1,15 +1,20 @@ using System.Linq; -using Content.Shared.Directions; +using Content.Shared.Ghost; +using Content.Shared.Pinpointer; +using Content.Shared.Popups; using Content.Shared.Projectiles; using Content.Shared.Pulling; using Content.Shared.Pulling.Components; using Content.Shared.Teleportation.Components; +using Content.Shared.Verbs; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; +using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Shared.Teleportation.Systems; @@ -24,6 +29,7 @@ public abstract class SharedPortalSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedPullingSystem _pulling = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; private const string PortalFixture = "portalFixture"; private const string ProjectileFixture = "projectile"; @@ -35,11 +41,42 @@ public abstract class SharedPortalSystem : EntitySystem { SubscribeLocalEvent(OnCollide); SubscribeLocalEvent(OnEndCollide); + SubscribeLocalEvent>(OnGetVerbs); SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnHandleState); } + private void OnGetVerbs(EntityUid uid, PortalComponent component, GetVerbsEvent args) + { + // Traversal altverb for ghosts to use that bypasses normal functionality + if (!args.CanAccess || !HasComp(args.User)) + return; + + // Don't use the verb with unlinked or with multi-output portals + // (this is only intended to be useful for ghosts to see where a linked portal leads) + var disabled = !TryComp(uid, out var link) || link.LinkedEntities.Count != 1; + + args.Verbs.Add(new AlternativeVerb + { + Priority = 11, + Act = () => + { + if (link == null || disabled) + return; + + var ent = link.LinkedEntities.First(); + TeleportEntity(uid, args.User, Transform(ent).Coordinates, ent, false); + }, + Disabled = disabled, + Text = Loc.GetString("portal-component-ghost-traverse"), + Message = disabled + ? Loc.GetString("portal-component-no-linked-entities") + : Loc.GetString("portal-component-can-ghost-traverse"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")) + }); + } + private void OnGetState(EntityUid uid, PortalTimeoutComponent component, ref ComponentGetState args) { args.State = new PortalTimeoutComponentState(component.EnteredPortal); @@ -138,12 +175,38 @@ public abstract class SharedPortalSystem : EntitySystem } } - private void TeleportEntity(EntityUid portal, EntityUid subject, EntityCoordinates target, EntityUid? targetEntity=null, + private void TeleportEntity(EntityUid portal, EntityUid subject, EntityCoordinates target, EntityUid? targetEntity=null, bool playSound=true, PortalComponent? portalComponent = null) { if (!Resolve(portal, ref portalComponent)) return; + var ourCoords = Transform(portal).Coordinates; + var onSameMap = ourCoords.GetMapId(EntityManager) == target.GetMapId(EntityManager); + var distanceInvalid = portalComponent.MaxTeleportRadius != null + && ourCoords.TryDistance(EntityManager, target, out var distance) + && distance > portalComponent.MaxTeleportRadius; + + if (!onSameMap && !portalComponent.CanTeleportToOtherMaps || distanceInvalid) + { + if (!_netMan.IsServer) + return; + + // Early out if this is an invalid configuration + _popup.PopupCoordinates(Loc.GetString("portal-component-invalid-configuration-fizzle"), + ourCoords, Filter.Pvs(ourCoords, entityMan: EntityManager), true); + + _popup.PopupCoordinates(Loc.GetString("portal-component-invalid-configuration-fizzle"), + target, Filter.Pvs(target, entityMan: EntityManager), true); + + QueueDel(portal); + + if (targetEntity != null) + QueueDel(targetEntity.Value); + + return; + } + var arrivalSound = CompOrNull(targetEntity)?.ArrivalSound ?? portalComponent.ArrivalSound; var departureSound = portalComponent.DepartureSound; @@ -159,6 +222,9 @@ public abstract class SharedPortalSystem : EntitySystem _transform.SetCoordinates(subject, target); + if (!playSound) + return; + _audio.PlayPredicted(departureSound, portal, subject); _audio.PlayPredicted(arrivalSound, subject, subject); } diff --git a/Resources/Locale/en-US/portal/portal.ftl b/Resources/Locale/en-US/portal/portal.ftl new file mode 100644 index 0000000000..8134871380 --- /dev/null +++ b/Resources/Locale/en-US/portal/portal.ftl @@ -0,0 +1,8 @@ +### Portal verb text + +portal-component-ghost-traverse = Traverse + +portal-component-no-linked-entities = Can't ghost traverse a portal that doesn't have exactly 1 linked portal +portal-component-can-ghost-traverse = Teleport to the linked portal + +portal-component-invalid-configuration-fizzle = The portal fizzles out! diff --git a/Resources/Prototypes/Entities/Effects/portal.yml b/Resources/Prototypes/Entities/Effects/portal.yml index 4ce2bfb865..92ead90dbf 100644 --- a/Resources/Prototypes/Entities/Effects/portal.yml +++ b/Resources/Prototypes/Entities/Effects/portal.yml @@ -4,6 +4,8 @@ name: bluespace portal description: Transports you to a linked destination! components: + - type: Transform + anchored: True - type: InteractionOutline - type: Clickable - type: Physics @@ -34,8 +36,9 @@ - type: PointLight color: OrangeRed radius: 3 - energy: 3 + energy: 1 netsync: false + - type: entity id: PortalBlue parent: BasePortal @@ -46,5 +49,5 @@ - type: PointLight color: SkyBlue radius: 3 - energy: 3 + energy: 1 netsync: false diff --git a/Resources/Textures/Effects/portal.rsi/meta.json b/Resources/Textures/Effects/portal.rsi/meta.json index 41b8c8df7d..ff4d8c51ee 100644 --- a/Resources/Textures/Effects/portal.rsi/meta.json +++ b/Resources/Textures/Effects/portal.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgStation at commit https://github.com/tgstation/tgstation/blob/4a367160a204db4c5b51c1f811a3b899f0bde3ea/icons/obj/stationobjs.dmi", + "copyright": "Taken from tgStation at commit https://github.com/tgstation/tgstation/blob/4a367160a204db4c5b51c1f811a3b899f0bde3ea/icons/obj/stationobjs.dmi and repaletted using old tg sprites by mirrorcult", "states": [ { "name": "portal-blue", @@ -24,15 +24,6 @@ 0.1, 0.1 ] ] - }, - { - "name": "portal", - "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 - ] - ] } ] } diff --git a/Resources/Textures/Effects/portal.rsi/portal-blue.png b/Resources/Textures/Effects/portal.rsi/portal-blue.png index 4b4e328f35..394bc4efe1 100644 Binary files a/Resources/Textures/Effects/portal.rsi/portal-blue.png and b/Resources/Textures/Effects/portal.rsi/portal-blue.png differ diff --git a/Resources/Textures/Effects/portal.rsi/portal-red.png b/Resources/Textures/Effects/portal.rsi/portal-red.png index e4a40093aa..9b070459a9 100644 Binary files a/Resources/Textures/Effects/portal.rsi/portal-red.png and b/Resources/Textures/Effects/portal.rsi/portal-red.png differ diff --git a/Resources/Textures/Effects/portal.rsi/portal.png b/Resources/Textures/Effects/portal.rsi/portal.png deleted file mode 100644 index 8171135a64..0000000000 Binary files a/Resources/Textures/Effects/portal.rsi/portal.png and /dev/null differ