diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 7f261f5df2d..30f657a2b5f 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -293,7 +293,7 @@ public void LoadActionAssignments(string path, bool userData) continue; var action = _serialization.Read(actionNode, notNullableOverride: true); - var actionId = Spawn(null); + var actionId = Spawn(); AddComp(actionId, action); AddActionDirect(user, actionId); diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs index c21ba2e32ca..27b2a5dedb0 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -44,7 +44,7 @@ protected override void Draw(in OverlayDrawArgs args) } // if not on the same map, continue - if (_entityManager.GetComponent(entity.Value).MapID != _eyeManager.CurrentMap) + if (_entityManager.GetComponent(entity.Value).MapID != args.MapId) { continue; } diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs index 18aa02e9d67..dd8e3e22121 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs @@ -92,13 +92,18 @@ public BwoinkControl() if (a.IsPinned != b.IsPinned) return a.IsPinned ? -1 : 1; - // First, sort by unread. Any chat with unread messages appears first. We just sort based on unread - // status, not number of unread messages, so that more recent unread messages take priority. + // First, sort by unread. Any chat with unread messages appears first. var aUnread = ach.Unread > 0; var bUnread = bch.Unread > 0; if (aUnread != bUnread) return aUnread ? -1 : 1; + // Sort by recent messages during the current round. + var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue; + var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue; + if (aRecent != bRecent) + return aRecent ? -1 : 1; + // Next, sort by connection status. Any disconnected players are grouped towards the end. if (a.Connected != b.Connected) return a.Connected ? -1 : 1; diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs index 359c8957f9d..525ef1f018f 100644 --- a/Content.Client/Alerts/ClientAlertsSystem.cs +++ b/Content.Client/Alerts/ClientAlertsSystem.cs @@ -93,6 +93,6 @@ private void OnPlayerDetached(EntityUid uid, AlertsComponent component, LocalPla public void AlertClicked(ProtoId alertType) { - RaiseNetworkEvent(new ClickAlertEvent(alertType)); + RaisePredictiveEvent(new ClickAlertEvent(alertType)); } } diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs index 68c937a7885..adb61d10e62 100644 --- a/Content.Client/Chat/UI/SpeechBubble.cs +++ b/Content.Client/Chat/UI/SpeechBubble.cs @@ -16,6 +16,7 @@ public abstract class SpeechBubble : Control [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] protected readonly IConfigurationManager ConfigManager = default!; + private readonly SharedTransformSystem _transformSystem; public enum SpeechType : byte { @@ -83,6 +84,7 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt { IoCManager.InjectDependencies(this); _senderEntity = senderEntity; + _transformSystem = _entityManager.System(); // Use text clipping so new messages don't overlap old ones being pushed up. RectClipContent = true; @@ -140,7 +142,7 @@ protected override void FrameUpdate(FrameEventArgs args) } var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset; - var worldPos = xform.WorldPosition + offset; + var worldPos = _transformSystem.GetWorldPosition(xform) + offset; var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale; var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved); diff --git a/Content.Client/Commands/ActionsCommands.cs b/Content.Client/Commands/ActionsCommands.cs index 3d8e906e09e..593b8e82569 100644 --- a/Content.Client/Commands/ActionsCommands.cs +++ b/Content.Client/Commands/ActionsCommands.cs @@ -1,3 +1,4 @@ +using Content.Client.Actions; using Content.Client.Actions; using Content.Client.Mapping; using Content.Shared.Administration; @@ -61,27 +62,3 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) } } } - -[AnyCommand] -public sealed class LoadMappingActionsCommand : LocalizedCommands -{ - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - public const string CommandName = "loadmapacts"; - - public override string Command => CommandName; - - public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command)); - - public override void Execute(IConsoleShell shell, string argStr, string[] args) - { - try - { - _entitySystemManager.GetEntitySystem().LoadMappingActions(); - } - catch - { - shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error")); - } - } -} diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs index 5f9afc78b98..97d792628a6 100644 --- a/Content.Client/Commands/HideMechanismsCommand.cs +++ b/Content.Client/Commands/HideMechanismsCommand.cs @@ -30,7 +30,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) sprite.ContainerOccluded = false; var tempParent = uid; - while (containerSys.TryGetContainingContainer(tempParent, out var container)) + while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container)) { if (!container.ShowContents) { diff --git a/Content.Client/Commands/MappingClientSideSetupCommand.cs b/Content.Client/Commands/MappingClientSideSetupCommand.cs index 39268c62847..3255e85e18f 100644 --- a/Content.Client/Commands/MappingClientSideSetupCommand.cs +++ b/Content.Client/Commands/MappingClientSideSetupCommand.cs @@ -1,6 +1,9 @@ +using Content.Client.Actions; +using Content.Client.Mapping; using Content.Client.Markers; using JetBrains.Annotations; using Robust.Client.Graphics; +using Robust.Client.State; using Robust.Shared.Console; namespace Content.Client.Commands; @@ -10,6 +13,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands { [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly ILightManager _lightManager = default!; + [Dependency] private readonly IStateManager _stateManager = default!; public override string Command => "mappingclientsidesetup"; @@ -21,8 +25,8 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { _entitySystemManager.GetEntitySystem().MarkersVisible = true; _lightManager.Enabled = false; - shell.ExecuteCommand(ShowSubFloorForever.CommandName); - shell.ExecuteCommand(LoadMappingActionsCommand.CommandName); + shell.ExecuteCommand("showsubfloorforever"); + _entitySystemManager.GetEntitySystem().LoadActionAssignments("/mapping_actions.yml", false); } } } diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs index 63868e7a93e..56604ba526d 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs @@ -130,7 +130,7 @@ public void UpdateCountdown() EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle"); var infoText = Loc.GetString($"comms-console-menu-time-remaining", - ("time", diff.TotalSeconds.ToString(CultureInfo.CurrentCulture))); + ("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture))); CountdownLabel.SetMessage(infoText); } } diff --git a/Content.Client/ContextMenu/UI/ContextMenuUIController.cs b/Content.Client/ContextMenu/UI/ContextMenuUIController.cs index 5b156644a73..2d94034bb9c 100644 --- a/Content.Client/ContextMenu/UI/ContextMenuUIController.cs +++ b/Content.Client/ContextMenu/UI/ContextMenuUIController.cs @@ -2,6 +2,7 @@ using System.Threading; using Content.Client.CombatMode; using Content.Client.Gameplay; +using Content.Client.Mapping; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; using Timer = Robust.Shared.Timing.Timer; @@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI /// /// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements. /// - public sealed class ContextMenuUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged + public sealed class ContextMenuUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged, IOnStateEntered, IOnStateExited { public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2); @@ -42,18 +43,51 @@ public sealed class ContextMenuUIController : UIController, IOnStateEntered? OnSubMenuOpened; public Action? OnContextKeyEvent; + private bool _setup; + public void OnStateEntered(GameplayState state) { + Setup(); + } + + public void OnStateExited(GameplayState state) + { + Shutdown(); + } + + public void OnStateEntered(MappingState state) + { + Setup(); + } + + public void OnStateExited(MappingState state) + { + Shutdown(); + } + + public void Setup() + { + if (_setup) + return; + + _setup = true; + RootMenu = new(this, null); RootMenu.OnPopupHide += Close; Menus.Push(RootMenu); } - public void OnStateExited(GameplayState state) + public void Shutdown() { + if (!_setup) + return; + + _setup = false; + Close(); RootMenu.OnPopupHide -= Close; RootMenu.Dispose(); + RootMenu = default!; } /// diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs index 845bd7c03d2..07b6f57bdb9 100644 --- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs @@ -4,6 +4,7 @@ using Robust.Client.Input; using Robust.Shared.Enums; using Robust.Shared.Map; +using Robust.Shared.Prototypes; namespace Content.Client.Decals.Overlays; @@ -16,7 +17,7 @@ public sealed class DecalPlacementOverlay : Overlay private readonly SharedTransformSystem _transform; private readonly SpriteSystem _sprite; - public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite) { @@ -24,6 +25,7 @@ public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSyst _placement = placement; _transform = transform; _sprite = sprite; + ZIndex = 1000; } protected override void Draw(in OverlayDrawArgs args) @@ -55,7 +57,7 @@ protected override void Draw(in OverlayDrawArgs args) if (snap) { - localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector; + localPos = localPos.Floored() + grid.TileSizeHalfVector; } // Nothing uses snap cardinals so probably don't need preview? diff --git a/Content.Client/Drowsiness/DrowsinessOverlay.cs b/Content.Client/Drowsiness/DrowsinessOverlay.cs new file mode 100644 index 00000000000..a316f31ae64 --- /dev/null +++ b/Content.Client/Drowsiness/DrowsinessOverlay.cs @@ -0,0 +1,80 @@ +using Content.Shared.Drowsiness; +using Content.Shared.StatusEffect; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.Drowsiness; + +public sealed class DrowsinessOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + private readonly ShaderInstance _drowsinessShader; + + public float CurrentPower = 0.0f; + + private const float PowerDivisor = 250.0f; + private const float Intensity = 0.2f; // for adjusting the visual scale + private float _visualScale = 0; // between 0 and 1 + + public DrowsinessOverlay() + { + IoCManager.InjectDependencies(this); + _drowsinessShader = _prototypeManager.Index("Drowsiness").InstanceUnique(); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + var playerEntity = _playerManager.LocalEntity; + + if (playerEntity == null) + return; + + if (!_entityManager.HasComponent(playerEntity) + || !_entityManager.TryGetComponent(playerEntity, out var status)) + return; + + var statusSys = _sysMan.GetEntitySystem(); + if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status)) + return; + + var curTime = _timing.CurTime; + var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds; + + CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) + return false; + + if (args.Viewport.Eye != eyeComp.Eye) + return false; + + _visualScale = Math.Clamp(CurrentPower / PowerDivisor, 0.0f, 1.0f); + return _visualScale > 0; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + _drowsinessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _drowsinessShader.SetParameter("Strength", _visualScale * Intensity); + handle.UseShader(_drowsinessShader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/Drowsiness/DrowsinessSystem.cs b/Content.Client/Drowsiness/DrowsinessSystem.cs new file mode 100644 index 00000000000..bc8862b19d2 --- /dev/null +++ b/Content.Client/Drowsiness/DrowsinessSystem.cs @@ -0,0 +1,53 @@ +using Content.Shared.Drowsiness; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client.Drowsiness; + +public sealed class DrowsinessSystem : SharedDrowsinessSystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private DrowsinessOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDrowsinessInit); + SubscribeLocalEvent(OnDrowsinessShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + _overlay = new(); + } + + private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args) + { + _overlay.CurrentPower = 0; + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args) + { + if (_player.LocalEntity == uid) + _overlayMan.AddOverlay(_overlay); + } + + private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args) + { + if (_player.LocalEntity == uid) + { + _overlay.CurrentPower = 0; + _overlayMan.RemoveOverlay(_overlay); + } + } +} diff --git a/Content.Client/Explosion/ExplosionOverlay.cs b/Content.Client/Explosion/ExplosionOverlay.cs index 653f63e0f90..a005d0317b1 100644 --- a/Content.Client/Explosion/ExplosionOverlay.cs +++ b/Content.Client/Explosion/ExplosionOverlay.cs @@ -16,6 +16,7 @@ public sealed class ExplosionOverlay : Overlay [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IPrototypeManager _proto = default!; + private readonly SharedTransformSystem _transformSystem; private SharedAppearanceSystem _appearance; public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; @@ -26,6 +27,7 @@ public ExplosionOverlay(SharedAppearanceSystem appearanceSystem) { IoCManager.InjectDependencies(this); _shader = _proto.Index("unshaded").Instance(); + _transformSystem = _entMan.System(); _appearance = appearanceSystem; } @@ -68,7 +70,7 @@ private void DrawExplosion( continue; var xform = xforms.GetComponent(gridId); - var (_, _, worldMatrix, invWorldMatrix) = xform.GetWorldPositionRotationMatrixWithInv(xforms); + var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(xform, xforms); gridBounds = invWorldMatrix.TransformBox(worldBounds).Enlarged(grid.TileSize * 2); drawHandle.SetTransform(worldMatrix); diff --git a/Content.Client/Explosion/ExplosionSystem.cs b/Content.Client/Explosion/ExplosionSystem.cs new file mode 100644 index 00000000000..a2ed2d50e0d --- /dev/null +++ b/Content.Client/Explosion/ExplosionSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Explosion.EntitySystems; + +namespace Content.Client.Explosion.EntitySystems; + +public sealed class ExplosionSystem : SharedExplosionSystem +{ + +} diff --git a/Content.Client/Fluids/PuddleOverlay.cs b/Content.Client/Fluids/PuddleOverlay.cs index a8c1d355105..caa5a925807 100644 --- a/Content.Client/Fluids/PuddleOverlay.cs +++ b/Content.Client/Fluids/PuddleOverlay.cs @@ -14,6 +14,7 @@ public sealed class PuddleOverlay : Overlay [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; private readonly PuddleDebugOverlaySystem _debugOverlaySystem; + private readonly SharedTransformSystem _transformSystem; private readonly Color _heavyPuddle = new(0, 255, 255, 50); private readonly Color _mediumPuddle = new(0, 150, 255, 50); @@ -29,6 +30,7 @@ public PuddleOverlay() _debugOverlaySystem = _entitySystemManager.GetEntitySystem(); var cache = IoCManager.Resolve(); _font = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8); + _transformSystem = _entityManager.System(); } protected override void Draw(in OverlayDrawArgs args) @@ -56,7 +58,7 @@ private void DrawWorld(in OverlayDrawArgs args) continue; var gridXform = xformQuery.GetComponent(gridId); - var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery); + var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery); gridBounds = invWorldMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2); drawHandle.SetTransform(worldMatrix); @@ -89,7 +91,7 @@ private void DrawScreen(in OverlayDrawArgs args) continue; var gridXform = xformQuery.GetComponent(gridId); - var (_, _, matrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery); + var (_, _, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery); var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2); foreach (var debugOverlayData in _debugOverlaySystem.GetData(gridId)) diff --git a/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs b/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs index 33944973b51..52ea835f4a8 100644 --- a/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs +++ b/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs @@ -22,7 +22,7 @@ protected override void Open() _ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage; } - public void SendGhostRoleRadioMessage(ProtoId protoId) + private void SendGhostRoleRadioMessage(ProtoId protoId) { SendMessage(new GhostRoleRadioMessage(protoId)); } diff --git a/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs b/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs index b05ac3fbddd..3897b1b949c 100644 --- a/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs +++ b/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs @@ -1,7 +1,6 @@ using Content.Client.UserInterface.Controls; using Content.Shared.Ghost.Roles; using Content.Shared.Ghost.Roles.Components; -using Robust.Client.GameObjects; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; @@ -33,7 +32,7 @@ public void SetEntity(EntityUid uid) private void RefreshUI() { - // The main control that will contain all of the clickable options + // The main control that will contain all the clickable options var main = FindControl("Main"); // The purpose of this radial UI is for ghost role radios that allow you to select @@ -70,7 +69,7 @@ private void RefreshUI() if (_prototypeManager.TryIndex(ghostRoleProto.IconPrototype, out var iconProto)) entProtoView.SetPrototype(iconProto); else - entProtoView.SetPrototype(comp.Prototype); + entProtoView.SetPrototype(ghostRoleProto.EntityPrototype); button.AddChild(entProtoView); main.AddChild(button); diff --git a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs index fc863648d79..9b14e01fb57 100644 --- a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs +++ b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs @@ -206,7 +206,7 @@ private bool PlayCheck() var container = _entManager.System(); // If we're a handheld instrument, we might be in a container. Get it just in case. - container.TryGetContainingContainer(Entity, out var conMan); + container.TryGetContainingContainer((Entity, null, null), out var conMan); // If the instrument is handheld and we're not holding it, we return. if (instrument.Handheld && (conMan == null || conMan.Owner != localEntity)) diff --git a/Content.Client/Interaction/DragDropSystem.cs b/Content.Client/Interaction/DragDropSystem.cs index d249766bbcc..a34cd0f5b11 100644 --- a/Content.Client/Interaction/DragDropSystem.cs +++ b/Content.Client/Interaction/DragDropSystem.cs @@ -42,6 +42,7 @@ public sealed class DragDropSystem : SharedDragDropSystem [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; // how often to recheck possible targets (prevents calling expensive // check logic each update) @@ -517,6 +518,9 @@ private void RemoveHighlights() if (dropEv2.Handled) return dropEv2.CanDrop; + if (dropEv.Handled && dropEv.CanDrop) + return true; + return null; } @@ -551,7 +555,7 @@ public override void FrameUpdate(float frameTime) if (Exists(_dragShadow)) { var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition); - Transform(_dragShadow.Value).WorldPosition = mousePos.Position; + _transformSystem.SetWorldPosition(_dragShadow.Value, mousePos.Position); } } } diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index a1cba2ed3fb..7c53393fc8e 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -5,23 +5,23 @@ using Content.Client.Corvax.TTS; using Content.Client.DebugMon; using Content.Client.Eui; +using Content.Client.Fullscreen; using Content.Client.GhostKick; +using Content.Client.Guidebook; using Content.Client.Launcher; +using Content.Client.Mapping; using Content.Client.Parallax.Managers; using Content.Client.Players.PlayTimeTracking; +using Content.Client.Replay; using Content.Client.Screenshot; -using Content.Client.Fullscreen; using Content.Client.Stylesheets; using Content.Client.Viewport; using Content.Client.Voting; using Content.Shared.Administration.Logs; -using Content.Client.Guidebook; using Content.Client.Lobby; -using Content.Client.Replay; using Content.Shared.Administration.Managers; using Content.Shared.Players.PlayTimeTracking; - namespace Content.Client.IoC { internal static class ClientContentIoC @@ -50,6 +50,7 @@ public static void Register() collection.Register(); collection.Register(); collection.Register(); + collection.Register(); collection.Register(); } } diff --git a/Content.Client/Items/Systems/ItemSystem.cs b/Content.Client/Items/Systems/ItemSystem.cs index 5e60d06d0ce..2b5a41c6ce7 100644 --- a/Content.Client/Items/Systems/ItemSystem.cs +++ b/Content.Client/Items/Systems/ItemSystem.cs @@ -43,7 +43,7 @@ private void OnEquipped(EntityUid uid, SpriteComponent component, GotEquippedEve public override void VisualsChanged(EntityUid uid) { // if the item is in a container, it might be equipped to hands or inventory slots --> update visuals. - if (Container.TryGetContainingContainer(uid, out var container)) + if (Container.TryGetContainingContainer((uid, null, null), out var container)) RaiseLocalEvent(container.Owner, new VisualsChangedEvent(GetNetEntity(uid), container.ID)); } diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index c662338c968..ac0a7a4aa99 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -2,9 +2,11 @@ using System.Numerics; using Content.Client.CrewManifest; using Content.Client.GameTicking.Managers; +using Content.Client.Lobby; using Content.Client.UserInterface.Controls; using Content.Client.Players.PlayTimeTracking; using Content.Shared.CCVar; +using Content.Shared.Preferences; using Content.Shared.Roles; using Content.Shared.StatusIcon; using Robust.Client.Console; @@ -26,6 +28,7 @@ public sealed class LateJoinGui : DefaultWindow [Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystem = default!; [Dependency] private readonly JobRequirementsManager _jobRequirements = default!; + [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; public event Action<(NetEntity, string)> SelectedId; @@ -254,7 +257,7 @@ private void RebuildUI() jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId)); - if (!_jobRequirements.IsAllowed(prototype, out var reason)) + if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason)) { jobButton.Disabled = true; diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 976c820fec3..5e0caa6175c 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -44,6 +44,7 @@ public sealed partial class LobbyUIController : UIController, IOnStateEntered(job.StartingGear); - foreach (var slot in slots) { - var itemType = gear.GetGear(slot.Name); + var itemType = ((IEquipmentLoadout) gear).GetGear(slot.Name); if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false)) { diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 00a3173e36e..1c1d1a9303f 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -662,7 +662,7 @@ public void RefreshAntags() selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1); var requirements = _entManager.System().GetAntagRequirement(antag); - if (!_requirements.CheckRoleTime(requirements, out var reason)) + if (!_requirements.CheckRoleRequirements(requirements, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason)) { selector.LockRequirements(reason); Profile = Profile?.WithAntagPreference(antag.ID, false); @@ -917,7 +917,7 @@ public void RefreshJobs() icon.Texture = jobIcon.Icon.Frame0(); selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides); - if (!_requirements.IsAllowed(job, out var reason)) + if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason)) { selector.LockRequirements(reason); } diff --git a/Content.Client/Mapping/MappingActionsButton.xaml b/Content.Client/Mapping/MappingActionsButton.xaml new file mode 100644 index 00000000000..099719a70e1 --- /dev/null +++ b/Content.Client/Mapping/MappingActionsButton.xaml @@ -0,0 +1,8 @@ + + + diff --git a/Content.Client/Mapping/MappingActionsButton.xaml.cs b/Content.Client/Mapping/MappingActionsButton.xaml.cs new file mode 100644 index 00000000000..1a2f2c069f6 --- /dev/null +++ b/Content.Client/Mapping/MappingActionsButton.xaml.cs @@ -0,0 +1,15 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Mapping; + +[GenerateTypedNameReferences] +public sealed partial class MappingActionsButton : Button +{ + public MappingActionsButton() + { + RobustXamlLoader.Load(this); + } +} + diff --git a/Content.Client/Mapping/MappingDoNotMeasure.xaml b/Content.Client/Mapping/MappingDoNotMeasure.xaml new file mode 100644 index 00000000000..08909636ee5 --- /dev/null +++ b/Content.Client/Mapping/MappingDoNotMeasure.xaml @@ -0,0 +1,4 @@ + + diff --git a/Content.Client/Mapping/MappingDoNotMeasure.xaml.cs b/Content.Client/Mapping/MappingDoNotMeasure.xaml.cs new file mode 100644 index 00000000000..c4cb560234c --- /dev/null +++ b/Content.Client/Mapping/MappingDoNotMeasure.xaml.cs @@ -0,0 +1,21 @@ +using System.Numerics; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Mapping; + +[GenerateTypedNameReferences] +public sealed partial class MappingDoNotMeasure : Control +{ + public MappingDoNotMeasure() + { + RobustXamlLoader.Load(this); + } + + protected override Vector2 MeasureOverride(Vector2 availableSize) + { + return Vector2.Zero; + } +} + diff --git a/Content.Client/Mapping/MappingManager.cs b/Content.Client/Mapping/MappingManager.cs new file mode 100644 index 00000000000..1aac02be714 --- /dev/null +++ b/Content.Client/Mapping/MappingManager.cs @@ -0,0 +1,69 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Content.Shared.Mapping; +using Robust.Client.UserInterface; +using Robust.Shared.Network; + +namespace Content.Client.Mapping; + +public sealed class MappingManager : IPostInjectInit +{ + [Dependency] private readonly IFileDialogManager _file = default!; + [Dependency] private readonly IClientNetManager _net = default!; + + private Stream? _saveStream; + private MappingMapDataMessage? _mapData; + + public void PostInject() + { + _net.RegisterNetMessage(); + _net.RegisterNetMessage(OnSaveError); + _net.RegisterNetMessage(OnMapData); + } + + private void OnSaveError(MappingSaveMapErrorMessage message) + { + _saveStream?.DisposeAsync(); + _saveStream = null; + } + + private async void OnMapData(MappingMapDataMessage message) + { + if (_saveStream == null) + { + _mapData = message; + return; + } + + await _saveStream.WriteAsync(Encoding.ASCII.GetBytes(message.Yml)); + await _saveStream.DisposeAsync(); + + _saveStream = null; + _mapData = null; + } + + public async Task SaveMap() + { + if (_saveStream != null) + await _saveStream.DisposeAsync(); + + var request = new MappingSaveMapMessage(); + _net.ClientSendMessage(request); + + var path = await _file.SaveFile(); + if (path is not { fileStream: var stream }) + return; + + if (_mapData != null) + { + await stream.WriteAsync(Encoding.ASCII.GetBytes(_mapData.Yml)); + _mapData = null; + await stream.FlushAsync(); + await stream.DisposeAsync(); + return; + } + + _saveStream = stream; + } +} diff --git a/Content.Client/Mapping/MappingOverlay.cs b/Content.Client/Mapping/MappingOverlay.cs new file mode 100644 index 00000000000..ef9f3e795e6 --- /dev/null +++ b/Content.Client/Mapping/MappingOverlay.cs @@ -0,0 +1,84 @@ +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using static Content.Client.Mapping.MappingState; + +namespace Content.Client.Mapping; + +public sealed class MappingOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + // 1 off in case something else uses these colors since we use them to compare + private static readonly Color PickColor = new(1, 255, 0); + private static readonly Color DeleteColor = new(255, 1, 0); + + private readonly Dictionary _oldColors = new(); + + private readonly MappingState _state; + private readonly ShaderInstance _shader; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + public MappingOverlay(MappingState state) + { + IoCManager.InjectDependencies(this); + + _state = state; + _shader = _prototypes.Index("unshaded").Instance(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + foreach (var (id, color) in _oldColors) + { + if (!_entities.TryGetComponent(id, out SpriteComponent? sprite)) + continue; + + if (sprite.Color == DeleteColor || sprite.Color == PickColor) + sprite.Color = color; + } + + _oldColors.Clear(); + + if (_player.LocalEntity == null) + return; + + var handle = args.WorldHandle; + handle.UseShader(_shader); + + switch (_state.State) + { + case CursorState.Pick: + { + if (_state.GetHoveredEntity() is { } entity && + _entities.TryGetComponent(entity, out SpriteComponent? sprite)) + { + _oldColors[entity] = sprite.Color; + sprite.Color = PickColor; + } + + break; + } + case CursorState.Delete: + { + if (_state.GetHoveredEntity() is { } entity && + _entities.TryGetComponent(entity, out SpriteComponent? sprite)) + { + _oldColors[entity] = sprite.Color; + sprite.Color = DeleteColor; + } + + break; + } + } + + handle.UseShader(null); + } +} diff --git a/Content.Client/Mapping/MappingPrototype.cs b/Content.Client/Mapping/MappingPrototype.cs new file mode 100644 index 00000000000..eff2dfab151 --- /dev/null +++ b/Content.Client/Mapping/MappingPrototype.cs @@ -0,0 +1,39 @@ +using Content.Shared.Decals; +using Content.Shared.Maps; +using Robust.Shared.Prototypes; + +namespace Content.Client.Mapping; + +/// +/// Used to represent a button's data in the mapping editor. +/// +public sealed class MappingPrototype +{ + /// + /// The prototype instance, if any. + /// Can be one of , or + /// If null, this is a top-level button (such as Entities, Tiles or Decals) + /// + public readonly IPrototype? Prototype; + + /// + /// The text to display on the UI for this button. + /// + public readonly string Name; + + /// + /// Which other prototypes (buttons) this one is nested inside of. + /// + public List? Parents; + + /// + /// Which other prototypes (buttons) are nested inside this one. + /// + public List? Children; + + public MappingPrototype(IPrototype? prototype, string name) + { + Prototype = prototype; + Name = name; + } +} diff --git a/Content.Client/Mapping/MappingPrototypeList.xaml b/Content.Client/Mapping/MappingPrototypeList.xaml new file mode 100644 index 00000000000..de311240df1 --- /dev/null +++ b/Content.Client/Mapping/MappingPrototypeList.xaml @@ -0,0 +1,21 @@ + + + +