| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- // SPDX-FileCopyrightText: 2021 Swept <sweptwastaken@protonmail.com>
- // SPDX-FileCopyrightText: 2021 Vera Aguilera Puerto <gradientvera@outlook.com>
- // SPDX-FileCopyrightText: 2022 Flipp Syder <76629141+vulppine@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2022 Jacob Tong <10494922+ShadowCommander@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr.@gmail.com>
- // SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
- // SPDX-FileCopyrightText: 2022 Jezithyr <jmaster9999@gmail.com>
- // SPDX-FileCopyrightText: 2022 Paul <ritter.paul1@googlemail.com>
- // SPDX-FileCopyrightText: 2022 Paul Ritter <ritter.paul1@googlemail.com>
- // SPDX-FileCopyrightText: 2022 ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2022 Visne <39844191+Visne@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2022 wrexbe <81056464+wrexbe@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2022 wrexbe <wrexbe@protonmail.com>
- // SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2023 Kara <lunarautomaton6@gmail.com>
- // SPDX-FileCopyrightText: 2023 Menshin <Menshin@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2023 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2023 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
- // SPDX-FileCopyrightText: 2024 AJCM-git <60196617+AJCM-git@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2024 Ed <96445749+TheShuEd@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2024 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2024 Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
- // SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
- // SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2024 metalgearsloth <comedian_vs_clown@hotmail.com>
- // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
- //
- // SPDX-License-Identifier: AGPL-3.0-or-later
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using Content.Shared._Shitmed.Body.Events; // Shitmed Change
- using Content.Client.DisplacementMap;
- using Content.Client.Examine;
- using Content.Client.Strip;
- using Content.Client.Verbs.UI;
- using Content.Shared.Body.Part; // Shitmed Change
- using Content.Shared.Hands;
- using Content.Shared.Hands.Components;
- using Content.Shared.Hands.EntitySystems;
- using Content.Shared.Inventory.VirtualItem;
- using Content.Shared.Item;
- using JetBrains.Annotations;
- using Robust.Client.GameObjects;
- using Robust.Client.Player;
- using Robust.Client.UserInterface;
- using Robust.Shared.Containers;
- using Robust.Shared.GameStates;
- using Robust.Shared.Player;
- namespace Content.Client.Hands.Systems
- {
- [UsedImplicitly]
- public sealed class HandsSystem : SharedHandsSystem
- {
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IUserInterfaceManager _ui = default!;
- [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
- [Dependency] private readonly StrippableSystem _stripSys = default!;
- [Dependency] private readonly ExamineSystem _examine = default!;
- [Dependency] private readonly DisplacementMapSystem _displacement = default!;
- public event Action<string, HandLocation>? OnPlayerAddHand;
- public event Action<string>? OnPlayerRemoveHand;
- public event Action<string?>? OnPlayerSetActiveHand;
- public event Action<HandsComponent>? OnPlayerHandsAdded;
- public event Action? OnPlayerHandsRemoved;
- public event Action<string, EntityUid>? OnPlayerItemAdded;
- public event Action<string, EntityUid>? OnPlayerItemRemoved;
- public event Action<string>? OnPlayerHandBlocked;
- public event Action<string>? OnPlayerHandUnblocked;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<HandsComponent, LocalPlayerAttachedEvent>(HandlePlayerAttached);
- SubscribeLocalEvent<HandsComponent, LocalPlayerDetachedEvent>(HandlePlayerDetached);
- SubscribeLocalEvent<HandsComponent, ComponentStartup>(OnHandsStartup);
- SubscribeLocalEvent<HandsComponent, ComponentShutdown>(OnHandsShutdown);
- SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
- SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
- SubscribeLocalEvent<HandsComponent, BodyPartRemovedEvent>(HandleBodyPartRemoved); // Shitmed Change
- SubscribeLocalEvent<HandsComponent, BodyPartDisabledEvent>(HandleBodyPartDisabled); // Shitmed Change
- OnHandSetActive += OnHandActivated;
- }
- #region StateHandling
- private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not HandsComponentState state)
- return;
- var handsModified = component.Hands.Count != state.Hands.Count;
- // we need to check that, even if we have the same amount, that the individual hands didn't change.
- if (!handsModified)
- {
- foreach (var hand in component.Hands.Values)
- {
- if (state.Hands.Contains(hand))
- continue;
- handsModified = true;
- break;
- }
- }
- var manager = EnsureComp<ContainerManagerComponent>(uid);
- if (handsModified)
- {
- List<Hand> addedHands = new();
- foreach (var hand in state.Hands)
- {
- if (component.Hands.ContainsKey(hand.Name))
- continue;
- var container = _containerSystem.EnsureContainer<ContainerSlot>(uid, hand.Name, manager);
- var newHand = new Hand(hand.Name, hand.Location, container);
- component.Hands.Add(hand.Name, newHand);
- addedHands.Add(newHand);
- }
- foreach (var name in component.Hands.Keys)
- {
- if (!state.HandNames.Contains(name))
- {
- RemoveHand(uid, name, component);
- }
- }
- component.SortedHands.Clear();
- component.SortedHands.AddRange(state.HandNames);
- var sorted = addedHands.OrderBy(hand => component.SortedHands.IndexOf(hand.Name));
- foreach (var hand in sorted)
- {
- AddHand(uid, hand, component);
- }
- }
- _stripSys.UpdateUi(uid);
- if (component.ActiveHand == null && state.ActiveHand == null)
- return; //edge case
- if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name)
- {
- SetActiveHand(uid, component.Hands[state.ActiveHand!], component);
- }
- }
- #endregion
- public void ReloadHandButtons()
- {
- if (!TryGetPlayerHands(out var hands))
- {
- return;
- }
- OnPlayerHandsAdded?.Invoke(hands);
- }
- public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
- {
- base.DoDrop(uid, hand, doDropInteraction, hands, log);
- if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
- sprite.RenderOrder = EntityManager.CurrentTick.Value;
- }
- public EntityUid? GetActiveHandEntity()
- {
- return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null;
- }
- /// <summary>
- /// Get the hands component of the local player
- /// </summary>
- public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands)
- {
- var player = _playerManager.LocalEntity;
- hands = null;
- return player != null && TryComp(player.Value, out hands);
- }
- /// <summary>
- /// Called when a user clicked on their hands GUI
- /// </summary>
- public void UIHandClick(HandsComponent hands, string handName)
- {
- if (!hands.Hands.TryGetValue(handName, out var pressedHand))
- return;
- if (hands.ActiveHand == null)
- return;
- var pressedEntity = pressedHand.HeldEntity;
- var activeEntity = hands.ActiveHand.HeldEntity;
- if (pressedHand == hands.ActiveHand && activeEntity != null)
- {
- // use item in hand
- // it will always be attack_self() in my heart.
- EntityManager.RaisePredictiveEvent(new RequestUseInHandEvent());
- return;
- }
- if (pressedHand != hands.ActiveHand && pressedEntity == null)
- {
- // change active hand
- EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName));
- return;
- }
- if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null)
- {
- // use active item on held item
- EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name));
- return;
- }
- if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null)
- {
- // move the item to the active hand
- EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name));
- }
- }
- /// <summary>
- /// Called when a user clicks on the little "activation" icon in the hands GUI. This is currently only used
- /// by storage (backpacks, etc).
- /// </summary>
- public void UIHandActivate(string handName)
- {
- EntityManager.RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
- }
- public void UIInventoryExamine(string handName)
- {
- if (!TryGetPlayerHands(out var hands) ||
- !hands.Hands.TryGetValue(handName, out var hand) ||
- hand.HeldEntity is not { Valid: true } entity)
- {
- return;
- }
- _examine.DoExamine(entity);
- }
- /// <summary>
- /// Called when a user clicks on the little "activation" icon in the hands GUI. This is currently only used
- /// by storage (backpacks, etc).
- /// </summary>
- public void UIHandOpenContextMenu(string handName)
- {
- if (!TryGetPlayerHands(out var hands) ||
- !hands.Hands.TryGetValue(handName, out var hand) ||
- hand.HeldEntity is not { Valid: true } entity)
- {
- return;
- }
- _ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
- }
- public void UIHandAltActivateItem(string handName)
- {
- RaisePredictiveEvent(new RequestHandAltInteractEvent(handName));
- }
- #region visuals
- // Shitmed Change Start
- private void HideLayers(EntityUid uid, HandsComponent component, Entity<BodyPartComponent> part, SpriteComponent? sprite = null)
- {
- if (part.Comp.PartType != BodyPartType.Hand || !Resolve(uid, ref sprite, logMissing: false))
- return;
- 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 (component.RevealedLayers.TryGetValue(location, out var revealedLayers))
- {
- foreach (var key in revealedLayers)
- sprite.RemoveLayer(key);
- revealedLayers.Clear();
- }
- }
- private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args) => HideLayers(uid, component, args.Part);
- private void HandleBodyPartDisabled(EntityUid uid, HandsComponent component, ref BodyPartDisabledEvent args) => HideLayers(uid, component, args.Part);
- // Shitmed Change End
- protected override void HandleEntityInserted(EntityUid uid, HandsComponent hands, EntInsertedIntoContainerMessage args)
- {
- base.HandleEntityInserted(uid, hands, args);
- if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
- return;
- UpdateHandVisuals(uid, args.Entity, hand);
- _stripSys.UpdateUi(uid);
- if (uid != _playerManager.LocalEntity)
- return;
- OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
- if (HasComp<VirtualItemComponent>(args.Entity))
- OnPlayerHandBlocked?.Invoke(hand.Name);
- }
- protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
- {
- base.HandleEntityRemoved(uid, hands, args);
- if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
- return;
- UpdateHandVisuals(uid, args.Entity, hand);
- _stripSys.UpdateUi(uid);
- if (uid != _playerManager.LocalEntity)
- return;
- OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
- if (HasComp<VirtualItemComponent>(args.Entity))
- OnPlayerHandUnblocked?.Invoke(hand.Name);
- }
- /// <summary>
- /// Update the players sprite with new in-hand visuals.
- /// </summary>
- private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null)
- {
- if (!Resolve(uid, ref handComp, ref sprite, false))
- return;
- // visual update might involve changes to the entity's effective sprite -> need to update hands GUI.
- if (uid == _playerManager.LocalEntity)
- OnPlayerItemAdded?.Invoke(hand.Name, held);
- if (!handComp.ShowInHands)
- return;
- // Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this
- // may eventually bloat the player with lots of layers.
- if (handComp.RevealedLayers.TryGetValue(hand.Location, out var revealedLayers))
- {
- foreach (var key in revealedLayers)
- {
- sprite.RemoveLayer(key);
- }
- revealedLayers.Clear();
- }
- else
- {
- revealedLayers = new();
- handComp.RevealedLayers[hand.Location] = revealedLayers;
- }
- if (hand.HeldEntity == null)
- {
- // the held item was removed.
- RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
- return;
- }
- var ev = new GetInhandVisualsEvent(uid, hand.Location);
- RaiseLocalEvent(held, ev);
- if (ev.Layers.Count == 0)
- {
- RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
- return;
- }
- // add the new layers
- foreach (var (key, layerData) in ev.Layers)
- {
- if (!revealedLayers.Add(key))
- {
- Log.Warning($"Duplicate key for in-hand visuals: {key}. Are multiple components attempting to modify the same layer? Entity: {ToPrettyString(held)}");
- continue;
- }
- var index = sprite.LayerMapReserveBlank(key);
- // In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
- if (layerData.RsiPath == null
- && layerData.TexturePath == null
- && sprite[index].Rsi == null)
- {
- if (TryComp<ItemComponent>(held, out var itemComponent) && itemComponent.RsiPath != null)
- sprite.LayerSetRSI(index, itemComponent.RsiPath);
- else if (TryComp(held, out SpriteComponent? clothingSprite))
- sprite.LayerSetRSI(index, clothingSprite.BaseRSI);
- }
- sprite.LayerSetData(index, layerData);
- //Add displacement maps
- if (handComp.HandDisplacement is not null)
- _displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers);
- }
- RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
- }
- private void OnVisualsChanged(EntityUid uid, HandsComponent component, VisualsChangedEvent args)
- {
- // update hands visuals if this item is in a hand (rather then inventory or other container).
- if (component.Hands.TryGetValue(args.ContainerId, out var hand))
- {
- UpdateHandVisuals(uid, GetEntity(args.Item), hand, component);
- }
- }
- #endregion
- #region Gui
- private void HandlePlayerAttached(EntityUid uid, HandsComponent component, LocalPlayerAttachedEvent args)
- {
- OnPlayerHandsAdded?.Invoke(component);
- }
- private void HandlePlayerDetached(EntityUid uid, HandsComponent component, LocalPlayerDetachedEvent args)
- {
- OnPlayerHandsRemoved?.Invoke();
- }
- private void OnHandsStartup(EntityUid uid, HandsComponent component, ComponentStartup args)
- {
- if (_playerManager.LocalEntity == uid)
- OnPlayerHandsAdded?.Invoke(component);
- }
- private void OnHandsShutdown(EntityUid uid, HandsComponent component, ComponentShutdown args)
- {
- if (_playerManager.LocalEntity == uid)
- OnPlayerHandsRemoved?.Invoke();
- }
- #endregion
- private void AddHand(EntityUid uid, Hand newHand, HandsComponent? handsComp = null)
- {
- AddHand(uid, newHand.Name, newHand.Location, handsComp);
- }
- public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null)
- {
- base.AddHand(uid, handName, handLocation, handsComp);
- if (uid == _playerManager.LocalEntity)
- OnPlayerAddHand?.Invoke(handName, handLocation);
- if (handsComp == null)
- return;
- if (handsComp.ActiveHand == null)
- SetActiveHand(uid, handsComp.Hands[handName], handsComp);
- }
- public override void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
- {
- if (uid == _playerManager.LocalEntity && handsComp != null &&
- handsComp.Hands.ContainsKey(handName) && uid ==
- _playerManager.LocalEntity)
- {
- OnPlayerRemoveHand?.Invoke(handName);
- }
- base.RemoveHand(uid, handName, handsComp);
- }
- private void OnHandActivated(Entity<HandsComponent>? ent)
- {
- if (ent is not { } hand)
- return;
- if (_playerManager.LocalEntity != hand.Owner)
- return;
- if (hand.Comp.ActiveHand == null)
- {
- OnPlayerSetActiveHand?.Invoke(null);
- return;
- }
- OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHand.Name);
- }
- }
- }
|