diff --git a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs index 5b6358d0bd..8503644d77 100644 --- a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs @@ -92,6 +92,23 @@ namespace Content.Client.GameObjects.Components.Mobs _cooldown.Clear(); } + public override void ChangeStatusEffectIcon(StatusEffect effect, string icon) + { + if (_status.TryGetValue(effect, out var value) && + value.Icon == icon) + { + return; + } + + _status[effect] = new StatusEffectStatus + { + Icon = icon, + Cooldown = value.Cooldown + }; + + Dirty(); + } + public void UpdateStatusEffects() { if (!CurrentlyControlled || _ui == null) @@ -132,10 +149,15 @@ namespace Content.Client.GameObjects.Components.Mobs SendNetworkMessage(new ClickStatusMessage(status.Effect)); } - public void RemoveStatusEffect(StatusEffect name) + public override void RemoveStatusEffect(StatusEffect effect) { - _status.Remove(name); + if (!_status.Remove(effect)) + { + return; + } + UpdateStatusEffects(); + Dirty(); } public void FrameUpdate(float frameTime) diff --git a/Content.Client/GameObjects/Components/Pulling/PullableComponent.cs b/Content.Client/GameObjects/Components/Pulling/PullableComponent.cs new file mode 100644 index 0000000000..43f3049fc8 --- /dev/null +++ b/Content.Client/GameObjects/Components/Pulling/PullableComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.GameObjects.Components.Pulling; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components.Pulling +{ + [RegisterComponent] + [ComponentReference(typeof(SharedPullableComponent))] + public class PullableComponent : SharedPullableComponent + { + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index e43a9ecc2d..151392fa30 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -124,7 +124,6 @@ "RCD", "RCDDeconstructWhitelist", "RCDAmmo", - "Pullable", "CursedEntityStorage", "Listening", "Radio", diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 3c7269287c..f105d0278a 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -5,10 +5,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; -using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.Interfaces.GameObjects.Components.Items; -using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Mobs; @@ -25,10 +23,11 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; using Robust.Shared.ViewVariables; +using Content.Server.GameObjects.Components.Pulling; +using Robust.Shared.Map; namespace Content.Server.GameObjects.Components.GUI { @@ -516,77 +515,6 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - public void StartPull(PullableComponent pullable) - { - if (Owner == pullable.Owner) - { - return; - } - - if (!Owner.IsInSameOrNoContainer(pullable.Owner)) - { - return; - } - - if (IsPulling) - { - StopPull(); - } - - PulledObject = pullable.Owner.GetComponent(); - var controller = PulledObject.EnsureController(); - controller.StartPull(Owner.GetComponent()); - } - - public void MovePulledObject(EntityCoordinates puller, EntityCoordinates to) - { - if (PulledObject != null && - PulledObject.TryGetController(out PullController controller)) - { - controller.TryMoveTo(puller, to); - } - } - - private void MoveEvent(MoveEvent moveEvent) - { - if (moveEvent.Sender != Owner) - { - return; - } - - if (!IsPulling) - { - return; - } - - PulledObject!.WakeBody(); - } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - if (!(message is PullMessage pullMessage) || - pullMessage.Puller.Owner != Owner) - { - return; - } - - switch (message) - { - case PullStartedMessage msg: - Owner.EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, MoveEvent); - - AddPullingStatuses(msg.Pulled.Owner); - break; - case PullStoppedMessage msg: - Owner.EntityManager.EventBus.UnsubscribeEvent(EventSource.Local, this); - - RemovePullingStatuses(msg.Pulled.Owner); - break; - } - } - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) { base.HandleNetworkMessage(message, channel, session); diff --git a/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs b/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs index 35bfe18dc8..324b326ac3 100644 --- a/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs @@ -1,38 +1,15 @@ #nullable enable -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Content.Server.GameObjects.Components.Body; -using Content.Server.GameObjects.Components.Chemistry; -using Content.Server.GameObjects.Components.GUI; -using Content.Server.GameObjects.Components.Items.Storage; -using Content.Server.GameObjects.Components.Power.ApcNetComponents; -using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; using Content.Server.Utility; -using Content.Shared.Chemistry; -using Content.Shared.GameObjects.Components.Body; -using Content.Shared.GameObjects.Components.Power; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; -using Content.Shared.Kitchen; -using Content.Shared.Prototypes.Kitchen; using Robust.Server.GameObjects; -using Robust.Server.GameObjects.Components.Container; -using Robust.Server.GameObjects.Components.UserInterface; -using Robust.Server.GameObjects.EntitySystems; -using Robust.Server.Interfaces.GameObjects; -using Robust.Shared.Audio; 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.Serialization; -using Robust.Shared.Timers; -using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Kitchen { @@ -49,11 +26,9 @@ namespace Content.Server.GameObjects.Components.Kitchen void IActivate.Activate(ActivateEventArgs eventArgs) { - var victim = eventArgs.User.GetComponent().PulledObject?.Owner; + SpriteComponent? sprite; - var sprite = Owner.GetComponent(); - - if (victim == null) + if (!EntitySystem.Get().TryGetPulled(eventArgs.User, out var victim)) { if (_meatParts == 0) { @@ -72,12 +47,18 @@ namespace Content.Server.GameObjects.Components.Kitchen } else { - sprite.LayerSetState(0, "spike"); + if (Owner.TryGetComponent(out sprite)) + { + sprite.LayerSetState(0, "spike"); + } + eventArgs.User.PopupMessage(_meatSource0); } + return; } - else if (_meatParts > 0) + + if (_meatParts > 0) { Owner.PopupMessage(eventArgs.User, Loc.GetString("The spike already has something on it, finish collecting its meat first!")); return; @@ -94,7 +75,10 @@ namespace Content.Server.GameObjects.Components.Kitchen _meatSource1p = Loc.GetString("You remove some meat from {0:theName}.", victim); _meatSource0 = Loc.GetString("You remove the last piece of meat from {0:theName}!", victim); - sprite.LayerSetState(0, "spikebloody"); + if (Owner.TryGetComponent(out sprite)) + { + sprite.LayerSetState(0, "spikebloody"); + } Owner.PopupMessageEveryone(Loc.GetString("{0:theName} has forced {1:theName} onto the spike, killing them instantly!", eventArgs.User, victim)); victim.Delete(); diff --git a/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs index 70cb1a384a..8ae619e9f7 100644 --- a/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs @@ -5,8 +5,11 @@ using Content.Server.GameObjects.Components.Buckle; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Pulling; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Network; using Robust.Shared.Players; using Robust.Shared.ViewVariables; @@ -25,7 +28,7 @@ namespace Content.Server.GameObjects.Components.Mobs return new StatusEffectComponentState(_statusEffects); } - public void ChangeStatusEffectIcon(StatusEffect effect, string icon) + public override void ChangeStatusEffectIcon(StatusEffect effect, string icon) { if (_statusEffects.TryGetValue(effect, out var value) && value.Icon == icon) { @@ -60,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Mobs Dirty(); } - public void RemoveStatusEffect(StatusEffect effect) + public override void RemoveStatusEffect(StatusEffect effect) { if (!_statusEffects.Remove(effect)) { @@ -106,19 +109,19 @@ namespace Content.Server.GameObjects.Components.Mobs controller.RemoveController(); break; case StatusEffect.Pulling: - if (!player.TryGetComponent(out HandsComponent hands)) - break; + EntitySystem + .Get() + .GetPulled(player)? + .GetComponentOrNull()? + .TryStopPull(); - hands.StopPull(); break; - case StatusEffect.Fire: if (!player.TryGetComponent(out FlammableComponent flammable)) break; flammable.Resist(); break; - default: player.PopupMessage(msg.Effect.ToString()); break; diff --git a/Content.Server/GameObjects/Components/Movement/PullableComponent.cs b/Content.Server/GameObjects/Components/Movement/PullableComponent.cs deleted file mode 100644 index 4875dfdd2d..0000000000 --- a/Content.Server/GameObjects/Components/Movement/PullableComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.GameObjects; - -namespace Content.Server.GameObjects.Components.Movement -{ - [RegisterComponent] - public class PullableComponent: Component - { - public override string Name => "Pullable"; - } -} diff --git a/Content.Server/GameObjects/Components/Pulling/PullableComponent.cs b/Content.Server/GameObjects/Components/Pulling/PullableComponent.cs new file mode 100644 index 0000000000..9aca41096a --- /dev/null +++ b/Content.Server/GameObjects/Components/Pulling/PullableComponent.cs @@ -0,0 +1,74 @@ +#nullable enable +using Content.Shared.GameObjects.Components.Items; +using Content.Shared.GameObjects.Components.Pulling; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Verbs; +using Content.Shared.Physics.Pull; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.GameObjects.Components.Pulling +{ + [RegisterComponent] + [ComponentReference(typeof(SharedPullableComponent))] + public class PullableComponent : SharedPullableComponent + { + [Verb] + public class PullingVerb : Verb + { + protected override void GetData(IEntity user, PullableComponent component, VerbData data) + { + data.Visibility = VerbVisibility.Invisible; + + if (user == component.Owner) + { + return; + } + + if (!user.Transform.Coordinates.TryDistance(user.EntityManager, component.Owner.Transform.Coordinates, out var distance) || + distance > SharedInteractionSystem.InteractionRange) + { + return; + } + + if (!user.HasComponent() || + !user.TryGetComponent(out IPhysicsComponent? userPhysics) || + !component.Owner.TryGetComponent(out IPhysicsComponent? targetPhysics) || + targetPhysics.Anchored) + { + return; + } + + var controller = targetPhysics.EnsureController(); + + data.Visibility = VerbVisibility.Visible; + data.Text = controller.Puller == userPhysics + ? Loc.GetString("Stop pulling") + : Loc.GetString("Pull"); + } + + protected override void Activate(IEntity user, PullableComponent component) + { + if (!user.TryGetComponent(out IPhysicsComponent? userCollidable) || + !component.Owner.TryGetComponent(out IPhysicsComponent? targetCollidable) || + targetCollidable.Anchored) + { + return; + } + + var controller = targetCollidable.EnsureController(); + + if (controller.Puller == userCollidable) + { + component.TryStopPull(); + } + else + { + component.TryStartPull(user); + } + } + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 2f3f71efec..b0e611e96d 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; +using Content.Server.GameObjects.Components.Pulling; using Content.Server.GameObjects.Components.Timing; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Inventory; @@ -273,12 +274,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click return false; } - if (!pulledObject.TryGetComponent(out var pull)) - { - return false; - } - - if (!player.TryGetComponent(out var hands)) + if (!pulledObject.TryGetComponent(out PullableComponent pull)) { return false; } @@ -289,24 +285,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click return false; } - if (!pull.Owner.TryGetComponent(out IPhysicsComponent physics) || - physics.Anchored) - { - return false; - } - - var controller = physics.EnsureController(); - - if (controller.GettingPulled) - { - hands.StopPull(); - } - else - { - hands.StartPull(pull); - } - - return false; + return pull.TogglePull(player); } private void UserInteraction(IEntity player, EntityCoordinates coordinates, EntityUid clickedUid) diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index d78b98ed1e..409ea2f988 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -16,7 +16,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Input.Binding; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; @@ -47,8 +46,6 @@ namespace Content.Server.GameObjects.EntitySystems .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) - .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) - .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) .Register(); } @@ -229,29 +226,5 @@ namespace Content.Server.GameObjects.EntitySystems } } } - - private bool HandleMovePulledObject(ICommonSession session, EntityCoordinates coords, EntityUid uid) - { - var playerEntity = session.AttachedEntity; - - if (playerEntity == null || - !playerEntity.TryGetComponent(out var hands)) - { - return false; - } - - hands.MovePulledObject(playerEntity.Transform.Coordinates, coords); - - return false; - } - - private static void HandleReleasePulledObject(ICommonSession session) - { - if (!TryGetAttachedComponent(session as IPlayerSession, out HandsComponent handsComp)) - return; - - handsComp.StopPull(); - } - } } diff --git a/Content.Server/GlobalVerbs/PullingVerb.cs b/Content.Server/GlobalVerbs/PullingVerb.cs deleted file mode 100644 index 63b5526d37..0000000000 --- a/Content.Server/GlobalVerbs/PullingVerb.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Content.Server.GameObjects.Components.GUI; -using Content.Server.GameObjects.Components.Movement; -using Content.Shared.GameObjects.Components.Items; -using Content.Shared.GameObjects.EntitySystems; -using Content.Shared.GameObjects.Verbs; -using Content.Shared.Physics.Pull; -using Robust.Server.Interfaces.GameObjects; -using Robust.Shared.GameObjects.Components; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Server.GlobalVerbs -{ - /// - /// Global verb that pulls an entity. - /// - [GlobalVerb] - public class PullingVerb : GlobalVerb - { - public override bool RequireInteractionRange => false; - - public override void GetData(IEntity user, IEntity target, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (user == target || - !user.HasComponent() || - !target.HasComponent()) - { - return; - } - - var dist = user.Transform.Coordinates.Position - target.Transform.Coordinates.Position; - if (dist.LengthSquared > SharedInteractionSystem.InteractionRangeSquared) - { - return; - } - - if (!user.HasComponent() || - !user.TryGetComponent(out IPhysicsComponent userPhysics) || - !target.TryGetComponent(out IPhysicsComponent targetPhysics) || - targetPhysics.Anchored) - { - return; - } - - var controller = targetPhysics.EnsureController(); - - data.Visibility = VerbVisibility.Visible; - data.Text = controller.Puller == userPhysics - ? Loc.GetString("Stop pulling") - : Loc.GetString("Pull"); - } - - public override void Activate(IEntity user, IEntity target) - { - if (!user.TryGetComponent(out IPhysicsComponent userPhysics) || - !target.TryGetComponent(out IPhysicsComponent targetPhysics) || - targetPhysics.Anchored || - !target.TryGetComponent(out PullableComponent pullable) || - !user.TryGetComponent(out HandsComponent hands)) - { - return; - } - - var controller = targetPhysics.EnsureController(); - - if (controller.Puller == userPhysics) - { - hands.StopPull(); - } - else - { - hands.StartPull(pullable); - } - } - } -} diff --git a/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs index a6f2188389..d13c57f7ca 100644 --- a/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs @@ -4,6 +4,5 @@ namespace Content.Shared.GameObjects.Components.Items { public interface ISharedHandsComponent : IComponent { - void StopPull(); } } diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs index 4e2eb5f5bb..dbb31f2883 100644 --- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -1,6 +1,8 @@ #nullable enable using System; +using Content.Shared.GameObjects.Components.Pulling; using Content.Shared.Physics.Pull; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; @@ -13,42 +15,6 @@ namespace Content.Shared.GameObjects.Components.Items { public sealed override string Name => "Hands"; public sealed override uint? NetID => ContentNetIDs.HANDS; - - [ViewVariables] - public IPhysicsComponent? PulledObject { get; protected set; } - - [ViewVariables] - protected bool IsPulling => PulledObject != null; - - public virtual void StopPull() - { - if (PulledObject != null && - PulledObject.TryGetController(out PullController controller)) - { - controller.StopPull(); - } - } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - if (!(message is PullMessage pullMessage) || - pullMessage.Puller.Owner != Owner) - { - return; - } - - switch (message) - { - case PullStartedMessage msg: - PulledObject = msg.Pulled; - break; - case PullStoppedMessage _: - PulledObject = null; - break; - } - } } [Serializable, NetSerializable] diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs index d6d094ed76..8ffb4da6ed 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs @@ -14,7 +14,11 @@ namespace Content.Shared.GameObjects.Components.Mobs public override string Name => "StatusEffectsUI"; public override uint? NetID => ContentNetIDs.STATUSEFFECTS; + public abstract void ChangeStatusEffectIcon(StatusEffect effect, string icon); + public abstract void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple? cooldown); + + public abstract void RemoveStatusEffect(StatusEffect effect); } [Serializable, NetSerializable] diff --git a/Content.Shared/GameObjects/Components/Pulling/SharedPullableComponent.cs b/Content.Shared/GameObjects/Components/Pulling/SharedPullableComponent.cs new file mode 100644 index 0000000000..4ca9bd022a --- /dev/null +++ b/Content.Shared/GameObjects/Components/Pulling/SharedPullableComponent.cs @@ -0,0 +1,263 @@ +#nullable enable +using System; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.Physics.Pull; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Components.Transform; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Pulling +{ + public abstract class SharedPullableComponent : Component + { + public override string Name => "Pullable"; + public override uint? NetID => ContentNetIDs.PULLABLE; + + private IEntity? _puller; + + public virtual IEntity? Puller + { + get => _puller; + private set + { + if (_puller == value) + { + return; + } + + _puller = value; + Dirty(); + + if (!Owner.TryGetComponent(out IPhysicsComponent? physics)) + { + return; + } + + PullController controller; + + if (value == null) + { + if (physics.TryGetController(out controller)) + { + controller.StopPull(); + } + + return; + } + + controller = physics.EnsureController(); + controller.StartPull(value); + } + } + + public bool BeingPulled => Puller != null; + + public bool CanStartPull(IEntity puller) + { + if (!puller.HasComponent()) + { + return false; + } + + if (!puller.TryGetComponent(out IPhysicsComponent? physics)) + { + return false; + } + + if (physics.Anchored) + { + return false; + } + + if (puller == Owner) + { + return false; + } + + if (!puller.IsInSameOrNoContainer(Owner)) + { + return false; + } + + return true; + } + + public bool TryStartPull(IEntity puller) + { + if (!CanStartPull(puller)) + { + return false; + } + + TryStopPull(); + + Puller = puller; + + if (Puller != puller) + { + return false; + } + + return true; + } + + public bool TryStopPull() + { + if (!BeingPulled) + { + return false; + } + + Puller = null; + return true; + } + + public bool TogglePull(IEntity puller) + { + if (Puller == null) + { + return TryStartPull(puller); + } + + return TryStopPull(); + } + + public bool TryMoveTo(EntityCoordinates to) + { + if (Puller == null) + { + return false; + } + + if (!Owner.TryGetComponent(out IPhysicsComponent? physics)) + { + return false; + } + + if (!physics.TryGetController(out PullController controller)) + { + return false; + } + + return controller.TryMoveTo(Puller.Transform.Coordinates, to); + } + + private void PullerMoved(MoveEvent moveEvent) + { + if (Puller == null) + { + return; + } + + if (moveEvent.Sender != Puller) + { + return; + } + + if (!Owner.TryGetComponent(out IPhysicsComponent? physics)) + { + return; + } + + physics.WakeBody(); + } + + public override ComponentState GetComponentState() + { + return new PullableComponentState(Puller?.Uid); + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + base.HandleComponentState(curState, nextState); + + if (!(curState is PullableComponentState state)) + { + return; + } + + if (state.Puller == null) + { + Puller = null; + return; + } + + Puller = Owner.EntityManager.GetEntity(state.Puller.Value); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + if (!(message is PullMessage pullMessage) || + pullMessage.Pulled.Owner != Owner) + { + return; + } + + switch (message) + { + case PullStartedMessage msg: + Owner.EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, PullerMoved); + + AddPullingStatuses(msg.Puller.Owner); + break; + case PullStoppedMessage msg: + Owner.EntityManager.EventBus.UnsubscribeEvent(EventSource.Local, this); + + RemovePullingStatuses(msg.Puller.Owner); + break; + } + } + + private void AddPullingStatuses(IEntity puller) + { + if (Owner.TryGetComponent(out SharedStatusEffectsComponent? pulledStatus)) + { + pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled, + "/Textures/Interface/StatusEffects/Pull/pulled.png"); + } + + if (puller.TryGetComponent(out SharedStatusEffectsComponent? ownerStatus)) + { + ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling, + "/Textures/Interface/StatusEffects/Pull/pulling.png"); + } + } + + private void RemovePullingStatuses(IEntity puller) + { + if (Owner.TryGetComponent(out SharedStatusEffectsComponent? pulledStatus)) + { + pulledStatus.RemoveStatusEffect(StatusEffect.Pulled); + } + + if (puller.TryGetComponent(out SharedStatusEffectsComponent? ownerStatus)) + { + ownerStatus.RemoveStatusEffect(StatusEffect.Pulling); + } + } + + public override void OnRemove() + { + TryStopPull(); + + base.OnRemove(); + } + } + + [Serializable, NetSerializable] + public class PullableComponentState : ComponentState + { + public readonly EntityUid? Puller; + + public PullableComponentState(EntityUid? puller) : base(ContentNetIDs.PULLABLE) + { + Puller = puller; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs b/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs new file mode 100644 index 0000000000..0a87ba22fe --- /dev/null +++ b/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs @@ -0,0 +1,72 @@ +#nullable enable +using Content.Shared.GameObjects.Components.Movement; +using Content.Shared.Physics.Pull; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Component = Robust.Shared.GameObjects.Component; + +namespace Content.Shared.GameObjects.Components.Pulling +{ + [RegisterComponent] + public class SharedPullerComponent : Component, IMoveSpeedModifier + { + public override string Name => "Puller"; + + private IEntity? _pulling; + + public float WalkSpeedModifier => Pulling == null ? 1.0f : 0.75f; + + public float SprintSpeedModifier => Pulling == null ? 1.0f : 0.75f; + + public IEntity? Pulling + { + get => _pulling; + private set + { + if (_pulling == value) + { + return; + } + + _pulling = value; + + if (Owner.TryGetComponent(out MovementSpeedModifierComponent? speed)) + { + speed.RefreshMovementSpeedModifiers(); + } + } + } + + public override void OnRemove() + { + if (Pulling != null && + Pulling.TryGetComponent(out SharedPullableComponent? pullable)) + { + pullable.TryStopPull(); + } + + base.OnRemove(); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + if (!(message is PullMessage pullMessage) || + pullMessage.Puller.Owner != Owner) + { + return; + } + + switch (message) + { + case PullStartedMessage msg: + Pulling = msg.Pulled.Owner; + break; + case PullStoppedMessage _: + Pulling = null; + break; + } + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 32a5222915..d6dc3fcafc 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -81,6 +81,7 @@ public const uint CRAYONS = 1075; public const uint PLACEABLE_SURFACE = 1076; public const uint STORABLE = 1077; + public const uint PULLABLE = 1078; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs new file mode 100644 index 0000000000..72b4bb3d3e --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs @@ -0,0 +1,117 @@ +#nullable enable +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.GameObjects.Components.Pulling; +using Content.Shared.Input; +using Content.Shared.Physics.Pull; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Input.Binding; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Players; + +namespace Content.Shared.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class SharedPullingSystem : EntitySystem + { + private readonly Dictionary _pullers = + new Dictionary(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPullStarted); + SubscribeLocalEvent(OnPullStopped); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) + .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) + .Register(); + } + + private void OnPullStarted(PullStartedMessage message) + { + SetPuller(message.Puller.Owner, message.Pulled.Owner); + } + + private void OnPullStopped(PullStoppedMessage message) + { + RemovePuller(message.Puller.Owner); + } + + private bool HandleMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + var player = session?.AttachedEntity; + + if (player == null) + { + return false; + } + + if (!TryGetPulled(player, out var pulled)) + { + return false; + } + + if (!pulled.TryGetComponent(out SharedPullableComponent? pullable)) + { + return false; + } + + pullable.TryMoveTo(coords); + + return false; + } + + private void HandleReleasePulledObject(ICommonSession? session) + { + var player = session?.AttachedEntity; + + if (player == null) + { + return; + } + + if (!TryGetPulled(player, out var pulled)) + { + return; + } + + if (!pulled.TryGetComponent(out SharedPullableComponent? pullable)) + { + return; + } + + pullable.TryStopPull(); + } + + private void SetPuller(IEntity puller, IEntity pulled) + { + _pullers[puller] = pulled; + } + + private bool RemovePuller(IEntity puller) + { + return _pullers.Remove(puller); + } + + public IEntity? GetPulled(IEntity by) + { + return _pullers.GetValueOrDefault(by); + } + + public bool TryGetPulled(IEntity by, [NotNullWhen(true)] out IEntity? pulled) + { + return (pulled = GetPulled(by)) != null; + } + + public bool IsPulling(IEntity puller) + { + return _pullers.ContainsKey(puller); + } + } +} diff --git a/Content.Shared/Physics/Pull/PullController.cs b/Content.Shared/Physics/Pull/PullController.cs index a703cfa059..cadb0db3ce 100644 --- a/Content.Shared/Physics/Pull/PullController.cs +++ b/Content.Shared/Physics/Pull/PullController.cs @@ -1,22 +1,28 @@ #nullable enable using System; -using Content.Shared.GameObjects.EntitySystems; +using System.Linq; using Robust.Shared.Containers; +using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Physics; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Utility; +using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem; namespace Content.Shared.Physics.Pull { public class PullController : VirtualController { - private const float DistBeforePull = 1.0f; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPhysicsManager _physicsManager = default!; - private const float DistBeforeStopPull = SharedInteractionSystem.InteractionRange; + private const float DistBeforeStopPull = InteractionRange; + + private const float StopMoveThreshold = 0.25f; private IPhysicsComponent? _puller; @@ -26,81 +32,167 @@ namespace Content.Shared.Physics.Pull public IPhysicsComponent? Puller => _puller; - public void StartPull(IPhysicsComponent puller) + public EntityCoordinates? MovingTo + { + get => _movingTo; + set + { + if (_movingTo == value || ControlledComponent == null) + { + return; + } + + _movingTo = value; + ControlledComponent.WakeBody(); + } + } + + private float DistanceBeforeStopPull() + { + if (_puller == null) + { + return 0; + } + + var aabbSize = _puller.AABB.Size; + + return (aabbSize.X > aabbSize.Y ? aabbSize.X : aabbSize.Y) + 0.15f; + } + + private bool PullerMovingTowardsPulled() + { + if (_puller == null) + { + return false; + } + + if (ControlledComponent == null) + { + return false; + } + + if (_puller.LinearVelocity.EqualsApprox(Vector2.Zero)) + { + return false; + } + + var pullerTransform = _puller.Owner.Transform; + var origin = pullerTransform.Coordinates.Position; + var velocity = _puller.LinearVelocity.Normalized; + var mapId = pullerTransform.MapPosition.MapId; + var ray = new CollisionRay(origin, velocity, (int) CollisionGroup.AllMask); + bool Predicate(IEntity e) => e != ControlledComponent.Owner; + var rayResults = + _physicsManager.IntersectRayWithPredicate(mapId, ray, DistanceBeforeStopPull() * 2, Predicate); + + return rayResults.Any(); + } + + public bool StartPull(IEntity entity) + { + DebugTools.AssertNotNull(entity); + + if (!entity.TryGetComponent(out IPhysicsComponent? physics)) + { + return false; + } + + return StartPull(physics); + } + + public bool StartPull(IPhysicsComponent puller) { DebugTools.AssertNotNull(puller); if (_puller == puller) { - return; + return false; } - _puller = puller; - if (ControlledComponent == null) { - return; + return false; } - ControlledComponent.WakeBody(); + _puller = puller; var message = new PullStartedMessage(this, _puller, ControlledComponent); _puller.Owner.SendMessage(null, message); ControlledComponent.Owner.SendMessage(null, message); + + _puller.Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, message); + + ControlledComponent.WakeBody(); + + return true; } - public void StopPull() + public bool StopPull() { var oldPuller = _puller; if (oldPuller == null) { - return; + return false; } _puller = null; if (ControlledComponent == null) { - return; + return false; } - ControlledComponent.WakeBody(); - - var message = new PullStoppedMessage(this, oldPuller, ControlledComponent); + var message = new PullStoppedMessage(oldPuller, ControlledComponent); oldPuller.Owner.SendMessage(null, message); ControlledComponent.Owner.SendMessage(null, message); + oldPuller.Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, message); + + ControlledComponent.WakeBody(); ControlledComponent.TryRemoveController(); + + return true; } - public void TryMoveTo(EntityCoordinates from, EntityCoordinates to) + public bool TryMoveTo(EntityCoordinates from, EntityCoordinates to) { if (_puller == null || ControlledComponent == null) { - return; + return false; } - var entityManager = IoCManager.Resolve(); - - if (!from.InRange(entityManager, to, SharedInteractionSystem.InteractionRange)) + if (!_puller.Owner.Transform.Coordinates.InRange(_entityManager, from, InteractionRange)) { - return; + return false; } - ControlledComponent.WakeBody(); - - var dist = _puller.Owner.Transform.Coordinates.Position - to.Position; - - if (Math.Sqrt(dist.LengthSquared) > DistBeforeStopPull || - Math.Sqrt(dist.LengthSquared) < 0.25f) + if (!_puller.Owner.Transform.Coordinates.InRange(_entityManager, to, InteractionRange)) { - return; + return false; } - _movingTo = to; + if (!from.InRange(_entityManager, to, InteractionRange)) + { + return false; + } + + if (from.Position.EqualsApprox(to.Position)) + { + return false; + } + + if (!_puller.Owner.Transform.Coordinates.TryDistance(_entityManager, to, out var distance) || + Math.Sqrt(distance) > DistBeforeStopPull || + Math.Sqrt(distance) < StopMoveThreshold) + { + return false; + } + + MovingTo = to; + return true; } public override void UpdateBeforeProcessing() @@ -116,21 +208,20 @@ namespace Content.Shared.Physics.Pull return; } - // Are we outside of pulling range? - var dist = _puller.Owner.Transform.WorldPosition - ControlledComponent.Owner.Transform.WorldPosition; + var distance = _puller.Owner.Transform.WorldPosition - ControlledComponent.Owner.Transform.WorldPosition; - if (dist.Length > DistBeforeStopPull) + if (distance.Length > DistBeforeStopPull) { StopPull(); } - else if (_movingTo.HasValue) + else if (MovingTo.HasValue) { - var diff = _movingTo.Value.Position - ControlledComponent.Owner.Transform.Coordinates.Position; + var diff = MovingTo.Value.Position - ControlledComponent.Owner.Transform.Coordinates.Position; LinearVelocity = diff.Normalized * 5; } - else if (dist.Length > DistBeforePull) + else if (distance.Length > DistanceBeforeStopPull() && !PullerMovingTowardsPulled()) { - LinearVelocity = dist.Normalized * _puller.LinearVelocity.Length * 1.1f; + LinearVelocity = distance.Normalized * _puller.LinearVelocity.Length * 1.5f; } else { @@ -144,18 +235,14 @@ namespace Content.Shared.Physics.Pull if (ControlledComponent == null) { - _movingTo = null; + MovingTo = null; return; } - if (_movingTo == null) + if (MovingTo != null && + ControlledComponent.Owner.Transform.Coordinates.Position.EqualsApprox(MovingTo.Value.Position, 0.01)) { - return; - } - - if (ControlledComponent.Owner.Transform.Coordinates.Position.EqualsApprox(_movingTo.Value.Position, 0.01)) - { - _movingTo = null; + MovingTo = null; } } } diff --git a/Content.Shared/Physics/Pull/PullMessage.cs b/Content.Shared/Physics/Pull/PullMessage.cs index 8a4ae5076e..92c811ddd4 100644 --- a/Content.Shared/Physics/Pull/PullMessage.cs +++ b/Content.Shared/Physics/Pull/PullMessage.cs @@ -5,13 +5,11 @@ namespace Content.Shared.Physics.Pull { public class PullMessage : ComponentMessage { - public readonly PullController Controller; public readonly IPhysicsComponent Puller; public readonly IPhysicsComponent Pulled; - protected PullMessage(PullController controller, IPhysicsComponent puller, IPhysicsComponent pulled) + protected PullMessage(IPhysicsComponent puller, IPhysicsComponent pulled) { - Controller = controller; Puller = puller; Pulled = pulled; } diff --git a/Content.Shared/Physics/Pull/PullStartedMessage.cs b/Content.Shared/Physics/Pull/PullStartedMessage.cs index 02be743c30..00d51bdbd3 100644 --- a/Content.Shared/Physics/Pull/PullStartedMessage.cs +++ b/Content.Shared/Physics/Pull/PullStartedMessage.cs @@ -5,7 +5,7 @@ namespace Content.Shared.Physics.Pull public class PullStartedMessage : PullMessage { public PullStartedMessage(PullController controller, IPhysicsComponent puller, IPhysicsComponent pulled) : - base(controller, puller, pulled) + base(puller, pulled) { } } diff --git a/Content.Shared/Physics/Pull/PullStoppedMessage.cs b/Content.Shared/Physics/Pull/PullStoppedMessage.cs index aff10c5514..dd43653b6b 100644 --- a/Content.Shared/Physics/Pull/PullStoppedMessage.cs +++ b/Content.Shared/Physics/Pull/PullStoppedMessage.cs @@ -4,8 +4,7 @@ namespace Content.Shared.Physics.Pull { public class PullStoppedMessage : PullMessage { - public PullStoppedMessage(PullController controller, IPhysicsComponent puller, IPhysicsComponent pulled) : - base(controller, puller, pulled) + public PullStoppedMessage(IPhysicsComponent puller, IPhysicsComponent pulled) : base(puller, pulled) { } } diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 7000e0c1fa..ccd3a203e6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -57,3 +57,4 @@ normal: running crit: crit dead: dead + - type: Puller diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 01e12c419d..b907ad4022 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -195,6 +195,7 @@ type: StrippableBoundUserInterface - key: enum.AcceptCloningUiKey.Key type: AcceptCloningBoundUserInterface + - type: Puller - type: entity save: false