using System.Numerics; using Content.Server.Inventory; using Content.Server.Stack; using Content.Server.Stunnable; using Content.Shared.ActionBlocker; using Content.Shared.Body.Part; using Content.Shared.CombatMode; using Content.Shared.Damage.Systems; using Content.Shared.Explosion; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Stacks; using Content.Shared.Throwing; using Robust.Shared.GameStates; using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Shared.Body.Systems; // Shitmed Change using Content.Shared._Shitmed.Body.Events; // Shitmed Change namespace Content.Server.Hands.Systems { public sealed class HandsSystem : SharedHandsSystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StackSystem _stackSystem = default!; [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly PullingSystem _pullingSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly SharedBodySystem _bodySystem = default!; // Shitmed Change public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDisarmed, before: new[] { typeof(StunSystem), typeof(StaminaSystem) }); SubscribeLocalEvent(HandlePullStarted); SubscribeLocalEvent(HandlePullStopped); SubscribeLocalEvent(HandleBodyPartAdded); SubscribeLocalEvent(HandleBodyPartRemoved); SubscribeLocalEvent(GetComponentState); SubscribeLocalEvent(OnExploded); SubscribeLocalEvent(HandleBodyPartEnabled); // Shitmed Change SubscribeLocalEvent(HandleBodyPartDisabled); // Shitmed Change CommandBinds.Builder .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Register(); } public override void Shutdown() { base.Shutdown(); CommandBinds.Unregister(); } private void GetComponentState(EntityUid uid, HandsComponent hands, ref ComponentGetState args) { args.State = new HandsComponentState(hands); } private void OnExploded(Entity ent, ref BeforeExplodeEvent args) { if (ent.Comp.DisableExplosionRecursion) return; foreach (var hand in ent.Comp.Hands.Values) { if (hand.HeldEntity is { } uid) args.Contents.Add(uid); } } private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent args) { if (args.Handled) return; // Break any pulls if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable)) _pullingSystem.TryStopPull(puller.Pulling.Value, pullable); var offsetRandomCoordinates = _transformSystem.GetMoverCoordinates(args.Target).Offset(_random.NextVector2(1f, 1.5f)); if (!ThrowHeldItem(args.Target, offsetRandomCoordinates)) return; args.PopupPrefix = "disarm-action-"; args.Handled = true; // no shove/stun. } // Shitmed Change Start private void TryAddHand(EntityUid uid, HandsComponent component, Entity part, string slot) { if (part.Comp is null || part.Comp.PartType != BodyPartType.Hand) return; // If this annoys you, which it should. // Ping Smugleaf. var location = part.Comp.Symmetry switch { BodyPartSymmetry.None => HandLocation.Middle, BodyPartSymmetry.Left => HandLocation.Left, BodyPartSymmetry.Right => HandLocation.Right, _ => throw new ArgumentOutOfRangeException(nameof(part.Comp.Symmetry)) }; if (part.Comp.Enabled && _bodySystem.TryGetParentBodyPart(part, out var _, out var parentPartComp) && parentPartComp.Enabled) AddHand(uid, slot, location); } private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args) { TryAddHand(uid, component, args.Part, args.Slot); } private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args) { if (args.Part.Comp is null || args.Part.Comp.PartType != BodyPartType.Hand) return; RemoveHand(uid, args.Slot); } private void HandleBodyPartEnabled(EntityUid uid, HandsComponent component, ref BodyPartEnabledEvent args) => TryAddHand(uid, component, args.Part, SharedBodySystem.GetPartSlotContainerId(args.Part.Comp.ParentSlot?.Id ?? string.Empty)); private void HandleBodyPartDisabled(EntityUid uid, HandsComponent component, ref BodyPartDisabledEvent args) { if (TerminatingOrDeleted(uid) || args.Part.Comp is null || args.Part.Comp.PartType != BodyPartType.Hand) return; RemoveHand(uid, SharedBodySystem.GetPartSlotContainerId(args.Part.Comp.ParentSlot?.Id ?? string.Empty)); } // Shitmed Change End #region pulling private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) { if (args.PullerUid != uid) return; if (TryComp(args.PullerUid, out var pullerComp) && !pullerComp.NeedsHands) return; if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.PulledUid, uid)) { DebugTools.Assert("Unable to find available hand when starting pulling??"); } } private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args) { if (args.PullerUid != uid) return; // Try find hand that is doing this pull. // and clear it. foreach (var hand in component.Hands.Values) { if (hand.HeldEntity == null || !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem) || virtualItem.BlockingEntity != args.PulledUid) { continue; } TryDrop(args.PullerUid, hand, handsComp: component); break; } } #endregion #region interactions private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) { if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player) || !coordinates.IsValid(EntityManager)) return false; return ThrowHeldItem(player, coordinates); } /// /// Throw the player's currently held item. /// public bool ThrowHeldItem(EntityUid player, EntityCoordinates coordinates, float minDistance = 0.1f) { if (ContainerSystem.IsEntityInContainer(player) || !TryComp(player, out HandsComponent? hands) || hands.ActiveHandEntity is not { } throwEnt || !_actionBlockerSystem.CanThrow(player, throwEnt)) return false; if (_timing.CurTime < hands.NextThrowTime) return false; hands.NextThrowTime = _timing.CurTime + hands.ThrowCooldown; if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually) { var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent(player).Coordinates, stack); if (splitStack is not { Valid: true }) return false; throwEnt = splitStack.Value; } var direction = _transformSystem.ToMapCoordinates(coordinates).Position - _transformSystem.GetWorldPosition(player); if (direction == Vector2.Zero) return true; var length = direction.Length(); var distance = Math.Clamp(length, minDistance, hands.ThrowRange); direction *= distance / length; var throwSpeed = hands.BaseThrowspeed; // Let other systems change the thrown entity (useful for virtual items) // or the throw strength. var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player); RaiseLocalEvent(player, ref ev); if (ev.Cancelled) return true; // This can grief the above event so we raise it afterwards if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands)) return false; _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp(ev.ItemUid)); return true; } #endregion } }