From 2e86f4c556db9f246bf9fe7e10133eb788fcd236 Mon Sep 17 00:00:00 2001 From: Kara Date: Tue, 15 Aug 2023 13:56:14 -0700 Subject: [PATCH] Portal sprite & behavior tweaks (#19179) * Portal repalette * Portal traversal verb + anchoring * map restriction * optional max distance checks if we decide to have them later * lower pointlight energy slightly hopefully makes it less garish --- .../Teleportation/HandTeleporterSystem.cs | 1 - Content.Server/Teleportation/PortalSystem.cs | 5 +- .../Components/HandTeleporterComponent.cs | 3 +- .../Components/PortalComponent.cs | 19 +++++ .../Systems/SharedPortalSystem.cs | 70 +++++++++++++++++- Resources/Locale/en-US/portal/portal.ftl | 8 ++ .../Prototypes/Entities/Effects/portal.yml | 7 +- .../Textures/Effects/portal.rsi/meta.json | 11 +-- .../Effects/portal.rsi/portal-blue.png | Bin 3000 -> 1705 bytes .../Effects/portal.rsi/portal-red.png | Bin 1651 -> 1720 bytes .../Textures/Effects/portal.rsi/portal.png | Bin 2866 -> 0 bytes 11 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 Resources/Locale/en-US/portal/portal.ftl delete mode 100644 Resources/Textures/Effects/portal.rsi/portal.png 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 4b4e328f354ab42f153cb3a4fa0a50670bfa887b..394bc4efe13e5e8fd697fa707a406f284d757ad8 100644 GIT binary patch delta 1297 zcmV+s1@8K|7pV=983+Ra006D|iL0?8lmUMgP)t-s00000X8)w`{{Wf)ec%72>Ho0o z|Bq^AwEzGB2XsizAOzE~2ZQp6%K-;gj8Ycqq=Quuc7Y-){*Y5ww41E9BpM+nqZ6AMO zgl=455O9$}yx+nH!UYE1xWFLY?_e|9|F+-vA3TACTZtMD8GbP^2;Uig`uOPq*+<9) zung)r=J#8P$jG?TKQCb}fPI8ofdc0UR`+ZD597H2cAi)BsKk#Z#?}4J2(x%C&{0qH z5^vvv3U3Ka;`Id@!Pf}{oT!^1nqq&?2^a%$^1g)0KY#P37Y|Ug6Gu`!IKE#Ng)=GLj!`d1WtR&R|&emKZ3s(VxmtU zLG3MF^pb?tr-NW>kCu_A%S^ZjE;F4Av4#r^wGW}VP)CAbp)Ywxo3YwusXox1K1D%doILyT?nuHubB?;#gqKP&DA9Q;^wm% zt^cWI!euKWZkM9#pI@fZRxs}mig*~M-Duo88B#67;HFq;6@i{@MHY`4ag!@hu~#-h zv~H)m6ZH9ahEK@J;kQCfw&e(#*gGTPusFEM|qd-L2M6f@dA zXcIo#jG1zUxO^*%AwBj7f&CR=wko8^OD4D%3O*$$5wQF8k%y2?>C;QV5j6SuX1wO9 zD*L7^B!oMHg+gsvS`r*BYZYIv|8L9i{{OM*h-FH4w3kcr{<3FfN=H_4hg#tg8M zAZ73GuM)%rexBe)3ci1spsrA{jxaCDe<6qr#QoO@vI{B3*DllTg-7Z>pn3 z9B#b#AxL~%ACf!c%^~^xTeK+Q;V^J|OfwiY^Jwd^*A#zl)?n1^Ipu<@w!0 z8I-Vj4_KC%!Wt|NL4F2 zgX;v3tcl6OI$VF{xA^>@>i?+(kD`sIJ)wCs;4EbqJe-;1$8igW??wtL}YO3Gh++CWLSA#jONm1e@ae zeS$A5G*7UeMCcbmoISqDfjrkGd0{00000NkvXX Hu0mjf8rOPt delta 2603 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 Yf-97BtKIw!hX4Qo07*qoM6N<$f}UD_CjbBd delta 1244 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