diff --git a/Content.Client/GameObjects/Components/DoAfterComponent.cs b/Content.Client/GameObjects/Components/DoAfterComponent.cs index 1b665d2a31..e751f5a996 100644 --- a/Content.Client/GameObjects/Components/DoAfterComponent.cs +++ b/Content.Client/GameObjects/Components/DoAfterComponent.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; using Content.Client.GameObjects.EntitySystems.DoAfter; using Content.Shared.GameObjects.Components; +using Robust.Client.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; @@ -38,6 +40,18 @@ namespace Content.Client.GameObjects.Components } } + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + switch (message) + { + case PlayerDetachedMsg _: + _doAfters.Clear(); + CancelledDoAfters.Clear(); + break; + } + } + /// /// Remove a DoAfter without showing a cancellation graphic. /// diff --git a/Content.Client/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Client/GameObjects/Components/GUI/StrippableComponent.cs new file mode 100644 index 0000000000..6fa50ded40 --- /dev/null +++ b/Content.Client/GameObjects/Components/GUI/StrippableComponent.cs @@ -0,0 +1,22 @@ +using Content.Client.GameObjects.Components.Items; +using Content.Client.Interfaces.GameObjects.Components.Interaction; +using Content.Shared.GameObjects.Components.GUI; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components.GUI +{ + [RegisterComponent] + public class StrippableComponent : SharedStrippableComponent, IClientDraggable + { + public bool ClientCanDropOn(CanDropEventArgs eventArgs) + { + return eventArgs.Target.HasComponent() + && eventArgs.Target != eventArgs.Dragged && eventArgs.Target == eventArgs.User; + } + + public bool ClientCanDrag(CanDragEventArgs eventArgs) + { + return true; + } + } +} diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs new file mode 100644 index 0000000000..afd87e0e06 --- /dev/null +++ b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using Content.Client.UserInterface; +using Content.Shared.GameObjects.Components.GUI; +using Content.Shared.GameObjects.Components.Inventory; +using JetBrains.Annotations; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.ViewVariables; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Client.GameObjects.Components.HUD.Inventory +{ + [UsedImplicitly] + public class StrippableBoundUserInterface : BoundUserInterface + { + public Dictionary Inventory { get; private set; } + public Dictionary Hands { get; private set; } + + [ViewVariables] + private StrippingMenu _strippingMenu; + + public StrippableBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _strippingMenu = new StrippingMenu($"{Owner.Owner.Name}'s inventory"); + _strippingMenu.OpenCentered(); + UpdateMenu(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + _strippingMenu.Dispose(); + + _strippingMenu.Close(); + } + + private void UpdateMenu() + { + if (_strippingMenu == null) return; + + _strippingMenu.ClearButtons(); + + if(Inventory != null) + foreach (var (slot, name) in Inventory) + { + _strippingMenu.AddButton(EquipmentSlotDefines.SlotNames[slot], name, (ev) => + { + SendMessage(new StrippingInventoryButtonPressed(slot)); + }); + } + + if(Hands != null) + foreach (var (hand, name) in Hands) + { + _strippingMenu.AddButton(hand, name, (ev) => + { + SendMessage(new StrippingHandButtonPressed(hand)); + }); + } + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (!(state is StrippingBoundUserInterfaceState stripState)) return; + + Inventory = stripState.Inventory; + Hands = stripState.Hands; + + UpdateMenu(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 0a024e4195..daa03d74e1 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -23,6 +23,7 @@ namespace Content.Client.GameObjects.Components.Items [Dependency] private readonly IGameHud _gameHud = default!; #pragma warning restore 649 + /// private readonly List _hands = new List(); [ViewVariables] public IReadOnlyList Hands => _hands; diff --git a/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs b/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs index 2d5af8c103..137dc2a5e7 100644 --- a/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs @@ -1,25 +1,16 @@ using Content.Client.GameObjects.Components.Mobs; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; namespace Content.Client.GameObjects.EntitySystems { public sealed class CameraRecoilSystem : EntitySystem { - public override void Initialize() - { - base.Initialize(); - - EntityQuery = new TypeEntityQuery(typeof(CameraRecoilComponent)); - } - public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); - foreach (var entity in RelevantEntities) + foreach (var recoil in EntityManager.ComponentManager.EntityQuery()) { - var recoil = entity.GetComponent(); recoil.FrameUpdate(frameTime); } } diff --git a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs index 35ea070048..b46b359ff4 100644 --- a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs +++ b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs @@ -43,6 +43,25 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin); } + /// + /// Called when the mind is detached from an entity + /// + /// Rather than just dispose of the Gui we'll just remove its child controls and re-use the control. + public void Detached() + { + foreach (var (_, control) in _doAfterControls) + { + control.Dispose(); + } + _doAfterControls.Clear(); + foreach (var (_, control) in _doAfterBars) + { + control.Dispose(); + } + _doAfterBars.Clear(); + _cancelledDoAfters.Clear(); + } + /// /// Add the necessary control for a DoAfter progress bar. /// diff --git a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs index 8ba59fde7f..8aed094c7f 100644 --- a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs @@ -57,7 +57,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter { _player = entity; // Setup the GUI and pass the new data to it if applicable. - Gui?.Dispose(); + Gui?.Detached(); if (entity == null) { diff --git a/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs index 5401274988..e184d7d1eb 100644 --- a/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs @@ -1,6 +1,5 @@ using Content.Client.GameObjects.Components.Instruments; using JetBrains.Annotations; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; @@ -12,12 +11,6 @@ namespace Content.Client.GameObjects.EntitySystems { [Dependency] private readonly IGameTiming _gameTiming = default; - public override void Initialize() - { - base.Initialize(); - EntityQuery = new TypeEntityQuery(typeof(InstrumentComponent)); - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -27,9 +20,9 @@ namespace Content.Client.GameObjects.EntitySystems return; } - foreach (var entity in RelevantEntities) + foreach (var instrumentComponent in EntityManager.ComponentManager.EntityQuery()) { - entity.GetComponent().Update(frameTime); + instrumentComponent.Update(frameTime); } } } diff --git a/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs b/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs index 7573253fb6..bec86abbb4 100644 --- a/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs @@ -1,5 +1,4 @@ using Content.Client.GameObjects.Components.Markers; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; namespace Content.Client.GameObjects.EntitySystems @@ -8,13 +7,6 @@ namespace Content.Client.GameObjects.EntitySystems { private bool _markersVisible; - public override void Initialize() - { - base.Initialize(); - - EntityQuery = new TypeEntityQuery(); - } - public bool MarkersVisible { get => _markersVisible; @@ -27,9 +19,9 @@ namespace Content.Client.GameObjects.EntitySystems private void UpdateMarkers() { - foreach (var entity in RelevantEntities) + foreach (var markerComponent in EntityManager.ComponentManager.EntityQuery()) { - entity.GetComponent().UpdateVisibility(); + markerComponent.UpdateVisibility(); } } } diff --git a/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs index bff419b8ab..3f180d4399 100644 --- a/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs @@ -1,6 +1,5 @@ using Content.Client.GameObjects.Components.Mobs; using JetBrains.Annotations; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; namespace Content.Client.GameObjects.EntitySystems @@ -8,20 +7,13 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public sealed class MeleeLungeSystem : EntitySystem { - public override void Initialize() - { - base.Initialize(); - - EntityQuery = new TypeEntityQuery(); - } - public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); - foreach (var entity in RelevantEntities) + foreach (var meleeLungeComponent in EntityManager.ComponentManager.EntityQuery()) { - entity.GetComponent().Update(frameTime); + meleeLungeComponent.Update(frameTime); } } } diff --git a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs index 8b95cb6405..ca7a9d79e4 100644 --- a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs @@ -24,16 +24,15 @@ namespace Content.Client.GameObjects.EntitySystems public override void Initialize() { SubscribeNetworkEvent(PlayWeaponArc); - EntityQuery = new TypeEntityQuery(typeof(MeleeWeaponArcAnimationComponent)); } public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); - foreach (var entity in RelevantEntities) + foreach (var arcAnimationComponent in EntityManager.ComponentManager.EntityQuery()) { - entity.GetComponent().Update(frameTime); + arcAnimationComponent.Update(frameTime); } } diff --git a/Content.Client/GameObjects/EntitySystems/MoverSystem.cs b/Content.Client/GameObjects/EntitySystems/MoverSystem.cs index b16cea1672..cdabb87d7c 100644 --- a/Content.Client/GameObjects/EntitySystems/MoverSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MoverSystem.cs @@ -30,11 +30,10 @@ namespace Content.Client.GameObjects.EntitySystems return; } - var physics = playerEnt.GetComponent(); - playerEnt.TryGetComponent(out ICollidableComponent? collidable); - physics.Predict = true; + var collidable = playerEnt.GetComponent(); + collidable.Predict = true; - UpdateKinematics(playerEnt.Transform, mover, physics, collidable); + UpdateKinematics(playerEnt.Transform, mover, collidable); } public override void Update(float frameTime) diff --git a/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs b/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs index a0fd868b2c..367c49f0b9 100644 --- a/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs @@ -1,5 +1,4 @@ using Content.Client.GameObjects.Components.Mobs; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; @@ -12,11 +11,6 @@ namespace Content.Client.GameObjects.EntitySystems [Dependency] private IGameTiming _gameTiming; #pragma warning restore 649 - public StatusEffectsSystem() - { - EntityQuery = new TypeEntityQuery(typeof(ClientStatusEffectsComponent)); - } - public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); @@ -24,9 +18,9 @@ namespace Content.Client.GameObjects.EntitySystems if (!_gameTiming.IsFirstTimePredicted) return; - foreach (var entity in RelevantEntities) + foreach (var clientStatusEffectsComponent in EntityManager.ComponentManager.EntityQuery()) { - entity.GetComponent().FrameUpdate(frameTime); + clientStatusEffectsComponent.FrameUpdate(frameTime); } } } diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs index 9d73013321..f388263d65 100644 --- a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs @@ -210,6 +210,9 @@ namespace Content.Client.GameObjects.EntitySystems if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity)) continue; + if (verb.BlockedByContainers && !user.IsInSameOrNoContainer(entity)) + continue; + var verbData = verb.GetData(user, component); if (verbData.IsInvisible) @@ -232,6 +235,9 @@ namespace Content.Client.GameObjects.EntitySystems if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity)) continue; + if (globalVerb.BlockedByContainers && !user.IsInSameOrNoContainer(entity)) + continue; + var verbData = globalVerb.GetData(user, entity); if (verbData.IsInvisible) diff --git a/Content.Client/GlobalVerbs/ExamineVerb.cs b/Content.Client/GlobalVerbs/ExamineVerb.cs index 13e6198490..2116290796 100644 --- a/Content.Client/GlobalVerbs/ExamineVerb.cs +++ b/Content.Client/GlobalVerbs/ExamineVerb.cs @@ -11,6 +11,8 @@ namespace Content.Client.GlobalVerbs { public override bool RequireInteractionRange => false; + public override bool BlockedByContainers => false; + public override void GetData(IEntity user, IEntity target, VerbData data) { data.Visibility = VerbVisibility.Visible; diff --git a/Content.Client/GlobalVerbs/ViewVariablesVerb.cs b/Content.Client/GlobalVerbs/ViewVariablesVerb.cs index 93e2f1f39a..fd31a6fd0c 100644 --- a/Content.Client/GlobalVerbs/ViewVariablesVerb.cs +++ b/Content.Client/GlobalVerbs/ViewVariablesVerb.cs @@ -13,6 +13,7 @@ namespace Content.Client.GlobalVerbs class ViewVariablesVerb : GlobalVerb { public override bool RequireInteractionRange => false; + public override bool BlockedByContainers => false; public override void GetData(IEntity user, IEntity target, VerbData data) { diff --git a/Content.Client/Parallax/ParallaxManager.cs b/Content.Client/Parallax/ParallaxManager.cs index 0e6ff0e827..d289ef16cc 100644 --- a/Content.Client/Parallax/ParallaxManager.cs +++ b/Content.Client/Parallax/ParallaxManager.cs @@ -8,6 +8,7 @@ using Robust.Client.Graphics; using Robust.Client.Interfaces.ResourceManagement; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Log; +using Robust.Shared.Interfaces.Resources; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; @@ -59,16 +60,11 @@ namespace Content.Client.Parallax if (!debugParallax && _resourceCache.UserData.Exists(ParallaxConfigOld)) { - bool match; - using (var data = _resourceCache.UserData.Open(ParallaxConfigOld, FileMode.Open)) - using (var reader = new StreamReader(data, EncodingHelpers.UTF8)) - { - match = reader.ReadToEnd() == contents; - } + var match = _resourceCache.UserData.ReadAllText(ParallaxConfigOld) == contents; if (match) { - using (var stream = _resourceCache.UserData.Open(ParallaxPath, FileMode.Open)) + using (var stream = _resourceCache.UserData.OpenRead(ParallaxPath)) { ParallaxTexture = Texture.LoadFromPNGStream(stream, "Parallax"); } @@ -95,7 +91,7 @@ namespace Content.Client.Parallax ParallaxTexture = Texture.LoadFromImage(image, "Parallax"); // Store it and CRC so further game starts don't need to regenerate it. - using (var stream = _resourceCache.UserData.Open(ParallaxPath, FileMode.Create)) + using (var stream = _resourceCache.UserData.Create(ParallaxPath)) { image.SaveAsPng(stream); } @@ -105,8 +101,7 @@ namespace Content.Client.Parallax var i = 0; foreach (var debugImage in debugImages) { - using (var stream = _resourceCache.UserData.Open(new ResourcePath($"/parallax_debug_{i}.png"), - FileMode.Create)) + using (var stream = _resourceCache.UserData.Create(new ResourcePath($"/parallax_debug_{i}.png"))) { debugImage.SaveAsPng(stream); } @@ -117,7 +112,7 @@ namespace Content.Client.Parallax image.Dispose(); - using (var stream = _resourceCache.UserData.Open(ParallaxConfigOld, FileMode.Create)) + using (var stream = _resourceCache.UserData.Create(ParallaxConfigOld)) using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8)) { writer.Write(contents); diff --git a/Content.Client/ScreenshotHook.cs b/Content.Client/ScreenshotHook.cs index 238288ab5f..d1cf81bd96 100644 --- a/Content.Client/ScreenshotHook.cs +++ b/Content.Client/ScreenshotHook.cs @@ -57,7 +57,7 @@ namespace Content.Client } await using var file = - _resourceManager.UserData.Open(BaseScreenshotPath / $"{filename}.png", FileMode.CreateNew); + _resourceManager.UserData.Open(BaseScreenshotPath / $"{filename}.png", FileMode.CreateNew, FileAccess.Read, FileShare.None); await Task.Run(() => { diff --git a/Content.Client/UserInterface/EscapeMenu.cs b/Content.Client/UserInterface/EscapeMenu.cs index 3f085d1a8a..7b3ba644ce 100644 --- a/Content.Client/UserInterface/EscapeMenu.cs +++ b/Content.Client/UserInterface/EscapeMenu.cs @@ -1,5 +1,4 @@ -using Content.Client.Sandbox; -using Robust.Client.Console; +using Robust.Client.Console; using Robust.Client.Interfaces.Placement; using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.UserInterface.Controls; @@ -15,16 +14,12 @@ namespace Content.Client.UserInterface internal sealed class EscapeMenu : SS14Window { private readonly IClientConsole _console; - private readonly ITileDefinitionManager __tileDefinitionManager; + private readonly ITileDefinitionManager _tileDefinitionManager; private readonly IPlacementManager _placementManager; private readonly IPrototypeManager _prototypeManager; private readonly IResourceCache _resourceCache; private readonly IConfigurationManager _configSystem; private readonly ILocalizationManager _localizationManager; -#pragma warning disable 649 - [Dependency] private readonly ISandboxManager _sandboxManager; - [Dependency] private readonly IClientConGroupController _conGroupController; -#pragma warning restore 649 private BaseButton DisconnectButton; private BaseButton QuitButton; @@ -41,7 +36,7 @@ namespace Content.Client.UserInterface _configSystem = configSystem; _localizationManager = localizationManager; _console = console; - __tileDefinitionManager = tileDefinitionManager; + _tileDefinitionManager = tileDefinitionManager; _placementManager = placementManager; _prototypeManager = prototypeManager; _resourceCache = resourceCache; diff --git a/Content.Client/UserInterface/StrippingMenu.cs b/Content.Client/UserInterface/StrippingMenu.cs new file mode 100644 index 0000000000..3088bd4270 --- /dev/null +++ b/Content.Client/UserInterface/StrippingMenu.cs @@ -0,0 +1,64 @@ +using System; +using Content.Client.UserInterface.Stylesheets; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Content.Client.UserInterface +{ + public class StrippingMenu : SS14Window + { + protected override Vector2? CustomSize => new Vector2(400, 600); + + private readonly VBoxContainer _vboxContainer; + + public StrippingMenu(string title) + { + Title = title; + + _vboxContainer = new VBoxContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SeparationOverride = 5, + }; + + Contents.AddChild(_vboxContainer); + } + + public void ClearButtons() + { + _vboxContainer.DisposeAllChildren(); + } + + public void AddButton(string title, string name, Action onPressed) + { + var button = new Button() + { + Text = name, + StyleClasses = { StyleBase.ButtonOpenRight } + }; + + button.OnPressed += onPressed; + + _vboxContainer.AddChild(new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SeparationOverride = 5, + Children = + { + new Label() + { + Text = $"{title}:" + }, + new Control() + { + SizeFlagsHorizontal = SizeFlags.FillExpand + }, + button, + } + }); + } + } +} diff --git a/Content.Server/Administration/WarpCommand.cs b/Content.Server/Administration/WarpCommand.cs index 4942c6599b..e54bca1671 100644 --- a/Content.Server/Administration/WarpCommand.cs +++ b/Content.Server/Administration/WarpCommand.cs @@ -4,10 +4,12 @@ using Content.Server.GameObjects.Components.Markers; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Enums; +using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Maths; namespace Content.Server.Administration { @@ -107,6 +109,10 @@ namespace Content.Server.Administration if (found.GridID != GridId.Invalid) { player.AttachedEntity.Transform.GridPosition = found; + if (player.AttachedEntity.TryGetComponent(out ICollidableComponent collidable)) + { + collidable.Stop(); + } } else { diff --git a/Content.Server/Atmos/HighPressureMovementController.cs b/Content.Server/Atmos/HighPressureMovementController.cs index 60086206b9..2e27f513ad 100644 --- a/Content.Server/Atmos/HighPressureMovementController.cs +++ b/Content.Server/Atmos/HighPressureMovementController.cs @@ -49,19 +49,23 @@ namespace Content.Server.Atmos { - if (maxForce > ThrowForce && throwTarget != GridCoordinates.InvalidGrid) + if (maxForce > ThrowForce) { - var moveForce = MathF.Min(maxForce * FloatMath.Clamp(moveProb, 0, 100) / 100f, 50f); - var pos = throwTarget.Position - transform.GridPosition.Position; - LinearVelocity = pos * moveForce; - } - else - { - var moveForce = MathF.Min(maxForce * FloatMath.Clamp(moveProb, 0, 100) / 100f, 25f); - LinearVelocity = direction.ToVec() * moveForce; - } + if (throwTarget != GridCoordinates.InvalidGrid) + { + var moveForce = maxForce * FloatMath.Clamp(moveProb, 0, 100) / 150f; + var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToVec()).Normalized; + LinearVelocity = pos * moveForce; + } - pressureComponent.LastHighPressureMovementAirCycle = cycle; + else + { + var moveForce = MathF.Min(maxForce * FloatMath.Clamp(moveProb, 0, 100) / 2500f, 20f); + LinearVelocity = direction.ToVec() * moveForce; + } + + pressureComponent.LastHighPressureMovementAirCycle = cycle; + } } } @@ -72,7 +76,7 @@ namespace Content.Server.Atmos if (ControlledComponent != null && !_physicsManager.IsWeightless(ControlledComponent.Owner.Transform.GridPosition)) { LinearVelocity *= 0.85f; - if (LinearVelocity.Length < 1f) + if (MathF.Abs(LinearVelocity.Length) < 1f) Stop(); } } diff --git a/Content.Server/Atmos/IGridAtmosphereComponent.cs b/Content.Server/Atmos/IGridAtmosphereComponent.cs index 430c674bce..5518881767 100644 --- a/Content.Server/Atmos/IGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/IGridAtmosphereComponent.cs @@ -40,6 +40,11 @@ namespace Content.Server.Atmos /// void Invalidate(MapIndices indices); + /// + /// Attempts to fix a sudden vacuum by creating gas. + /// + void FixVacuum(MapIndices indices); + /// /// Adds an active tile so it becomes processed every update until it becomes inactive. /// Also makes the tile excited. diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index eab7e6b9ad..8e634a1c7d 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -17,6 +17,7 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Random; @@ -78,7 +79,6 @@ namespace Content.Server.Atmos [ViewVariables] public Hotspot Hotspot; - [ViewVariables] private Direction _pressureDirection; [ViewVariables] @@ -106,6 +106,7 @@ namespace Content.Server.Atmos GridIndex = gridIndex; GridIndices = gridIndices; Air = mixture; + ResetTileAtmosInfo(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -181,6 +182,8 @@ namespace Content.Server.Atmos || ContainerHelpers.IsInContainer(entity)) continue; + physics.WakeBody(); + var pressureMovements = physics.EnsureController(); if (pressure.LastHighPressureMovementAirCycle < _gridAtmosphereComponent.UpdateCounter) { @@ -221,7 +224,8 @@ namespace Content.Server.Atmos { if (Air == null || (_tileAtmosInfo.LastCycle >= cycleNum)) return; // Already done. - _tileAtmosInfo = new TileAtmosInfo(); + ResetTileAtmosInfo(); + var startingMoles = Air.TotalMoles; var runAtmos = false; @@ -264,7 +268,7 @@ namespace Content.Server.Atmos { if (adj?.Air == null) continue; if(adj._tileAtmosInfo.LastQueueCycle == queueCycle) continue; - adj._tileAtmosInfo = new TileAtmosInfo(); + adj.ResetTileAtmosInfo(); adj._tileAtmosInfo.LastQueueCycle = queueCycle; if(tileCount < Atmospherics.ZumosHardTileLimit) @@ -328,7 +332,7 @@ namespace Content.Server.Atmos var tile = tiles[i]; tile._tileAtmosInfo.FastDone = true; if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue; - Direction eligibleAdjBits = 0; + var eligibleDirections = new List(); var amtEligibleAdj = 0; foreach (var direction in Cardinal) { @@ -338,16 +342,17 @@ namespace Content.Server.Atmos if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; - eligibleAdjBits |= direction; + eligibleDirections.Add(direction); amtEligibleAdj++; } if (amtEligibleAdj <= 0) continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this. + var molesToMove = tile._tileAtmosInfo.MoleDelta / amtEligibleAdj; foreach (var direction in Cardinal) { - if ((eligibleAdjBits & direction) == 0 || + if (eligibleDirections.Contains(direction) || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; tile.AdjustEqMovement(direction, molesToMove); tile._tileAtmosInfo.MoleDelta -= molesToMove; @@ -394,13 +399,10 @@ namespace Content.Server.Atmos foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; - if (giver._tileAtmosInfo.MoleDelta <= 0) - break; // We're done here now. Let's not do more work than needed. - - if (tile2?._tileAtmosInfo == null || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) - continue; - + if (giver._tileAtmosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed. + if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue; + queue[queueLength++] = tile2; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite(); @@ -458,7 +460,7 @@ namespace Content.Server.Atmos var queueLength = 0; queue[queueLength++] = taker; taker._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; - for (int i = 0; i < queueLength; i++) + for (var i = 0; i < queueLength; i++) { if (taker._tileAtmosInfo.MoleDelta >= 0) break; // We're done here now. Let's not do more work than needed. @@ -469,11 +471,8 @@ namespace Content.Server.Atmos if (!tile._adjacentTiles.ContainsKey(direction)) continue; var tile2 = tile._adjacentTiles[direction]; - if (taker._tileAtmosInfo.MoleDelta >= 0) - break; // We're done here now. Let's not do more work than needed. - - if (tile2?._tileAtmosInfo == null || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) - continue; + if (taker._tileAtmosInfo.MoleDelta >= 0) break; // We're done here now. Let's not do more work than needed. + if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue; queue[queueLength++] = tile2; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; @@ -504,16 +503,16 @@ namespace Content.Server.Atmos for (var i = queueLength - 1; i >= 0; i--) { var tile = queue[i]; - if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || - tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) continue; - tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, - tile._tileAtmosInfo.CurrentTransferAmount); + if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) + continue; - if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, - out var adjacent)) - adjacent._tileAtmosInfo.CurrentTransferAmount += - tile._tileAtmosInfo.CurrentTransferAmount; - tile._tileAtmosInfo.CurrentTransferAmount = 0; + tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); + + if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent)) + { + adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; + tile._tileAtmosInfo.CurrentTransferAmount = 0; + } } } @@ -552,38 +551,34 @@ namespace Content.Server.Atmos foreach (var direction in Cardinal) { var amount = _tileAtmosInfo[direction]; - transferDirections[direction] = amount; if (amount == 0) continue; + transferDirections[direction] = amount; _tileAtmosInfo[direction] = 0; hasTransferDirs = true; } if (!hasTransferDirs) return; - foreach (var direction in Cardinal) + foreach (var (direction, amount) in transferDirections) { - var amount = transferDirections[direction]; if (!_adjacentTiles.TryGetValue(direction, out var tile) || tile.Air == null) continue; if (amount > 0) { - // Prevent infinite recursion. - tile._tileAtmosInfo[direction.GetOpposite()] = 0; - if (Air.TotalMoles < amount) - FinalizeEqNeighbors(); + FinalizeEqNeighbors(transferDirections.Keys); tile.Air.Merge(Air.Remove(amount)); UpdateVisuals(); tile.UpdateVisuals(); - ConsiderPressureDifference(tile, amount); + ConsiderPressureDifference(direction, amount); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FinalizeEqNeighbors() + private void FinalizeEqNeighbors(IEnumerable directions) { - foreach (var direction in Cardinal) + foreach (var direction in directions) { var amount = _tileAtmosInfo[direction]; if(amount < 0 && _adjacentTiles.TryGetValue(direction, out var adjacent)) @@ -592,13 +587,13 @@ namespace Content.Server.Atmos } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConsiderPressureDifference(TileAtmosphere tile, float difference) + private void ConsiderPressureDifference(Direction direction, float difference) { _gridAtmosphereComponent.AddHighPressureDelta(this); if (difference > PressureDifference) { PressureDifference = difference; - _pressureDirection = ((Vector2i) (GridIndices - tile.GridIndices)).GetCardinalDir(); + _pressureDirection = difference < 0 ? direction.GetOpposite() : direction; } } @@ -606,8 +601,14 @@ namespace Content.Server.Atmos private void AdjustEqMovement(Direction direction, float molesToMove) { _tileAtmosInfo[direction] += molesToMove; - if(direction != (Direction)(-1) && _adjacentTiles.TryGetValue(direction, out var adj)) - _adjacentTiles[direction]._tileAtmosInfo[direction.GetOpposite()] -= molesToMove; + if(direction != Direction.Invalid && _adjacentTiles.TryGetValue(direction, out var adj)) + adj._tileAtmosInfo[direction.GetOpposite()] -= molesToMove; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResetTileAtmosInfo() + { + _tileAtmosInfo = new TileAtmosInfo {CurrentTransferDirection = Direction.Invalid}; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -625,7 +626,7 @@ namespace Content.Server.Atmos _currentCycle = fireCount; var adjacentTileLength = 0; - foreach (var (_, enemyTile) in _adjacentTiles) + foreach (var (direction, enemyTile) in _adjacentTiles) { // If the tile is null or has no air, we don't do anything if(enemyTile?.Air == null) continue; @@ -675,11 +676,11 @@ namespace Content.Server.Atmos // Space wind! if (difference > 0) { - ConsiderPressureDifference(enemyTile, difference); + ConsiderPressureDifference(direction, difference); } else { - enemyTile.ConsiderPressureDifference(this, -difference); + enemyTile.ConsiderPressureDifference(direction.GetOpposite(), -difference); } LastShareCheck(); @@ -929,11 +930,10 @@ namespace Content.Server.Atmos var tiles = new List(); var spaceTiles = new List(); tiles.Add(this); - _tileAtmosInfo = new TileAtmosInfo - { - LastQueueCycle = queueCycle, - CurrentTransferDirection = Direction.Invalid - }; + + ResetTileAtmosInfo(); + _tileAtmosInfo.LastQueueCycle = queueCycle; + var tileCount = 1; for (var i = 0; i < tileCount; i++) { @@ -947,16 +947,17 @@ namespace Content.Server.Atmos } else { - if (i > Atmospherics.ZumosHardTileLimit) continue; + if (i > Atmospherics.ZumosTileLimit) continue; foreach (var direction in Cardinal) { - if (!_adjacentTiles.TryGetValue(direction, out var tile2)) continue; + if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; if (tile2?.Air == null) continue; if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue; tile.ConsiderFirelocks(tile2); if (tile._adjacentTiles[direction]?.Air != null) { - tile2._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle}; + tile2.ResetTileAtmosInfo(); + tile2._tileAtmosInfo.LastQueueCycle = queueCycle; tiles.Add(tile2); tileCount++; } @@ -979,7 +980,7 @@ namespace Content.Server.Atmos var tile = progressionOrder[i]; foreach (var direction in Cardinal) { - if (!_adjacentTiles.TryGetValue(direction, out var tile2)) continue; + if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; if (tile2?._tileAtmosInfo.LastQueueCycle != queueCycle) continue; if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue; if(tile2.Air.Immutable) continue; @@ -992,14 +993,12 @@ namespace Content.Server.Atmos } } - for (int i = 0; i < progressionCount; i++) + for (var i = progressionCount - 1; i >= 0; i--) { var tile = progressionOrder[i]; if (tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) continue; - var hpdLength = _gridAtmosphereComponent.HighPressureDeltaCount; - var inHdp = _gridAtmosphereComponent.HasHighPressureDelta(tile); - if(!inHdp) - _gridAtmosphereComponent.AddHighPressureDelta(tile); + _gridAtmosphereComponent.AddHighPressureDelta(tile); + _gridAtmosphereComponent.AddActiveTile(tile); if (!tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var tile2) || tile2.Air == null) continue; var sum = tile2.Air.TotalMoles; totalGasesRemoved += sum; @@ -1007,11 +1006,13 @@ namespace Content.Server.Atmos tile2._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; tile.PressureDifference = tile._tileAtmosInfo.CurrentTransferAmount; tile._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection; + if (tile2._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) { tile2.PressureDifference = tile2._tileAtmosInfo.CurrentTransferAmount; tile2._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection; } + tile.Air.Clear(); tile.UpdateVisuals(); tile.HandleDecompressionFloorRip(sum); @@ -1020,7 +1021,8 @@ namespace Content.Server.Atmos private void HandleDecompressionFloorRip(float sum) { - if (sum > 20 && _robustRandom.Prob(FloatMath.Clamp(sum / 100, 0.005f, 0.5f))) + var chance = FloatMath.Clamp(sum / 500, 0.005f, 0.5f); + if (sum > 20 && _robustRandom.Prob(chance)) _gridAtmosphereComponent.PryTile(GridIndices); } @@ -1061,14 +1063,19 @@ namespace Content.Server.Atmos { foreach (var direction in Cardinal) { - if(!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction))) - _adjacentTiles[direction] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction)); + if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction))) + { + var adjacent = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction)); + _adjacentTiles[direction] = adjacent; + adjacent.UpdateAdjacent(direction.GetOpposite()); + } } } public void UpdateAdjacent(Direction direction) { - _adjacentTiles[direction] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction)); + if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction))) + _adjacentTiles[direction] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction)); } private void LastShareCheck() diff --git a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs index 478dd540cb..07c6ed66e9 100644 --- a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs @@ -40,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Atmos base.ExposeData(serializer); serializer.DataField(ref _airBlocked, "airBlocked", true); - serializer.DataField(ref _fixVacuum, "fixVacuum", false); + serializer.DataField(ref _fixVacuum, "fixVacuum", true); } public override void Initialize() @@ -57,15 +57,6 @@ namespace Content.Server.GameObjects.Components.Atmos UpdatePosition(); } - public override void OnRemove() - { - base.OnRemove(); - - _airBlocked = false; - - UpdatePosition(); - } - public void MapInit() { _snapGrid.OnPositionChanged += OnTransformMove; @@ -80,6 +71,11 @@ namespace Content.Server.GameObjects.Components.Atmos _airBlocked = false; _snapGrid.OnPositionChanged -= OnTransformMove; + + if(_fixVacuum) + EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)? + .FixVacuum(_snapGrid.Position); + UpdatePosition(); } diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs index c7207ffa4e..05ba999b0b 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -28,7 +28,6 @@ namespace Content.Server.GameObjects.Components.Atmos [RegisterComponent, Serializable] public class GridAtmosphereComponent : Component, IGridAtmosphereComponent { - [Robust.Shared.IoC.Dependency] private IGameTiming _gameTiming = default!; [Robust.Shared.IoC.Dependency] private IMapManager _mapManager = default!; /// @@ -154,13 +153,14 @@ namespace Content.Server.GameObjects.Components.Atmos if (tile == null) { tile = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}); - _tiles.Add(indices, tile); + _tiles[indices] = tile; } if (IsSpace(indices)) { tile.Air = new GasMixture(GetVolumeForCells(1)); tile.Air.MarkImmutable(); + _tiles[indices] = tile; } else if (IsAirBlocked(indices)) { @@ -174,17 +174,7 @@ namespace Content.Server.GameObjects.Components.Atmos { if (tile.Air == null && obs.FixVacuum) { - var adjacent = GetAdjacentTiles(indices); - tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}; - - var ratio = 1f / adjacent.Count; - - foreach (var (direction, adj) in adjacent) - { - var mix = adj.Air.RemoveRatio(ratio); - tile.Air.Merge(mix); - adj.Air.Merge(mix); - } + FixVacuum(tile.GridIndices); } } @@ -206,6 +196,25 @@ namespace Content.Server.GameObjects.Components.Atmos _invalidatedCoords.Clear(); } + /// + public void FixVacuum(MapIndices indices) + { + var tile = GetTile(indices); + if (tile?.GridIndex != _grid.Index) return; + var adjacent = GetAdjacentTiles(indices); + tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}; + _tiles[indices] = tile; + + var ratio = 1f / adjacent.Count; + + foreach (var (direction, adj) in adjacent) + { + var mix = adj.Air.RemoveRatio(ratio); + tile.Air.Merge(mix); + adj.Air.Merge(mix); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddActiveTile(TileAtmosphere tile) diff --git a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs index cc477cd8e9..066cf21836 100644 --- a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs @@ -16,9 +16,7 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] class VaporComponent : Component, ICollideBehavior { -#pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager = default!; -#pragma warning enable 649 public override string Name => "Vapor"; [ViewVariables] @@ -66,7 +64,7 @@ namespace Content.Server.GameObjects.Components.Chemistry { var worldBounds = collidable.WorldAABB; var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID); - + var tiles = mapGrid.GetTilesIntersecting(worldBounds); var amount = _transferAmount / ReagentUnit.New(tiles.Count()); foreach (var tile in tiles) diff --git a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs index 8e5ca778a1..d29c7d93c9 100644 --- a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs +++ b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs @@ -2,8 +2,6 @@ using Content.Shared.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -16,9 +14,6 @@ namespace Content.Server.GameObjects.Components.Construction [RegisterComponent] public class ConstructionComponent : Component, IExamine { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 /// public override string Name => "Construction"; diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs index 9f9d0efe21..29d5132925 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs @@ -217,7 +217,7 @@ namespace Content.Server.GameObjects.Components.Disposal public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(ref _clangSound, "clangSound", "/Audio/effects/clang.ogg"); + serializer.DataField(ref _clangSound, "clangSound", "/Audio/Effects/clang.ogg"); } public override void Initialize() diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index bc4d887457..89a6049b66 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -94,7 +94,7 @@ namespace Content.Server.GameObjects.Components.Disposal collidable.Anchored; [ViewVariables] - private State State => _pressure >= 1 ? State.Ready : State.Pressurizing; + private PressureState State => _pressure >= 1 ? PressureState.Ready : PressureState.Pressurizing; [ViewVariables] private bool Engaged diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 0ea707e19c..ea75c7f382 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -42,6 +42,8 @@ namespace Content.Server.GameObjects.Components.GUI private string? _activeHand; private uint _nextHand; + public event Action? OnItemChanged; + [ViewVariables(VVAccess.ReadWrite)] public string? ActiveHand { @@ -60,6 +62,8 @@ namespace Content.Server.GameObjects.Components.GUI [ViewVariables] private readonly List _hands = new List(); + public IEnumerable Hands => _hands.Select(h => h.Name); + // Mostly arbitrary. public const float PickupRange = 2; @@ -105,6 +109,12 @@ namespace Content.Server.GameObjects.Components.GUI return GetHand(handName)?.Entity?.GetComponent(); } + public bool TryGetItem(string handName, [MaybeNullWhen(false)] out ItemComponent item) + { + item = GetItem(handName); + return item != null; + } + public ItemComponent? GetActiveHand => ActiveHand == null ? null : GetItem(ActiveHand); @@ -136,6 +146,8 @@ namespace Content.Server.GameObjects.Components.GUI { if (PutInHand(item, hand, false)) { + OnItemChanged?.Invoke(); + return true; } } @@ -156,6 +168,7 @@ namespace Content.Server.GameObjects.Components.GUI if (success) { item.Owner.Transform.LocalPosition = Vector2.Zero; + OnItemChanged?.Invoke(); } _entitySystemManager.GetEntitySystem().HandSelectedInteraction(Owner, item.Owner); @@ -250,6 +263,8 @@ namespace Content.Server.GameObjects.Components.GUI container.Insert(item.Owner); } + OnItemChanged?.Invoke(); + Dirty(); return true; } @@ -300,6 +315,8 @@ namespace Content.Server.GameObjects.Components.GUI container.Insert(item.Owner); } + OnItemChanged?.Invoke(); + Dirty(); return true; } @@ -364,6 +381,8 @@ namespace Content.Server.GameObjects.Components.GUI throw new InvalidOperationException(); } + OnItemChanged?.Invoke(); + Dirty(); return true; } @@ -415,6 +434,8 @@ namespace Content.Server.GameObjects.Components.GUI ActiveHand ??= name; + OnItemChanged?.Invoke(); + Dirty(); } @@ -435,6 +456,8 @@ namespace Content.Server.GameObjects.Components.GUI _activeHand = _hands.FirstOrDefault()?.Name; } + OnItemChanged?.Invoke(); + Dirty(); } @@ -645,13 +668,13 @@ namespace Content.Server.GameObjects.Components.GUI Dirty(); - if (!message.Entity.TryGetComponent(out IPhysicsComponent physics)) + if (!message.Entity.TryGetComponent(out ICollidableComponent collidable)) { return; } // set velocity to zero - physics.Stop(); + collidable.Stop(); return; } } diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index aa9f03ac10..1d1e52b6a2 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -33,9 +33,13 @@ namespace Content.Server.GameObjects.Components.GUI #pragma warning restore 649 [ViewVariables] - private readonly Dictionary SlotContainers = new Dictionary(); + private readonly Dictionary _slotContainers = new Dictionary(); - private KeyValuePair? HoverEntity; + private KeyValuePair? _hoverEntity; + + public IEnumerable Slots => _slotContainers.Keys; + + public event Action OnItemChanged; public override void Initialize() { @@ -43,7 +47,7 @@ namespace Content.Server.GameObjects.Components.GUI foreach (var slotName in InventoryInstance.SlotMasks) { - if (slotName != Slots.NONE) + if (slotName != EquipmentSlotDefines.Slots.NONE) { AddSlot(slotName); } @@ -58,7 +62,7 @@ namespace Content.Server.GameObjects.Components.GUI { var multiplier = 1f; - foreach (var (slot, containerSlot) in SlotContainers) + foreach (var (slot, containerSlot) in _slotContainers) { foreach (var entity in containerSlot.ContainedEntities) { @@ -81,7 +85,7 @@ namespace Content.Server.GameObjects.Components.GUI { var multiplier = 1f; - foreach (var (slot, containerSlot) in SlotContainers) + foreach (var (slot, containerSlot) in _slotContainers) { foreach (var entity in containerSlot.ContainedEntities) { @@ -99,7 +103,7 @@ namespace Content.Server.GameObjects.Components.GUI bool IEffectBlocker.CanSlip() { if(Owner.TryGetComponent(out InventoryComponent inventoryComponent) && - inventoryComponent.TryGetSlotItem(Slots.SHOES, out ItemComponent shoes) + inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.SHOES, out ItemComponent shoes) ) { return EffectBlockerSystem.CanSlip(shoes.Owner); @@ -110,7 +114,7 @@ namespace Content.Server.GameObjects.Components.GUI public override void OnRemove() { - var slots = SlotContainers.Keys.ToList(); + var slots = _slotContainers.Keys.ToList(); foreach (var slot in slots) { RemoveSlot(slot); @@ -140,15 +144,15 @@ namespace Content.Server.GameObjects.Components.GUI } public T GetSlotItem(Slots slot) where T : ItemComponent { - if (!SlotContainers.ContainsKey(slot)) + if (!_slotContainers.ContainsKey(slot)) { return null; } - var containedEntity = SlotContainers[slot].ContainedEntity; + var containedEntity = _slotContainers[slot].ContainedEntity; if (containedEntity?.Deleted == true) { - SlotContainers[slot] = null; + _slotContainers[slot] = null; containedEntity = null; Dirty(); } @@ -169,7 +173,7 @@ namespace Content.Server.GameObjects.Components.GUI /// /// The slot to put the item in. /// The item to insert into the slot. - /// The translated reason why the item cannot be equiped, if this function returns false. Can be null. + /// The translated reason why the item cannot be equipped, if this function returns false. Can be null. /// True if the item was successfully inserted, false otherwise. public bool Equip(Slots slot, ItemComponent item, out string reason) { @@ -184,7 +188,7 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - var inventorySlot = SlotContainers[slot]; + var inventorySlot = _slotContainers[slot]; if (!inventorySlot.Insert(item.Owner)) { return false; @@ -192,6 +196,8 @@ namespace Content.Server.GameObjects.Components.GUI _entitySystemManager.GetEntitySystem().EquippedInteraction(Owner, item.Owner, slot); + OnItemChanged?.Invoke(); + Dirty(); return true; @@ -239,7 +245,7 @@ namespace Content.Server.GameObjects.Components.GUI reason = Loc.GetString("You can't equip this!"); } - return pass && SlotContainers[slot].CanInsert(item.Owner); + return pass && _slotContainers[slot].CanInsert(item.Owner); } public bool CanEquip(Slots slot, ItemComponent item) => CanEquip(slot, item, out var _); @@ -258,7 +264,7 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - var inventorySlot = SlotContainers[slot]; + var inventorySlot = _slotContainers[slot]; var item = inventorySlot.ContainedEntity.GetComponent(); if (!inventorySlot.Remove(inventorySlot.ContainedEntity)) { @@ -271,6 +277,8 @@ namespace Content.Server.GameObjects.Components.GUI _entitySystemManager.GetEntitySystem().UnequippedInteraction(Owner, item.Owner, slot); + OnItemChanged?.Invoke(); + Dirty(); return true; @@ -288,7 +296,7 @@ namespace Content.Server.GameObjects.Components.GUI if (!ActionBlockerSystem.CanUnequip(Owner)) return false; - var InventorySlot = SlotContainers[slot]; + var InventorySlot = _slotContainers[slot]; return InventorySlot.ContainedEntity != null && InventorySlot.CanRemove(InventorySlot.ContainedEntity); } @@ -307,7 +315,12 @@ namespace Content.Server.GameObjects.Components.GUI } Dirty(); - return SlotContainers[slot] = ContainerManagerComponent.Create(GetSlotString(slot), Owner); + + _slotContainers[slot] = ContainerManagerComponent.Create(GetSlotString(slot), Owner); + + OnItemChanged?.Invoke(); + + return _slotContainers[slot]; } /// @@ -331,7 +344,10 @@ namespace Content.Server.GameObjects.Components.GUI "Unable to remove slot as the contained clothing could not be dropped"); } - SlotContainers.Remove(slot); + _slotContainers.Remove(slot); + + OnItemChanged?.Invoke(); + Dirty(); } @@ -342,7 +358,7 @@ namespace Content.Server.GameObjects.Components.GUI /// True if the slot exists, false otherwise. public bool HasSlot(Slots slot) { - return SlotContainers.ContainsKey(slot); + return _slotContainers.ContainsKey(slot); } /// @@ -354,7 +370,7 @@ namespace Content.Server.GameObjects.Components.GUI // make sure this is one of our containers. // Technically the correct way would be to enumerate the possible slot names // comparing with this container, but I might as well put the dictionary to good use. - if (!(container is ContainerSlot slot) || !SlotContainers.ContainsValue(slot)) + if (!(container is ContainerSlot slot) || !_slotContainers.ContainsValue(slot)) return; if (entity.TryGetComponent(out ItemComponent itemComp)) @@ -362,6 +378,8 @@ namespace Content.Server.GameObjects.Components.GUI itemComp.RemovedFromSlot(); } + OnItemChanged?.Invoke(); + Dirty(); } @@ -417,7 +435,7 @@ namespace Content.Server.GameObjects.Components.GUI if (activeHand != null && GetSlotItem(msg.Inventoryslot) == null) { var canEquip = CanEquip(msg.Inventoryslot, activeHand, out var reason); - HoverEntity = new KeyValuePair(msg.Inventoryslot, (activeHand.Owner.Uid, canEquip)); + _hoverEntity = new KeyValuePair(msg.Inventoryslot, (activeHand.Owner.Uid, canEquip)); Dirty(); } @@ -476,7 +494,7 @@ namespace Content.Server.GameObjects.Components.GUI public override ComponentState GetComponentState() { var list = new List>(); - foreach (var (slot, container) in SlotContainers) + foreach (var (slot, container) in _slotContainers) { if (container.ContainedEntity != null) { @@ -484,8 +502,8 @@ namespace Content.Server.GameObjects.Components.GUI } } - var hover = HoverEntity; - HoverEntity = null; + var hover = _hoverEntity; + _hoverEntity = null; return new InventoryComponentState(list, hover); } @@ -497,7 +515,7 @@ namespace Content.Server.GameObjects.Components.GUI return; } - foreach (var slot in SlotContainers.Values.ToList()) + foreach (var slot in _slotContainers.Values.ToList()) { foreach (var entity in slot.ContainedEntities) { diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs new file mode 100644 index 0000000000..e045fd2b5a --- /dev/null +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.GameObjects.EntitySystems.DoAfter; +using Content.Server.Interfaces; +using Content.Shared.GameObjects.Components.GUI; +using Content.Shared.GameObjects.Components.Inventory; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Server.GameObjects.Components.GUI +{ + [RegisterComponent] + public sealed class StrippableComponent : SharedStrippableComponent, IDragDrop + { + [Dependency] private IServerNotifyManager _notifyManager = default!; + + public const float StripDelay = 2f; + + [ViewVariables] + private BoundUserInterface _userInterface; + + private InventoryComponent _inventoryComponent; + private HandsComponent _handsComponent; + + public override void Initialize() + { + base.Initialize(); + + _userInterface = Owner.GetComponent().GetBoundUserInterface(StrippingUiKey.Key); + _userInterface.OnReceiveMessage += HandleUserInterfaceMessage; + + _inventoryComponent = Owner.GetComponent(); + _handsComponent = Owner.GetComponent(); + + _inventoryComponent.OnItemChanged += UpdateSubscribed; + + // Initial update. + UpdateSubscribed(); + } + + private void UpdateSubscribed() + { + var inventory = GetInventorySlots(); + var hands = GetHandSlots(); + + _userInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands)); + } + + public bool CanDragDrop(DragDropEventArgs eventArgs) + { + return eventArgs.User.HasComponent() + && eventArgs.Target != eventArgs.Dropped && eventArgs.Target == eventArgs.User; + } + + public bool DragDrop(DragDropEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false; + + OpenUserInterface(actor.playerSession); + return true; + } + + private Dictionary GetInventorySlots() + { + var dictionary = new Dictionary(); + + foreach (var slot in _inventoryComponent.Slots) + { + dictionary[slot] = _inventoryComponent.GetSlotItem(slot)?.Owner.Name ?? "None"; + } + + return dictionary; + } + + private Dictionary GetHandSlots() + { + var dictionary = new Dictionary(); + + foreach (var hand in _handsComponent.Hands) + { + dictionary[hand] = _handsComponent.GetItem(hand)?.Owner.Name ?? "None"; + } + + return dictionary; + } + + private void OpenUserInterface(IPlayerSession session) + { + _userInterface.Open(session); + } + + /// + /// Places item in user's active hand to an inventory slot. + /// + private async void PlaceActiveHandItemInInventory(IEntity user, Slots slot) + { + var inventory = Owner.GetComponent(); + var userHands = user.GetComponent(); + var item = userHands.GetActiveHand; + + bool Check() + { + if (!ActionBlockerSystem.CanInteract(user)) + return false; + + if (item == null) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("You aren't holding anything!")); + return false; + } + + if (!userHands.CanDrop(userHands.ActiveHand!)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("You can't drop that!")); + return false; + } + + if (!inventory.HasSlot(slot)) + return false; + + if (inventory.TryGetSlotItem(slot, out ItemComponent _)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} already {0:have} something there!", Owner)); + return false; + } + + if (!inventory.CanEquip(slot, item)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot equip that there!", Owner)); + return false; + } + + return true; + } + + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true, + }; + + var result = await doAfterSystem.DoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + userHands.Drop(item!.Owner, false); + inventory.Equip(slot, item!.Owner); + + UpdateSubscribed(); + } + + /// + /// Places item in user's active hand in one of the entity's hands. + /// + private async void PlaceActiveHandItemInHands(IEntity user, string hand) + { + var hands = Owner.GetComponent(); + var userHands = user.GetComponent(); + var item = userHands.GetActiveHand; + + bool Check() + { + if (!ActionBlockerSystem.CanInteract(user)) + return false; + + if (item == null) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("You aren't holding anything!")); + return false; + } + + if (!userHands.CanDrop(userHands.ActiveHand!)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("You can't drop that!")); + return false; + } + + if (!hands.HasHand(hand)) + return false; + + if (hands.TryGetItem(hand, out var _)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} already {0:have} something there!", Owner)); + return false; + } + + if (!hands.CanPutInHand(item, hand)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot put that there!", Owner)); + return false; + } + + return true; + } + + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true, + }; + + var result = await doAfterSystem.DoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + userHands.Drop(hand, false); + hands.PutInHand(item, hand, false); + UpdateSubscribed(); + } + + /// + /// Takes an item from the inventory and places it in the user's active hand. + /// + private async void TakeItemFromInventory(IEntity user, Slots slot) + { + var inventory = Owner.GetComponent(); + var userHands = user.GetComponent(); + + bool Check() + { + if (!ActionBlockerSystem.CanInteract(user)) + return false; + + if (!inventory.HasSlot(slot)) + return false; + + if (!inventory.TryGetSlotItem(slot, out ItemComponent itemToTake)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} {0:have} nothing there!", Owner)); + return false; + } + + if (!inventory.CanUnequip(slot)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot unequip that!", Owner)); + return false; + } + + return true; + } + + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + }; + + var result = await doAfterSystem.DoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + var item = inventory.GetSlotItem(slot); + inventory.Unequip(slot); + userHands.PutInHandOrDrop(item); + UpdateSubscribed(); + } + + /// + /// Takes an item from a hand and places it in the user's active hand. + /// + private async void TakeItemFromHands(IEntity user, string hand) + { + var hands = Owner.GetComponent(); + var userHands = user.GetComponent(); + + bool Check() + { + if (!ActionBlockerSystem.CanInteract(user)) + return false; + + if (!hands.HasHand(hand)) + return false; + + if (!hands.TryGetItem(hand, out var heldItem)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} {0:have} nothing there!", Owner)); + return false; + } + + if (!hands.CanDrop(hand)) + { + _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot drop that!", Owner)); + return false; + } + + return true; + } + + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + }; + + var result = await doAfterSystem.DoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + var item = hands.GetItem(hand); + hands.Drop(hand, false); + userHands.PutInHandOrDrop(item); + UpdateSubscribed(); + } + + private void HandleUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) + { + var user = obj.Session.AttachedEntity; + if (user == null || !(user.TryGetComponent(out HandsComponent userHands))) return; + + var placingItem = userHands.GetActiveHand != null; + + switch (obj.Message) + { + case StrippingInventoryButtonPressed inventoryMessage: + var inventory = Owner.GetComponent(); + + if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _)) + placingItem = false; + + if(placingItem) + PlaceActiveHandItemInInventory(user, inventoryMessage.Slot); + else + TakeItemFromInventory(user, inventoryMessage.Slot); + break; + case StrippingHandButtonPressed handMessage: + var hands = Owner.GetComponent(); + + if (hands.TryGetItem(handMessage.Hand, out _)) + placingItem = false; + + if(placingItem) + PlaceActiveHandItemInHands(user, handMessage.Hand); + else + TakeItemFromHands(user, handMessage.Hand); + break; + default: + break; + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 79bd216244..73d1fd09ca 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -10,7 +10,6 @@ using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; -using Content.Shared.Physics; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystems; @@ -50,8 +49,6 @@ namespace Content.Server.GameObjects.Components.Items.Storage private bool _occludesLight; private bool _open; private bool _isWeldedShut; - private int _collisionMaskStorage; - private int _collisionLayerStorage; [ViewVariables] protected Container Contents; @@ -202,18 +199,13 @@ namespace Content.Server.GameObjects.Components.Items.Storage { if (!_isCollidableWhenOpen && Owner.TryGetComponent(out var collidableComponent)) { - var physShape = collidableComponent.PhysicsShapes[0]; if (Open) { - _collisionMaskStorage = physShape.CollisionMask; - physShape.CollisionMask = (int)CollisionGroup.Impassable; - _collisionLayerStorage = physShape.CollisionLayer; - physShape.CollisionLayer = (int)CollisionGroup.None; + collidableComponent.Hard = false; } else { - physShape.CollisionMask = _collisionMaskStorage; - physShape.CollisionLayer = _collisionLayerStorage; + collidableComponent.Hard = true; } } diff --git a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs index daf1f94d07..67eca347f2 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs @@ -14,7 +14,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; -using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Serialization; @@ -29,7 +28,6 @@ namespace Content.Server.GameObjects.Components.Items.Storage public override uint? NetID => ContentNetIDs.ITEM; #pragma warning disable 649 - [Dependency] private readonly IRobustRandom _robustRandom; [Dependency] private readonly IMapManager _mapManager; #pragma warning restore 649 @@ -93,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage return false; } - if (Owner.TryGetComponent(out PhysicsComponent physics) && + if (Owner.TryGetComponent(out CollidableComponent physics) && physics.Anchored) { return false; diff --git a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs index 20271f0b23..d148490bd9 100644 --- a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs @@ -132,12 +132,12 @@ namespace Content.Server.GameObjects.Components.Mobs if (!HasMind) { message.AddMarkup(!dead - ? $"[color=red]" + Loc.GetString("{0:They} are totally catatonic. The stresses of life in deep-space must have been too much for {0:them}. Any recovery is unlikely.", Owner) + "[/color]" + ? $"[color=red]" + Loc.GetString("{0:They} {0:are} totally catatonic. The stresses of life in deep-space must have been too much for {0:them}. Any recovery is unlikely.", Owner) + "[/color]" : $"[color=purple]" + Loc.GetString("{0:Their} soul has departed.", Owner) + "[/color]"); } else if (Mind?.Session == null) { - message.AddMarkup("[color=yellow]" + Loc.GetString("{0:They} have a blank, absent-minded stare and appears completely unresponsive to anything. {0:They} may snap out of it soon.", Owner) + "[/color]"); + message.AddMarkup("[color=yellow]" + Loc.GetString("{0:They} {0:have} a blank, absent-minded stare and appears completely unresponsive to anything. {0:They} may snap out of it soon.", Owner) + "[/color]"); } } } diff --git a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index 0c89727f0c..26b450ca47 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -42,9 +42,9 @@ namespace Content.Server.GameObjects.Components.Movement { base.Initialize(); - // This component requires a physics component. - if (!Owner.HasComponent()) - Owner.AddComponent(); + // This component requires a collidable component. + if (!Owner.HasComponent()) + Owner.AddComponent(); } /// diff --git a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs index 62063854f2..85bb04b84b 100644 --- a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs @@ -71,22 +71,15 @@ namespace Content.Server.GameObjects.Components.Movement _entityManager.TryGetEntity(grid.GridEntityId, out var gridEntity)) { //TODO: Switch to shuttle component - if (!gridEntity.TryGetComponent(out IPhysicsComponent physComp)) + if (!gridEntity.TryGetComponent(out ICollidableComponent collidable)) { - physComp = gridEntity.AddComponent(); - physComp.Mass = 1; + collidable = gridEntity.AddComponent(); + collidable.Mass = 1; + collidable.CanCollide = true; + collidable.PhysicsShapes.Add(new PhysShapeGrid(grid)); } - //TODO: Is this always true? - if (!gridEntity.HasComponent()) - { - var collideComp = gridEntity.AddComponent(); - collideComp.CanCollide = true; - //collideComp.IsHardCollidable = true; - collideComp.PhysicsShapes.Add(new PhysShapeGrid(grid)); - } - - var controller = physComp.EnsureController(); + var controller = collidable.EnsureController(); controller.Push(CalcNewVelocity(direction, enabled), CurrentWalkSpeed); } } diff --git a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs index 00613c2b96..9b633dc688 100644 --- a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs @@ -92,9 +92,9 @@ namespace Content.Server.GameObjects.Components.Projectiles } if (!entity.Deleted && entity.TryGetComponent(out CameraRecoilComponent recoilComponent) - && Owner.TryGetComponent(out IPhysicsComponent physicsComponent)) + && Owner.TryGetComponent(out ICollidableComponent collidableComponent)) { - var direction = physicsComponent.LinearVelocity.Normalized; + var direction = collidableComponent.LinearVelocity.Normalized; recoilComponent.Kick(direction); } } diff --git a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs index 2ae7c7f5c9..b072072bc5 100644 --- a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs @@ -88,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Projectiles public void StartThrow(Vector2 direction, float speed) { - var comp = Owner.GetComponent(); + var comp = Owner.GetComponent(); comp.Status = BodyStatus.InAir; var controller = comp.EnsureController(); diff --git a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs index 73194471c8..ffbcfbe4cc 100644 --- a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs +++ b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs @@ -21,9 +21,9 @@ namespace Content.Server.GameObjects.Components.Rotatable private void TryRotate(IEntity user, Angle angle) { - if (Owner.TryGetComponent(out IPhysicsComponent physics)) + if (Owner.TryGetComponent(out ICollidableComponent collidable)) { - if (physics.Anchored) + if (collidable.Anchored) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("It's stuck.")); return; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index b3d6b8e924..4b0553fe2b 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs @@ -5,7 +5,6 @@ using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Projectiles; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; -using Content.Server.Interfaces; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Weapons.Ranged; using Content.Shared.GameObjects.EntitySystems; @@ -43,7 +42,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels #pragma warning disable 649 [Dependency] private IGameTiming _gameTiming; [Dependency] private IRobustRandom _robustRandom; - [Dependency] private readonly IServerNotifyManager _notifyManager; #pragma warning restore 649 public override FireRateSelector FireRateSelector => _fireRateSelector; @@ -385,15 +383,15 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels projectileAngle = angle; } - var physicsComponent = projectile.GetComponent(); - physicsComponent.Status = BodyStatus.InAir; + var collidableComponent = projectile.GetComponent(); + collidableComponent.Status = BodyStatus.InAir; projectile.Transform.GridPosition = Owner.Transform.GridPosition; var projectileComponent = projectile.GetComponent(); projectileComponent.IgnoreEntity(shooter); projectile - .GetComponent() + .GetComponent() .EnsureController() .LinearVelocity = projectileAngle.ToVec() * velocity; diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs index ea9ca44ec1..a9a7899300 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs @@ -27,7 +27,6 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering #pragma warning disable 649 [Dependency] private IMapManager _mapManager; - [Dependency] private IEntityManager _entityManager; [Dependency] private IPauseManager _pauseManager; #pragma warning restore 649 private PathfindingSystem _pathfindingSystem; diff --git a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs index 71acf54c52..327104a148 100644 --- a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs @@ -17,18 +17,14 @@ namespace Content.Server.GameObjects.EntitySystems [UsedImplicitly] public class AtmosphereSystem : EntitySystem { -#pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPauseManager _pauseManager = default!; -#pragma warning restore 649 public override void Initialize() { base.Initialize(); _mapManager.TileChanged += OnTileChanged; - EntityQuery = new MultipleTypeEntityQuery(new List(){typeof(IGridAtmosphereComponent)}); } public IGridAtmosphereComponent? GetGridAtmosphere(GridId gridId) @@ -36,7 +32,7 @@ namespace Content.Server.GameObjects.EntitySystems // TODO Return space grid atmosphere for invalid grids or grids with no atmos var grid = _mapManager.GetGrid(gridId); - if (!_entityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return null; + if (!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return null; return gridEnt.TryGetComponent(out IGridAtmosphereComponent atmos) ? atmos : null; } @@ -45,13 +41,12 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var gridEnt in RelevantEntities) + foreach (var (mapGridComponent, gridAtmosphereComponent) in EntityManager.ComponentManager.EntityQuery()) { - var grid = gridEnt.GetComponent(); - if (_pauseManager.IsGridPaused(grid.GridIndex)) + if (_pauseManager.IsGridPaused(mapGridComponent.GridIndex)) continue; - gridEnt.GetComponent().Update(frameTime); + gridAtmosphereComponent.Update(frameTime); } } diff --git a/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs index 53fa77c516..1b83b0b3fe 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs @@ -4,6 +4,7 @@ using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Utility; namespace Content.Server.GameObjects.EntitySystems.Click @@ -19,7 +20,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click static ExamineSystem() { _entityNotFoundMessage = new FormattedMessage(); - _entityNotFoundMessage.AddText("That entity doesn't exist"); + _entityNotFoundMessage.AddText(Loc.GetString("That entity doesn't exist")); } public override void Initialize() diff --git a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs index 35c99acd98..84250d4e91 100644 --- a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs @@ -58,23 +58,13 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { - foreach (var entity in RelevantEntities) + foreach (var (moverComponent, collidableComponent) in EntityManager.ComponentManager.EntityQuery()) { + var entity = moverComponent.Owner; if (_pauseManager.IsEntityPaused(entity)) - { continue; - } - var mover = entity.GetComponent(); - var physics = entity.GetComponent(); - if (entity.TryGetComponent(out var collider)) - { - UpdateKinematics(entity.Transform, mover, physics, collider); - } - else - { - UpdateKinematics(entity.Transform, mover, physics); - } + UpdateKinematics(entity.Transform, moverComponent, collidableComponent); } } @@ -93,7 +83,7 @@ namespace Content.Server.GameObjects.EntitySystems ev.Entity.RemoveComponent(); } - if (ev.Entity.TryGetComponent(out IPhysicsComponent physics) && + if (ev.Entity.TryGetComponent(out ICollidableComponent physics) && physics.TryGetController(out MoverController controller)) { controller.StopMoving(); diff --git a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs index b0720ccfb3..6de086226a 100644 --- a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs @@ -2,6 +2,7 @@ using System.Reflection; using Content.Shared.GameObjects.Verbs; using Robust.Server.Interfaces.Player; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; @@ -37,6 +38,12 @@ namespace Content.Server.GameObjects.EntitySystems var session = eventArgs.SenderSession; var userEntity = session.AttachedEntity; + if (userEntity == null) + { + Logger.Warning($"{nameof(UseVerb)} called by player {session} with no attached entity."); + return; + } + foreach (var (component, verb) in VerbUtility.GetVerbs(entity)) { if ($"{component.GetType()}:{verb.GetType()}" != use.VerbKey) @@ -44,14 +51,14 @@ namespace Content.Server.GameObjects.EntitySystems continue; } - if (verb.RequireInteractionRange) + if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity)) { - var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition) - .LengthSquared; - if (distanceSquared > VerbUtility.InteractionRangeSquared) - { - break; - } + break; + } + + if (verb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity)) + { + break; } verb.Activate(userEntity, component); @@ -65,14 +72,15 @@ namespace Content.Server.GameObjects.EntitySystems continue; } - if (globalVerb.RequireInteractionRange) + if (globalVerb.RequireInteractionRange && + !VerbUtility.InVerbUseRange(userEntity, entity)) { - var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition) - .LengthSquared; - if (distanceSquared > VerbUtility.InteractionRangeSquared) - { - break; - } + break; + } + + if (globalVerb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity)) + { + break; } globalVerb.Activate(userEntity, entity); @@ -92,6 +100,12 @@ namespace Content.Server.GameObjects.EntitySystems var userEntity = player.AttachedEntity; + if (userEntity == null) + { + Logger.Warning($"{nameof(UseVerb)} called by player {player} with no attached entity."); + return; + } + var data = new List(); //Get verbs, component dependent. foreach (var (component, verb) in VerbUtility.GetVerbs(entity)) @@ -99,6 +113,9 @@ namespace Content.Server.GameObjects.EntitySystems if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity)) continue; + if (verb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity)) + continue; + var verbData = verb.GetData(userEntity, component); if (verbData.IsInvisible) continue; @@ -113,6 +130,9 @@ namespace Content.Server.GameObjects.EntitySystems if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity)) continue; + if (globalVerb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity)) + continue; + var verbData = globalVerb.GetData(userEntity, entity); if (verbData.IsInvisible) continue; diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs index 50365c6569..bd2f43a13d 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -62,7 +62,7 @@ namespace Content.Server.GameTicking.GamePresets for (var i = 0; i < numTraitors; i++) { IPlayerSession traitor; - if(prefList.Count() == 0) + if(prefList.Count == 0) { traitor = _random.PickAndTake(list); Logger.InfoS("preset", "Insufficient preferred traitors, picking at random."); diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 8c3ed03f1b..bef68e027d 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -264,7 +264,7 @@ namespace Content.Server.GameTicking // Spawn everybody in! foreach (var (player, job) in assignedJobs) { - SpawnPlayer(player, job, false); + SpawnPlayer(player, profiles[player.Name], job, false); } // Time to start the preset. @@ -344,7 +344,7 @@ namespace Content.Server.GameTicking if (LobbyEnabled) _playerJoinLobby(targetPlayer); else - SpawnPlayer(targetPlayer); + SpawnPlayerAsync(targetPlayer); } public void MakeObserve(IPlayerSession player) @@ -358,7 +358,7 @@ namespace Content.Server.GameTicking { if (!_playersInLobby.ContainsKey(player)) return; - SpawnPlayer(player, jobId); + SpawnPlayerAsync(player, jobId); } public void ToggleReady(IPlayerSession player, bool ready) @@ -620,7 +620,7 @@ namespace Content.Server.GameTicking _playerJoinLobby(player); } - + EntitySystem.Get().ResettingCleanup(); EntitySystem.Get().ResettingCleanup(); EntitySystem.Get().ResetLayouts(); @@ -684,13 +684,13 @@ namespace Content.Server.GameTicking return; } - SpawnPlayer(session); + SpawnPlayerAsync(session); } else { if (data.Mind.CurrentEntity == null) { - SpawnPlayer(session); + SpawnPlayerAsync(session); } else { @@ -744,14 +744,22 @@ namespace Content.Server.GameTicking }, _updateShutdownCts.Token); } - private async void SpawnPlayer(IPlayerSession session, string jobId = null, bool lateJoin = true) + private async void SpawnPlayerAsync(IPlayerSession session, string jobId = null, bool lateJoin = true) { - _playerJoinGame(session); - var character = (HumanoidCharacterProfile) (await _prefsManager .GetPreferencesAsync(session.SessionId.Username)) .SelectedCharacter; + SpawnPlayer(session, character, jobId, lateJoin); + } + + private void SpawnPlayer(IPlayerSession session, + HumanoidCharacterProfile character, + string jobId = null, + bool lateJoin = true) + { + _playerJoinGame(session); + var data = session.ContentData(); data.WipeMind(); data.Mind = new Mind(session.SessionId) diff --git a/Content.Server/GlobalVerbs/ControlMobVerb.cs b/Content.Server/GlobalVerbs/ControlMobVerb.cs index 2b69c30c46..390094d763 100644 --- a/Content.Server/GlobalVerbs/ControlMobVerb.cs +++ b/Content.Server/GlobalVerbs/ControlMobVerb.cs @@ -13,6 +13,7 @@ namespace Content.Server.GlobalVerbs public class ControlMobVerb : GlobalVerb { public override bool RequireInteractionRange => false; + public override bool BlockedByContainers => false; public override void GetData(IEntity user, IEntity target, VerbData data) { diff --git a/Content.Server/GlobalVerbs/RejuvenateVerb.cs b/Content.Server/GlobalVerbs/RejuvenateVerb.cs index 11b11e8c2a..5f074e085b 100644 --- a/Content.Server/GlobalVerbs/RejuvenateVerb.cs +++ b/Content.Server/GlobalVerbs/RejuvenateVerb.cs @@ -16,6 +16,7 @@ namespace Content.Server.GlobalVerbs class RejuvenateVerb : GlobalVerb { public override bool RequireInteractionRange => false; + public override bool BlockedByContainers => false; public override void GetData(IEntity user, IEntity target, VerbData data) { diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs index d631716cc9..e716533285 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -1,6 +1,11 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; +using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects.Components.Container; @@ -12,10 +17,20 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items { public interface IHandsComponent : ISharedHandsComponent { + /// + /// Invoked when the hand contents changes or when a hand is added/removed. + /// + event Action? OnItemChanged; + + /// + /// The hands in this component. + /// + IEnumerable Hands { get; } + /// /// The hand name of the currently active hand. /// - string ActiveHand { get; set; } + string? ActiveHand { get; set; } /// /// Enumerates over every held item. @@ -27,12 +42,20 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// /// The name of the hand to get. /// The item in the held, null if no item is held - ItemComponent GetItem(string handName); + ItemComponent? GetItem(string handName); + + /// + /// Attempts to get an item in a hand. + /// + /// The name of the hand to get. + /// The item in the held, null if no item is held + /// Whether it was holding an item + bool TryGetItem(string handName, [MaybeNullWhen(false)] out ItemComponent item); /// /// Gets item held by the current active hand /// - ItemComponent GetActiveHand { get; } + ItemComponent? GetActiveHand { get; } /// /// Puts an item into any empty hand, preferring the active hand. @@ -78,7 +101,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// /// true if the entity is held, false otherwise /// - bool TryHand(IEntity entity, out string handName); + bool TryHand(IEntity entity, [MaybeNullWhen(false)] out string handName); /// /// Drops the item contained in the slot to the same position as our entity. diff --git a/Content.Server/Throw/ThrowHelper.cs b/Content.Server/Throw/ThrowHelper.cs index 6b62531a7f..ad8e76b4bf 100644 --- a/Content.Server/Throw/ThrowHelper.cs +++ b/Content.Server/Throw/ThrowHelper.cs @@ -85,7 +85,7 @@ namespace Content.Server.Throw projComp.StartThrow(angle.ToVec(), spd); if (throwSourceEnt != null && - throwSourceEnt.TryGetComponent(out var physics) && + throwSourceEnt.TryGetComponent(out var physics) && physics.TryGetController(out MoverController mover)) { var physicsMgr = IoCManager.Resolve(); @@ -136,7 +136,7 @@ namespace Content.Server.Throw var distance = (targetLoc.ToMapPos(mapManager) - sourceLoc.ToMapPos(mapManager)).Length; var throwDuration = ThrownItemComponent.DefaultThrowTime; var mass = 1f; - if (thrownEnt.TryGetComponent(out IPhysicsComponent physicsComponent)) + if (thrownEnt.TryGetComponent(out ICollidableComponent physicsComponent)) { mass = physicsComponent.Mass; } diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs index 1ce14ae17f..9bcd7edbf0 100644 --- a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs +++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs @@ -51,7 +51,7 @@ namespace Content.Shared.GameObjects.Components.Disposal } [Serializable, NetSerializable] - public enum State + public enum PressureState { Ready, Pressurizing diff --git a/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs b/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs new file mode 100644 index 0000000000..bcc9b2fc7c --- /dev/null +++ b/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Inventory; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Shared.GameObjects.Components.GUI +{ + public class SharedStrippableComponent : Component + { + public override string Name => "Strippable"; + + [NetSerializable, Serializable] + public enum StrippingUiKey + { + Key, + } + } + + [NetSerializable, Serializable] + public class StrippingInventoryButtonPressed : BoundUserInterfaceMessage + { + public Slots Slot { get; } + + public StrippingInventoryButtonPressed(Slots slot) + { + Slot = slot; + } + } + + [NetSerializable, Serializable] + public class StrippingHandButtonPressed : BoundUserInterfaceMessage + { + public string Hand { get; } + + public StrippingHandButtonPressed(string hand) + { + Hand = hand; + } + } + + [NetSerializable, Serializable] + public class StrippingBoundUserInterfaceState : BoundUserInterfaceState + { + public Dictionary Inventory { get; } + public Dictionary Hands { get; } + + public StrippingBoundUserInterfaceState(Dictionary inventory, Dictionary hands) + { + Inventory = inventory; + Hands = hands; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs index da88fdd468..9c74d666bc 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs @@ -142,11 +142,11 @@ namespace Content.Shared.GameObjects.Components.Movement /// public override void OnAdd() { - // This component requires that the entity has a PhysicsComponent. - if (!Owner.HasComponent()) + // This component requires that the entity has a CollidableComponent. + if (!Owner.HasComponent()) Logger.Error( $"[ECS] {Owner.Prototype?.Name} - {nameof(SharedPlayerInputMoverComponent)} requires" + - $" {nameof(IPhysicsComponent)}. "); + $" {nameof(ICollidableComponent)}. "); base.OnAdd(); } diff --git a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs index 4181a2e36d..4d05195dba 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs @@ -50,13 +50,12 @@ namespace Content.Shared.GameObjects.Components.Movement || _slipped.Contains(entity.Uid) || !entity.TryGetComponent(out SharedStunnableComponent stun) || !entity.TryGetComponent(out ICollidableComponent otherBody) - || !entity.TryGetComponent(out IPhysicsComponent otherPhysics) || !Owner.TryGetComponent(out ICollidableComponent body)) { return false; } - if (otherPhysics.LinearVelocity.Length < RequiredSlipSpeed || stun.KnockedDown) + if (otherBody.LinearVelocity.Length < RequiredSlipSpeed || stun.KnockedDown) { return false; } diff --git a/Content.Shared/GameObjects/EntitySystems/ExamineSystemShared.cs b/Content.Shared/GameObjects/EntitySystems/ExamineSystemShared.cs index 0e09c98af2..71e0190d8d 100644 --- a/Content.Shared/GameObjects/EntitySystems/ExamineSystemShared.cs +++ b/Content.Shared/GameObjects/EntitySystems/ExamineSystemShared.cs @@ -1,5 +1,7 @@ -using Content.Shared.GameObjects.Components.Mobs; +using System; +using Content.Shared.GameObjects.Components.Mobs; using JetBrains.Annotations; +using Robust.Shared.Containers; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Maths; @@ -22,6 +24,15 @@ namespace Content.Shared.GameObjects.EntitySystems public const float ExamineRangeSquared = ExamineRange * ExamineRange; protected const float ExamineDetailsRange = 3f; + private static bool IsInDetailsRange(IEntity examiner, IEntity entity) + { + return Get() + .InRangeUnobstructed(examiner.Transform.MapPosition, entity.Transform.MapPosition, + ExamineDetailsRange, predicate: entity0 => entity0 == examiner || entity0 == entity, + ignoreInsideBlocker: true) && + examiner.IsInSameOrNoContainer(entity); + } + [Pure] protected static bool CanExamine(IEntity examiner, IEntity examined) { @@ -40,9 +51,16 @@ namespace Content.Shared.GameObjects.EntitySystems return false; } + Func predicate = entity => entity == examiner || entity == examined; + + if (ContainerHelpers.TryGetContainer(examiner, out var container)) + { + predicate += entity => entity == container.Owner; + } + return Get() .InRangeUnobstructed(examiner.Transform.MapPosition, examined.Transform.MapPosition, - ExamineRange, predicate: entity => entity == examiner || entity == examined, ignoreInsideBlocker:true); + ExamineRange, predicate: predicate, ignoreInsideBlocker:true); } public static FormattedMessage GetExamineText(IEntity entity, IEntity examiner) @@ -60,15 +78,11 @@ namespace Content.Shared.GameObjects.EntitySystems message.PushColor(Color.DarkGray); - var inDetailsRange = Get() - .InRangeUnobstructed(examiner.Transform.MapPosition, entity.Transform.MapPosition, - ExamineDetailsRange, predicate: entity0 => entity0 == examiner || entity0 == entity, ignoreInsideBlocker: true); - //Add component statuses from components that report one foreach (var examineComponent in entity.GetAllComponents()) { var subMessage = new FormattedMessage(); - examineComponent.Examine(subMessage, inDetailsRange); + examineComponent.Examine(subMessage, IsInDetailsRange(examiner, entity)); if (subMessage.Tags.Count == 0) continue; diff --git a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs index 8c2cdcc9c3..7c9a4537a2 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs @@ -29,8 +29,6 @@ namespace Content.Shared.GameObjects.EntitySystems { base.Initialize(); - EntityQuery = new TypeEntityQuery(typeof(IMoverComponent)); - var moveUpCmdHandler = new MoverDirInputCmdHandler(Direction.North); var moveLeftCmdHandler = new MoverDirInputCmdHandler(Direction.West); var moveRightCmdHandler = new MoverDirInputCmdHandler(Direction.East); @@ -54,18 +52,17 @@ namespace Content.Shared.GameObjects.EntitySystems base.Shutdown(); } - protected void UpdateKinematics(ITransformComponent transform, IMoverComponent mover, IPhysicsComponent physics, - ICollidableComponent? collider = null) + protected void UpdateKinematics(ITransformComponent transform, IMoverComponent mover, ICollidableComponent collidable) { - physics.EnsureController(); + collidable.EnsureController(); var weightless = !transform.Owner.HasComponent() && _physicsManager.IsWeightless(transform.GridPosition); - if (weightless && collider != null) + if (weightless) { // No gravity: is our entity touching anything? - var touching = IsAroundCollider(transform, mover, collider); + var touching = IsAroundCollider(transform, mover, collidable); if (!touching) { @@ -78,18 +75,16 @@ namespace Content.Shared.GameObjects.EntitySystems var combined = walkDir + sprintDir; if (combined.LengthSquared < 0.001 || !ActionBlockerSystem.CanMove(mover.Owner) && !weightless) { - if (physics.TryGetController(out MoverController controller)) + if (collidable.TryGetController(out MoverController controller)) { controller.StopMoving(); } } else { - //Console.WriteLine($"{IoCManager.Resolve().TickStamp}: {combined}"); - if (weightless) { - if (physics.TryGetController(out MoverController controller)) + if (collidable.TryGetController(out MoverController controller)) { controller.Push(combined, mover.CurrentPushSpeed); } @@ -99,12 +94,13 @@ namespace Content.Shared.GameObjects.EntitySystems } var total = walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed; - //Console.WriteLine($"{walkDir} ({mover.CurrentWalkSpeed}) + {sprintDir} ({mover.CurrentSprintSpeed}): {total}"); - {if (physics.TryGetController(out MoverController controller)) { - controller.Move(total, 1); - }} + if (collidable.TryGetController(out MoverController controller)) + { + controller.Move(total, 1); + } + } transform.LocalRotation = total.GetDir().ToAngle(); diff --git a/Content.Shared/GameObjects/Verbs/GlobalVerb.cs b/Content.Shared/GameObjects/Verbs/GlobalVerb.cs index ad5ebed628..e14830fab3 100644 --- a/Content.Shared/GameObjects/Verbs/GlobalVerb.cs +++ b/Content.Shared/GameObjects/Verbs/GlobalVerb.cs @@ -20,6 +20,12 @@ namespace Content.Shared.GameObjects.Verbs /// public virtual bool RequireInteractionRange => true; + /// + /// If true, this verb requires both the user and the entity on which + /// this verb resides to be in the same container or no container. + /// + public virtual bool BlockedByContainers => true; + /// /// Gets the visible verb data for the user. /// diff --git a/Content.Shared/GameObjects/Verbs/Verb.cs b/Content.Shared/GameObjects/Verbs/Verb.cs index b4b1f27751..99cf50b24d 100644 --- a/Content.Shared/GameObjects/Verbs/Verb.cs +++ b/Content.Shared/GameObjects/Verbs/Verb.cs @@ -20,6 +20,12 @@ namespace Content.Shared.GameObjects.Verbs /// public virtual bool RequireInteractionRange => true; + /// + /// If true, this verb requires both the user and the entity on which + /// this verb resides to be in the same container or no container. + /// + public virtual bool BlockedByContainers => true; + /// /// Gets the visible verb data for the user. /// diff --git a/Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs b/Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs index 3a37046b20..acf2711f28 100644 --- a/Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs +++ b/Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs @@ -18,7 +18,6 @@ namespace Content.Shared.Health.BodySystem.Mechanism private string _name; private string _description; private string _examineMessage; - private string _spritePath; private string _rsiPath; private string _rsiState; private int _durability; diff --git a/Content.Shared/Physics/CollisionGroup.cs b/Content.Shared/Physics/CollisionGroup.cs index 8ef86b5a6f..0834d69787 100644 --- a/Content.Shared/Physics/CollisionGroup.cs +++ b/Content.Shared/Physics/CollisionGroup.cs @@ -20,6 +20,7 @@ namespace Content.Shared.Physics VaultImpassable = 1 << 3, // 8 Things that cannot be jumped over, not half walls or tables SmallImpassable = 1 << 4, // 16 Things a smaller object - a cat, a crab - can't go through - a wall, but not a computer terminal or a table Clickable = 1 << 5, // 32 Temporary "dummy" layer to ensure that objects can still be clicked even if they don't collide with anything (you can't interact with objects that have no layer, including items) + GhostImpassable = 1 << 6, // 64 Things impassible by ghosts/observers, ie blessed tiles or forcefields MapGrid = MapGridHelpers.CollisionGroup, // Map grids, like shuttles. This is the actual grid itself, not the walls or other entities connected to the grid. diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index eba5ba58d4..4b6fea353d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -7,6 +7,12 @@ - type: Mind - type: Physics mass: 5 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.35,-0.35,0.35,0.35" + mask: + - GhostImpassable - type: Eye zoom: 0.5, 0.5 drawFov: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index b5a18678fd..9259c28043 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -144,6 +144,12 @@ - type: Pullable - type: CanSeeGases - type: DoAfter + - type: Strippable + - type: UserInterface + interfaces: + - key: enum.StrippingUiKey.Key + type: StrippableBoundUserInterface + - type: entity save: false diff --git a/RobustToolbox b/RobustToolbox index 5819e5ee92..1934428c95 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 5819e5ee92b287da6306807f8e05efc457c06a6c +Subproject commit 1934428c95d44210cfc9c155c7d15405d4a374c4 diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index f9a53158a2..c93412bf8d 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -71,10 +71,12 @@ True True True + True True True True True + True True True True