| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- using Content.Shared.ActionBlocker;
- using Content.Shared.Administration.Logs;
- using Content.Shared.Alert;
- using Content.Shared.Buckle.Components;
- using Content.Shared.Cuffs.Components;
- using Content.Shared.Database;
- using Content.Shared.Hands;
- using Content.Shared.Hands.EntitySystems;
- using Content.Shared.IdentityManagement;
- using Content.Shared.Input;
- using Content.Shared.Interaction;
- using Content.Shared.Item;
- using Content.Shared.Mobs;
- using Content.Shared.Mobs.Systems;
- using Content.Shared.Movement.Events;
- using Content.Shared.Movement.Pulling.Components;
- using Content.Shared.Movement.Pulling.Events;
- using Content.Shared.Movement.Systems;
- using Content.Shared.Popups;
- using Content.Shared.Pulling.Events;
- using Content.Shared.Standing;
- using Content.Shared.Verbs;
- using Robust.Shared.Containers;
- using Robust.Shared.Input.Binding;
- using Robust.Shared.Physics;
- using Robust.Shared.Physics.Components;
- using Robust.Shared.Physics.Events;
- using Robust.Shared.Physics.Systems;
- using Robust.Shared.Player;
- using Robust.Shared.Timing;
- namespace Content.Shared.Movement.Pulling.Systems;
- /// <summary>
- /// Allows one entity to pull another behind them via a physics distance joint.
- /// </summary>
- public sealed class PullingSystem : EntitySystem
- {
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly ActionBlockerSystem _blocker = default!;
- [Dependency] private readonly AlertsSystem _alertsSystem = default!;
- [Dependency] private readonly MovementSpeedModifierSystem _modifierSystem = default!;
- [Dependency] private readonly SharedJointSystem _joints = default!;
- [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
- [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
- [Dependency] private readonly SharedInteractionSystem _interaction = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
- [Dependency] private readonly HeldSpeedModifierSystem _clothingMoveSpeed = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- public override void Initialize()
- {
- base.Initialize();
- UpdatesAfter.Add(typeof(SharedPhysicsSystem));
- UpdatesOutsidePrediction = true;
- SubscribeLocalEvent<PullableComponent, MoveInputEvent>(OnPullableMoveInput);
- SubscribeLocalEvent<PullableComponent, CollisionChangeEvent>(OnPullableCollisionChange);
- SubscribeLocalEvent<PullableComponent, JointRemovedEvent>(OnJointRemoved);
- SubscribeLocalEvent<PullableComponent, GetVerbsEvent<Verb>>(AddPullVerbs);
- SubscribeLocalEvent<PullableComponent, EntGotInsertedIntoContainerMessage>(OnPullableContainerInsert);
- SubscribeLocalEvent<PullableComponent, ModifyUncuffDurationEvent>(OnModifyUncuffDuration);
- SubscribeLocalEvent<PullableComponent, StopBeingPulledAlertEvent>(OnStopBeingPulledAlert);
- SubscribeLocalEvent<PullerComponent, UpdateMobStateEvent>(OnStateChanged);
- SubscribeLocalEvent<PullerComponent, AfterAutoHandleStateEvent>(OnAfterState);
- SubscribeLocalEvent<PullerComponent, EntGotInsertedIntoContainerMessage>(OnPullerContainerInsert);
- SubscribeLocalEvent<PullerComponent, EntityUnpausedEvent>(OnPullerUnpaused);
- SubscribeLocalEvent<PullerComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
- SubscribeLocalEvent<PullerComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
- SubscribeLocalEvent<PullerComponent, DropHandItemsEvent>(OnDropHandItems);
- SubscribeLocalEvent<PullerComponent, StopPullingAlertEvent>(OnStopPullingAlert);
- SubscribeLocalEvent<PullableComponent, StrappedEvent>(OnBuckled);
- SubscribeLocalEvent<PullableComponent, BuckledEvent>(OnGotBuckled);
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(OnReleasePulledObject, handle: false))
- .Register<PullingSystem>();
- }
- private void OnStateChanged(EntityUid uid, PullerComponent component, ref UpdateMobStateEvent args)
- {
- if (component.Pulling == null)
- return;
- if (TryComp<PullableComponent>(component.Pulling, out var comp) && (args.State == MobState.Critical || args.State == MobState.Dead))
- {
- TryStopPull(component.Pulling.Value, comp);
- }
- }
- private void OnBuckled(Entity<PullableComponent> ent, ref StrappedEvent args)
- {
- // Prevent people from pulling the entity they are buckled to
- if (ent.Comp.Puller == args.Buckle.Owner && !args.Buckle.Comp.PullStrap)
- StopPulling(ent, ent);
- }
- private void OnGotBuckled(Entity<PullableComponent> ent, ref BuckledEvent args)
- {
- StopPulling(ent, ent);
- }
- private void OnAfterState(Entity<PullerComponent> ent, ref AfterAutoHandleStateEvent args)
- {
- if (ent.Comp.Pulling == null)
- RemComp<ActivePullerComponent>(ent.Owner);
- else
- EnsureComp<ActivePullerComponent>(ent.Owner);
- }
- private void OnDropHandItems(EntityUid uid, PullerComponent pullerComp, DropHandItemsEvent args)
- {
- if (pullerComp.Pulling == null || pullerComp.NeedsHands)
- return;
- if (!TryComp(pullerComp.Pulling, out PullableComponent? pullableComp))
- return;
- TryStopPull(pullerComp.Pulling.Value, pullableComp, uid);
- }
- private void OnStopPullingAlert(Entity<PullerComponent> ent, ref StopPullingAlertEvent args)
- {
- if (args.Handled)
- return;
- if (!TryComp<PullableComponent>(ent.Comp.Pulling, out var pullable))
- return;
- args.Handled = TryStopPull(ent.Comp.Pulling.Value, pullable, ent);
- }
- private void OnPullerContainerInsert(Entity<PullerComponent> ent, ref EntGotInsertedIntoContainerMessage args)
- {
- if (ent.Comp.Pulling == null)
- return;
- if (!TryComp(ent.Comp.Pulling.Value, out PullableComponent? pulling))
- return;
- TryStopPull(ent.Comp.Pulling.Value, pulling, ent.Owner);
- }
- private void OnPullableContainerInsert(Entity<PullableComponent> ent, ref EntGotInsertedIntoContainerMessage args)
- {
- TryStopPull(ent.Owner, ent.Comp);
- }
- private void OnModifyUncuffDuration(Entity<PullableComponent> ent, ref ModifyUncuffDurationEvent args)
- {
- if (!ent.Comp.BeingPulled)
- return;
- // We don't care if the person is being uncuffed by someone else
- if (args.User != args.Target)
- return;
- args.Duration *= 2;
- }
- private void OnStopBeingPulledAlert(Entity<PullableComponent> ent, ref StopBeingPulledAlertEvent args)
- {
- if (args.Handled)
- return;
- args.Handled = TryStopPull(ent, ent, ent);
- }
- public override void Shutdown()
- {
- base.Shutdown();
- CommandBinds.Unregister<PullingSystem>();
- }
- private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref EntityUnpausedEvent args)
- {
- component.NextThrow += args.PausedTime;
- }
- private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args)
- {
- // If client deletes the virtual hand then stop the pull.
- if (component.Pulling == null)
- return;
- if (component.Pulling != args.BlockingEntity)
- return;
- if (EntityManager.TryGetComponent(args.BlockingEntity, out PullableComponent? comp))
- {
- TryStopPull(args.BlockingEntity, comp);
- }
- }
- private void AddPullVerbs(EntityUid uid, PullableComponent component, GetVerbsEvent<Verb> args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
- // Are they trying to pull themselves up by their bootstraps?
- if (args.User == args.Target)
- return;
- //TODO VERB ICONS add pulling icon
- if (component.Puller == args.User)
- {
- Verb verb = new()
- {
- Text = Loc.GetString("pulling-verb-get-data-text-stop-pulling"),
- Act = () => TryStopPull(uid, component, user: args.User),
- DoContactInteraction = false // pulling handle its own contact interaction.
- };
- args.Verbs.Add(verb);
- }
- else if (CanPull(args.User, args.Target))
- {
- Verb verb = new()
- {
- Text = Loc.GetString("pulling-verb-get-data-text"),
- Act = () => TryStartPull(args.User, args.Target),
- DoContactInteraction = false // pulling handle its own contact interaction.
- };
- args.Verbs.Add(verb);
- }
- }
- private void OnRefreshMovespeed(EntityUid uid, PullerComponent component, RefreshMovementSpeedModifiersEvent args)
- {
- if (TryComp<HeldSpeedModifierComponent>(component.Pulling, out var heldMoveSpeed) && component.Pulling.HasValue)
- {
- var (walkMod, sprintMod) =
- _clothingMoveSpeed.GetHeldMovementSpeedModifiers(component.Pulling.Value, heldMoveSpeed);
- args.ModifySpeed(walkMod, sprintMod);
- return;
- }
- args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier);
- }
- private void OnPullableMoveInput(EntityUid uid, PullableComponent component, ref MoveInputEvent args)
- {
- // If someone moves then break their pulling.
- if (!component.BeingPulled)
- return;
- var entity = args.Entity;
- if (!_blocker.CanMove(entity))
- return;
- TryStopPull(uid, component, user: uid);
- }
- private void OnPullableCollisionChange(EntityUid uid, PullableComponent component, ref CollisionChangeEvent args)
- {
- // IDK what this is supposed to be.
- if (!_timing.ApplyingState && component.PullJointId != null && !args.CanCollide)
- {
- _joints.RemoveJoint(uid, component.PullJointId);
- }
- }
- private void OnJointRemoved(EntityUid uid, PullableComponent component, JointRemovedEvent args)
- {
- // Just handles the joint getting nuked without going through pulling system (valid behavior).
- // Not relevant / pullable state handle it.
- if (component.Puller != args.OtherEntity ||
- args.Joint.ID != component.PullJointId ||
- _timing.ApplyingState)
- {
- return;
- }
- if (args.Joint.ID != component.PullJointId || component.Puller == null)
- return;
- StopPulling(uid, component);
- }
- /// <summary>
- /// Forces pulling to stop and handles cleanup.
- /// </summary>
- private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp)
- {
- if (pullableComp.Puller == null)
- return;
- if (!_timing.ApplyingState)
- {
- // Joint shutdown
- if (pullableComp.PullJointId != null)
- {
- _joints.RemoveJoint(pullableUid, pullableComp.PullJointId);
- pullableComp.PullJointId = null;
- }
- if (TryComp<PhysicsComponent>(pullableUid, out var pullablePhysics))
- {
- _physics.SetFixedRotation(pullableUid, pullableComp.PrevFixedRotation, body: pullablePhysics);
- }
- }
- var oldPuller = pullableComp.Puller;
- if (oldPuller != null)
- RemComp<ActivePullerComponent>(oldPuller.Value);
- pullableComp.PullJointId = null;
- pullableComp.Puller = null;
- Dirty(pullableUid, pullableComp);
- // No more joints with puller -> force stop pull.
- if (TryComp<PullerComponent>(oldPuller, out var pullerComp))
- {
- var pullerUid = oldPuller.Value;
- _alertsSystem.ClearAlert(pullerUid, pullerComp.PullingAlert);
- pullerComp.Pulling = null;
- Dirty(oldPuller.Value, pullerComp);
- // Messaging
- var message = new PullStoppedMessage(pullerUid, pullableUid);
- _modifierSystem.RefreshMovementSpeedModifiers(pullerUid);
- _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(pullerUid):user} stopped pulling {ToPrettyString(pullableUid):target}");
- RaiseLocalEvent(pullerUid, message);
- RaiseLocalEvent(pullableUid, message);
- }
- _alertsSystem.ClearAlert(pullableUid, pullableComp.PulledAlert);
- }
- public bool IsPulled(EntityUid uid, PullableComponent? component = null)
- {
- return Resolve(uid, ref component, false) && component.BeingPulled;
- }
- public bool IsPulling(EntityUid puller, PullerComponent? component = null)
- {
- return Resolve(puller, ref component, false) && component.Pulling != null;
- }
- private void OnReleasePulledObject(ICommonSession? session)
- {
- if (session?.AttachedEntity is not { Valid: true } player)
- {
- return;
- }
- if (!TryComp(player, out PullerComponent? pullerComp) ||
- !TryComp(pullerComp.Pulling, out PullableComponent? pullableComp))
- {
- return;
- }
- TryStopPull(pullerComp.Pulling.Value, pullableComp, user: player);
- }
- public bool CanPull(EntityUid puller, EntityUid pullableUid, PullerComponent? pullerComp = null)
- {
- if (!Resolve(puller, ref pullerComp, false))
- {
- return false;
- }
- if (pullerComp.NeedsHands
- && !_handsSystem.TryGetEmptyHand(puller, out _)
- && pullerComp.Pulling == null)
- {
- return false;
- }
- if (!_blocker.CanInteract(puller, pullableUid))
- {
- return false;
- }
- if (!EntityManager.TryGetComponent<PhysicsComponent>(pullableUid, out var physics))
- {
- return false;
- }
- if (physics.BodyType == BodyType.Static)
- {
- return false;
- }
- if (puller == pullableUid)
- {
- return false;
- }
- if (!_containerSystem.IsInSameOrNoContainer(puller, pullableUid))
- {
- return false;
- }
- var getPulled = new BeingPulledAttemptEvent(puller, pullableUid);
- RaiseLocalEvent(pullableUid, getPulled, true);
- var startPull = new StartPullAttemptEvent(puller, pullableUid);
- RaiseLocalEvent(puller, startPull, true);
- return !startPull.Cancelled && !getPulled.Cancelled;
- }
- public bool TogglePull(Entity<PullableComponent?> pullable, EntityUid pullerUid)
- {
- if (!Resolve(pullable, ref pullable.Comp, false))
- return false;
- if (pullable.Comp.Puller == pullerUid)
- {
- return TryStopPull(pullable, pullable.Comp);
- }
- return TryStartPull(pullerUid, pullable, pullableComp: pullable);
- }
- public bool TogglePull(EntityUid pullerUid, PullerComponent puller)
- {
- if (!TryComp<PullableComponent>(puller.Pulling, out var pullable))
- return false;
- return TogglePull((puller.Pulling.Value, pullable), pullerUid);
- }
- public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid,
- PullerComponent? pullerComp = null, PullableComponent? pullableComp = null)
- {
- if (!Resolve(pullerUid, ref pullerComp, false) ||
- !Resolve(pullableUid, ref pullableComp, false))
- {
- return false;
- }
- if (pullerComp.Pulling == pullableUid)
- return true;
- if (!CanPull(pullerUid, pullableUid))
- return false;
- if (!TryComp(pullerUid, out PhysicsComponent? pullerPhysics) || !TryComp(pullableUid, out PhysicsComponent? pullablePhysics))
- return false;
- // Ensure that the puller is not currently pulling anything.
- if (TryComp<PullableComponent>(pullerComp.Pulling, out var oldPullable)
- && !TryStopPull(pullerComp.Pulling.Value, oldPullable, pullerUid))
- return false;
- // Stop anyone else pulling the entity we want to pull
- if (pullableComp.Puller != null)
- {
- // We're already pulling this item
- if (pullableComp.Puller == pullerUid)
- return false;
- if (!TryStopPull(pullableUid, pullableComp, pullableComp.Puller))
- return false;
- }
- var pullAttempt = new PullAttemptEvent(pullerUid, pullableUid);
- RaiseLocalEvent(pullerUid, pullAttempt);
- if (pullAttempt.Cancelled)
- return false;
- RaiseLocalEvent(pullableUid, pullAttempt);
- if (pullAttempt.Cancelled)
- return false;
- // Pulling confirmed
- _interaction.DoContactInteraction(pullableUid, pullerUid);
- // Use net entity so it's consistent across client and server.
- pullableComp.PullJointId = $"pull-joint-{GetNetEntity(pullableUid)}";
- EnsureComp<ActivePullerComponent>(pullerUid);
- pullerComp.Pulling = pullableUid;
- pullableComp.Puller = pullerUid;
- // store the pulled entity's physics FixedRotation setting in case we change it
- pullableComp.PrevFixedRotation = pullablePhysics.FixedRotation;
- // joint state handling will manage its own state
- if (!_timing.ApplyingState)
- {
- var joint = _joints.CreateDistanceJoint(pullableUid, pullerUid,
- pullablePhysics.LocalCenter, pullerPhysics.LocalCenter,
- id: pullableComp.PullJointId);
- joint.CollideConnected = false;
- // This maximum has to be there because if the object is constrained too closely, the clamping goes backwards and asserts.
- // Internally, the joint length has been set to the distance between the pivots.
- // Add an additional 15cm (pretty arbitrary) to the maximum length for the hard limit.
- joint.MaxLength = joint.Length + 0.15f;
- joint.MinLength = 0f;
- // Set the spring stiffness to zero. The joint won't have any effect provided
- // the current length is beteen MinLength and MaxLength. At those limits, the
- // joint will have infinite stiffness.
- joint.Stiffness = 0f;
- _physics.SetFixedRotation(pullableUid, pullableComp.FixedRotationOnPull, body: pullablePhysics);
- }
- // Messaging
- var message = new PullStartedMessage(pullerUid, pullableUid);
- _modifierSystem.RefreshMovementSpeedModifiers(pullerUid);
- _alertsSystem.ShowAlert(pullerUid, pullerComp.PullingAlert);
- _alertsSystem.ShowAlert(pullableUid, pullableComp.PulledAlert);
- RaiseLocalEvent(pullerUid, message);
- RaiseLocalEvent(pullableUid, message);
- Dirty(pullerUid, pullerComp);
- Dirty(pullableUid, pullableComp);
- var pullingMessage =
- Loc.GetString("getting-pulled-popup", ("puller", Identity.Entity(pullerUid, EntityManager)));
- _popup.PopupEntity(pullingMessage, pullableUid, pullableUid);
- _adminLogger.Add(LogType.Action, LogImpact.Low,
- $"{ToPrettyString(pullerUid):user} started pulling {ToPrettyString(pullableUid):target}");
- return true;
- }
- public bool TryStopPull(EntityUid pullableUid, PullableComponent pullable, EntityUid? user = null)
- {
- var pullerUidNull = pullable.Puller;
- if (pullerUidNull == null)
- return true;
- if (user != null && !_blocker.CanInteract(user.Value, pullableUid))
- return false;
- var msg = new AttemptStopPullingEvent(user);
- RaiseLocalEvent(pullableUid, msg, true);
- if (msg.Cancelled)
- return false;
- StopPulling(pullableUid, pullable);
- return true;
- }
- }
|