diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs index 2ff040ecae..ecac981f59 100644 --- a/Content.Client/Ghost/GhostSystem.cs +++ b/Content.Client/Ghost/GhostSystem.cs @@ -103,8 +103,7 @@ namespace Content.Client.Ghost if (window != null) { - window.Locations = msg.Locations; - window.Players = msg.Players; + window.UpdateWarps(msg.Warps); window.Populate(); } } diff --git a/Content.Client/Ghost/UI/GhostTargetWindow.xaml.cs b/Content.Client/Ghost/UI/GhostTargetWindow.xaml.cs index 7d242c428e..65d438a34f 100644 --- a/Content.Client/Ghost/UI/GhostTargetWindow.xaml.cs +++ b/Content.Client/Ghost/UI/GhostTargetWindow.xaml.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using Content.Shared.Ghost; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; namespace Content.Client.Ghost.UI { @@ -17,9 +12,7 @@ namespace Content.Client.Ghost.UI { private readonly IEntityNetworkManager _netManager; - public List Locations { get; set; } = new(); - - public Dictionary Players { get; set; } = new(); + private List<(string, EntityUid)> _warps = new(); public GhostTargetWindow(IEntityNetworkManager netManager) { @@ -28,58 +21,37 @@ namespace Content.Client.Ghost.UI _netManager = netManager; } + public void UpdateWarps(IEnumerable warps) + { + // Server COULD send these sorted but how about we just use the client to do it instead + _warps = warps + .OrderBy(w => w.IsWarpPoint) + .ThenBy(w => w.DisplayName, Comparer.Create( + (x, y) => string.Compare(x, y, StringComparison.Ordinal))) + .Select(w => + { + var name = w.IsWarpPoint + ? Loc.GetString("ghost-target-window-current-button", ("name", w.DisplayName)) + : w.DisplayName; + + return (name, w.Entity); + }) + .ToList(); + } + public void Populate() { ButtonContainer.DisposeAllChildren(); - AddButtonPlayers(); - AddButtonLocations(); + AddButtons(); } - private void AddButtonPlayers() + private void AddButtons() { - var sortedPlayers = new List<(string, EntityUid)>(Players.Count); - - foreach (var (key, player) in Players) - { - sortedPlayers.Add((player, key)); - } - - sortedPlayers.Sort((x, y) => string.Compare(x.Item1, y.Item1, StringComparison.Ordinal)); - - foreach (var (key, player) in sortedPlayers) + foreach (var (name, warp) in _warps) { var currentButtonRef = new Button { - Text = key, - TextAlign = Label.AlignMode.Right, - HorizontalAlignment = HAlignment.Center, - VerticalAlignment = VAlignment.Center, - SizeFlagsStretchRatio = 1, - MinSize = (340, 20), - ClipText = true, - }; - - currentButtonRef.OnPressed += (_) => - { - var msg = new GhostWarpToTargetRequestEvent(player); - _netManager.SendSystemNetworkMessage(msg); - }; - - ButtonContainer.AddChild(currentButtonRef); - } - } - - private void AddButtonLocations() - { - // Server COULD send these sorted but how about we just use the client to do it instead. - var sortedLocations = new List(Locations); - sortedLocations.Sort((x, y) => string.Compare(x, y, StringComparison.Ordinal)); - - foreach (var name in sortedLocations) - { - var currentButtonRef = new Button - { - Text = Loc.GetString("ghost-target-window-current-button", ("name", name)), + Text = name, TextAlign = Label.AlignMode.Right, HorizontalAlignment = HAlignment.Center, VerticalAlignment = VAlignment.Center, @@ -90,7 +62,7 @@ namespace Content.Client.Ghost.UI currentButtonRef.OnPressed += _ => { - var msg = new GhostWarpToLocationRequestEvent(name); + var msg = new GhostWarpToTargetRequestEvent(warp); _netManager.SendSystemNetworkMessage(msg); }; diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 389e3366f5..b92b8e298e 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -48,7 +48,6 @@ namespace Content.Server.Ghost SubscribeNetworkEvent(OnGhostWarpsRequest); SubscribeNetworkEvent(OnGhostReturnToBodyRequest); - SubscribeNetworkEvent(OnGhostWarpToLocationRequest); SubscribeNetworkEvent(OnGhostWarpToTargetRequest); SubscribeLocalEvent(OnActionPerform); @@ -157,7 +156,7 @@ namespace Content.Server.Ghost return; } - var response = new GhostWarpsResponseEvent(GetLocationNames().ToList(), GetPlayerWarps(entity)); + var response = new GhostWarpsResponseEvent(GetPlayerWarps(entity).Concat(GetLocationWarps()).ToList()); RaiseNetworkEvent(response, args.SenderSession.ConnectedClient); } @@ -175,37 +174,6 @@ namespace Content.Server.Ghost actor.PlayerSession.ContentData()!.Mind?.UnVisit(); } - private void OnGhostWarpToLocationRequest(GhostWarpToLocationRequestEvent msg, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not {Valid: true} attached || - !EntityManager.TryGetComponent(attached, out GhostComponent? ghost)) - { - Logger.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Name} without being a ghost."); - return; - } - - // TODO: why the fuck is this using the name instead of an entity id or something? - // at least it makes sense for the warp command to need to use names, but not this. - - if (FindLocation(msg.Name) is not { } warp) - { - Logger.Warning($"User {args.SenderSession.Name} tried to warp to an invalid warp: {msg.Name}"); - return; - } - - if (warp.Follow) - { - _followerSystem.StartFollowingEntity(attached, warp.Owner); - return; - } - - var xform = Transform(attached); - xform.Coordinates = Transform(warp.Owner).Coordinates; - xform.AttachToGridOrMap(); - if (TryComp(attached, out PhysicsComponent? physics)) - physics.LinearVelocity = Vector2.Zero; - } - private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not {Valid: true} attached || @@ -221,7 +189,18 @@ namespace Content.Server.Ghost return; } - _followerSystem.StartFollowingEntity(ghost.Owner, msg.Target); + if (TryComp(msg.Target, out WarpPointComponent? warp) && warp.Follow + || HasComp(msg.Target)) + { + _followerSystem.StartFollowingEntity(ghost.Owner, msg.Target); + return; + } + + var xform = Transform(ghost.Owner); + xform.Coordinates = Transform(msg.Target).Coordinates; + xform.AttachToGridOrMap(); + if (TryComp(attached, out PhysicsComponent? physics)) + physics.LinearVelocity = Vector2.Zero; } private void DeleteEntity(EntityUid uid) @@ -234,50 +213,33 @@ namespace Content.Server.Ghost EntityManager.DeleteEntity(uid); } - private IEnumerable GetLocationNames() + private IEnumerable GetLocationWarps() { foreach (var warp in EntityManager.EntityQuery(true)) { if (warp.Location != null) { - yield return warp.Location; + yield return new GhostWarp(warp.Owner, warp.Location, true); } } } - private WarpPointComponent? FindLocation(string name) + private IEnumerable GetPlayerWarps(EntityUid except) { - foreach (var warp in EntityManager.EntityQuery(true)) - { - if (warp.Location == name) - { - return warp; - } - } - - return null; - } - - private Dictionary GetPlayerWarps(EntityUid except) - { - var players = new Dictionary(); - foreach (var player in _playerManager.Sessions) { if (player.AttachedEntity is {Valid: true} attached) { + if (attached == except) continue; + TryComp(attached, out var mind); string playerInfo = $"{EntityManager.GetComponent(attached).EntityName} ({mind?.Mind?.CurrentJob?.Name ?? "Unknown"})"; if (TryComp(attached, out var state) && !state.IsDead()) - players.Add(attached, playerInfo); + yield return new GhostWarp(attached, playerInfo, false); } } - - players.Remove(except); - - return players; } public void OnEntityStorageInsertAttempt(EntityUid uid, GhostComponent comp, InsertIntoEntityStorageAttemptEvent args) diff --git a/Content.Shared/Ghost/SharedGhostSystem.cs b/Content.Shared/Ghost/SharedGhostSystem.cs index 294efd0402..b480906f97 100644 --- a/Content.Shared/Ghost/SharedGhostSystem.cs +++ b/Content.Shared/Ghost/SharedGhostSystem.cs @@ -43,6 +43,35 @@ namespace Content.Shared.Ghost { } + /// + /// An individual place a ghost can warp to. + /// This is used as part of + /// + [Serializable, NetSerializable] + public struct GhostWarp + { + public GhostWarp(EntityUid entity, string displayName, bool isWarpPoint) + { + Entity = entity; + DisplayName = displayName; + IsWarpPoint = isWarpPoint; + } + + /// + /// The entity representing the warp point. + /// This is passed back to the server in + /// + public EntityUid Entity { get; } + /// + /// The display name to be surfaced in the ghost warps menu + /// + public string DisplayName { get; } + /// + /// Whether this warp represents a warp point or a player + /// + public bool IsWarpPoint { get; } + } + /// /// A server to client response for a . /// Contains players, and locations a ghost can warp to @@ -50,38 +79,15 @@ namespace Content.Shared.Ghost [Serializable, NetSerializable] public sealed class GhostWarpsResponseEvent : EntityEventArgs { - public GhostWarpsResponseEvent(List locations, Dictionary players) + public GhostWarpsResponseEvent(List warps) { - Locations = locations; - Players = players; + Warps = warps; } /// - /// A list of location names that can be warped to. + /// A list of warp points. /// - public List Locations { get; } - - /// - /// A dictionary containing the entity id, and name of players that can be warped to. - /// - public Dictionary Players { get; } - } - - /// - /// A client to server request for their ghost to be warped to a location - /// - [Serializable, NetSerializable] - public sealed class GhostWarpToLocationRequestEvent : EntityEventArgs - { - /// - /// The location name to warp to. - /// - public string Name { get; } - - public GhostWarpToLocationRequestEvent(string locationName) - { - Name = locationName; - } + public List Warps { get; } } ///