2021-07-31 03:14:00 +02:00
using Content.Client.Animations ;
2022-10-12 01:16:23 -07:00
using Content.Client.Examine ;
2022-10-16 06:00:04 +13:00
using Content.Client.Strip ;
2022-10-12 01:16:23 -07:00
using Content.Client.Verbs ;
2021-07-31 03:14:00 +02:00
using Content.Shared.Hands ;
using Content.Shared.Hands.Components ;
2022-03-17 20:13:31 +13:00
using Content.Shared.Hands.EntitySystems ;
2022-03-02 12:29:42 +13:00
using Content.Shared.Item ;
2021-07-31 03:14:00 +02:00
using JetBrains.Annotations ;
using Robust.Client.GameObjects ;
using Robust.Client.Player ;
using Robust.Shared.Containers ;
2022-01-05 17:53:08 +13:00
using Robust.Shared.GameStates ;
using Robust.Shared.Map ;
2021-07-31 03:14:00 +02:00
using Robust.Shared.Timing ;
2022-10-16 06:00:04 +13:00
using System.Diagnostics.CodeAnalysis ;
2023-01-07 21:24:52 -06:00
using Content.Client.Verbs.UI ;
using Robust.Client.UserInterface ;
2021-07-31 03:14:00 +02:00
2022-09-11 18:56:21 -07:00
namespace Content.Client.Hands.Systems
2021-07-31 03:14:00 +02:00
{
[UsedImplicitly]
public sealed class HandsSystem : SharedHandsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
2023-01-07 21:24:52 -06:00
[Dependency] private readonly IUserInterfaceManager _ui = default ! ;
2022-10-12 01:16:23 -07:00
2022-03-17 20:13:31 +13:00
[Dependency] private readonly SharedContainerSystem _containerSystem = default ! ;
2022-10-16 06:00:04 +13:00
[Dependency] private readonly StrippableSystem _stripSys = default ! ;
2022-10-12 01:16:23 -07:00
[Dependency] private readonly ExamineSystem _examine = 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 ;
2021-07-31 03:14:00 +02:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2022-10-12 01:16:23 -07:00
SubscribeLocalEvent < SharedHandsComponent , EntRemovedFromContainerMessage > ( HandleItemRemoved ) ;
SubscribeLocalEvent < SharedHandsComponent , EntInsertedIntoContainerMessage > ( HandleItemAdded ) ;
2022-03-17 20:13:31 +13:00
2021-07-31 03:14:00 +02:00
SubscribeLocalEvent < HandsComponent , PlayerAttachedEvent > ( HandlePlayerAttached ) ;
SubscribeLocalEvent < HandsComponent , PlayerDetachedEvent > ( HandlePlayerDetached ) ;
2022-10-12 01:16:23 -07:00
SubscribeLocalEvent < HandsComponent , ComponentAdd > ( HandleCompAdd ) ;
2021-07-31 03:14:00 +02:00
SubscribeLocalEvent < HandsComponent , ComponentRemove > ( HandleCompRemove ) ;
2022-01-05 17:53:08 +13:00
SubscribeLocalEvent < HandsComponent , ComponentHandleState > ( HandleComponentState ) ;
2022-03-02 12:29:42 +13:00
SubscribeLocalEvent < HandsComponent , VisualsChangedEvent > ( OnVisualsChanged ) ;
2021-07-31 03:14:00 +02:00
2022-01-05 17:53:08 +13:00
SubscribeNetworkEvent < PickupAnimationEvent > ( HandlePickupAnimation ) ;
2022-10-12 01:16:23 -07:00
OnHandSetActive + = OnHandActivated ;
2021-07-31 03:14:00 +02:00
}
2022-01-05 17:53:08 +13:00
#region StateHandling
private void HandleComponentState ( EntityUid uid , HandsComponent component , ref ComponentHandleState args )
2021-07-31 03:14:00 +02:00
{
2022-01-05 17:53:08 +13:00
if ( args . Current is not HandsComponentState state )
return ;
2021-07-31 03:14:00 +02:00
2022-01-05 17:53:08 +13:00
var handsModified = component . Hands . Count ! = state . Hands . Count ;
2022-03-17 20:13:31 +13:00
var manager = EnsureComp < ContainerManagerComponent > ( uid ) ;
2022-10-12 01:16:23 -07:00
if ( handsModified )
2022-01-05 17:53:08 +13:00
{
2022-10-12 01:16:23 -07:00
List < Hand > addedHands = new ( ) ;
foreach ( var hand in state . Hands )
2022-01-05 17:53:08 +13:00
{
2022-10-12 01:16:23 -07:00
if ( component . Hands . TryAdd ( hand . Name , hand ) )
{
hand . Container = _containerSystem . EnsureContainer < ContainerSlot > ( uid , hand . Name , manager ) ;
addedHands . Add ( hand ) ;
}
2022-01-05 17:53:08 +13:00
}
2022-03-17 20:13:31 +13:00
foreach ( var name in component . Hands . Keys )
2022-01-05 17:53:08 +13:00
{
2022-03-17 20:13:31 +13:00
if ( ! state . HandNames . Contains ( name ) )
2022-10-12 01:16:23 -07:00
{
RemoveHand ( uid , name , component ) ;
}
}
foreach ( var hand in addedHands )
{
AddHand ( uid , hand , component ) ;
2022-01-05 17:53:08 +13:00
}
2022-03-17 20:13:31 +13:00
component . SortedHands = new ( state . HandNames ) ;
2022-01-05 17:53:08 +13:00
}
2022-10-16 06:00:04 +13:00
_stripSys . UpdateUi ( uid ) ;
2022-10-12 01:16:23 -07:00
if ( component . ActiveHand = = null & & state . ActiveHand = = null )
return ; //edge case
2022-03-17 20:13:31 +13:00
2022-10-12 01:16:23 -07:00
if ( component . ActiveHand ! = null & & state . ActiveHand ! = component . ActiveHand . Name )
{
SetActiveHand ( uid , component . Hands [ state . ActiveHand ! ] , component ) ;
}
2021-07-31 03:14:00 +02:00
}
2022-01-05 17:53:08 +13:00
#endregion
2021-07-31 03:14:00 +02:00
2022-01-05 17:53:08 +13:00
#region PickupAnimation
private void HandlePickupAnimation ( PickupAnimationEvent msg )
2021-07-31 03:14:00 +02:00
{
2022-01-05 17:53:08 +13:00
PickupAnimation ( msg . ItemUid , msg . InitialPosition , msg . FinalPosition ) ;
}
2021-07-31 03:14:00 +02:00
2022-01-05 17:53:08 +13:00
public override void PickupAnimation ( EntityUid item , EntityCoordinates initialPosition , Vector2 finalPosition ,
EntityUid ? exclude )
{
PickupAnimation ( item , initialPosition , finalPosition ) ;
2021-07-31 03:14:00 +02:00
}
2022-01-05 17:53:08 +13:00
public void PickupAnimation ( EntityUid item , EntityCoordinates initialPosition , Vector2 finalPosition )
2021-07-31 03:14:00 +02:00
{
2022-01-05 17:53:08 +13:00
if ( ! _gameTiming . IsFirstTimePredicted )
return ;
2021-07-31 03:14:00 +02:00
2022-01-05 17:53:08 +13:00
if ( finalPosition . EqualsApprox ( initialPosition . Position , tolerance : 0.1f ) )
return ;
2021-07-31 03:14:00 +02:00
2022-01-05 17:53:08 +13:00
ReusableAnimations . AnimateEntityPickup ( item , initialPosition , finalPosition ) ;
2021-07-31 03:14:00 +02:00
}
2022-01-05 17:53:08 +13:00
#endregion
2021-07-31 03:14:00 +02:00
2022-10-17 15:13:41 -07:00
public void ReloadHandButtons ( )
{
if ( ! TryGetPlayerHands ( out var hands ) )
{
return ;
}
OnPlayerHandsAdded ? . Invoke ( hands ) ;
}
2022-07-27 04:22:49 +12:00
public override void DoDrop ( EntityUid uid , Hand hand , bool doDropInteraction = true , SharedHandsComponent ? hands = null )
{
base . DoDrop ( uid , hand , doDropInteraction , hands ) ;
if ( TryComp ( hand . HeldEntity , out SpriteComponent ? sprite ) )
sprite . RenderOrder = EntityManager . CurrentTick . Value ;
}
2021-12-30 18:27:15 -08:00
public EntityUid ? GetActiveHandEntity ( )
2021-10-22 04:26:02 +02:00
{
2022-03-17 20:13:31 +13:00
return TryGetPlayerHands ( out var hands ) ? hands . ActiveHandEntity : null ;
2021-10-22 04:26:02 +02:00
}
2022-01-05 17:53:08 +13:00
/// <summary>
/// Get the hands component of the local player
/// </summary>
public bool TryGetPlayerHands ( [ NotNullWhen ( true ) ] out HandsComponent ? hands )
2021-10-22 04:26:02 +02:00
{
var player = _playerManager . LocalPlayer ? . ControlledEntity ;
2022-01-05 17:53:08 +13:00
hands = null ;
return player ! = null & & TryComp ( player . Value , out hands ) ;
2021-10-22 04:26:02 +02:00
}
2022-01-05 17:53:08 +13:00
/// <summary>
/// Called when a user clicked on their hands GUI
/// </summary>
2021-07-31 03:14:00 +02:00
public void UIHandClick ( HandsComponent hands , string handName )
{
2022-03-17 20:13:31 +13:00
if ( ! hands . Hands . TryGetValue ( handName , out var pressedHand ) )
2021-07-31 03:14:00 +02:00
return ;
2022-03-17 20:13:31 +13:00
if ( hands . ActiveHand = = null )
2021-07-31 03:14:00 +02:00
return ;
var pressedEntity = pressedHand . HeldEntity ;
2022-03-17 20:13:31 +13:00
var activeEntity = hands . ActiveHand . HeldEntity ;
2021-07-31 03:14:00 +02:00
2022-03-17 20:13:31 +13:00
if ( pressedHand = = hands . ActiveHand & & activeEntity ! = null )
2021-07-31 03:14:00 +02:00
{
// use item in hand
// it will always be attack_self() in my heart.
2022-03-17 20:13:31 +13:00
EntityManager . RaisePredictiveEvent ( new RequestUseInHandEvent ( ) ) ;
2021-07-31 03:14:00 +02:00
return ;
}
2022-03-17 20:13:31 +13:00
if ( pressedHand ! = hands . ActiveHand & & pressedEntity = = null )
2021-07-31 03:14:00 +02:00
{
// change active hand
2022-01-05 17:53:08 +13:00
EntityManager . RaisePredictiveEvent ( new RequestSetHandEvent ( handName ) ) ;
2021-07-31 03:14:00 +02:00
return ;
}
2022-03-17 20:13:31 +13:00
if ( pressedHand ! = hands . ActiveHand & & pressedEntity ! = null & & activeEntity ! = null )
2021-07-31 03:14:00 +02:00
{
// use active item on held item
2022-03-17 20:13:31 +13:00
EntityManager . RaisePredictiveEvent ( new RequestHandInteractUsingEvent ( pressedHand . Name ) ) ;
2021-07-31 03:14:00 +02:00
return ;
}
2022-03-17 20:13:31 +13:00
if ( pressedHand ! = hands . ActiveHand & & pressedEntity ! = null & & activeEntity = = null )
2021-07-31 03:14:00 +02:00
{
2022-03-17 20:13:31 +13:00
// move the item to the active hand
EntityManager . RaisePredictiveEvent ( new RequestMoveHandItemEvent ( pressedHand . Name ) ) ;
2021-07-31 03:14:00 +02:00
}
}
2022-01-05 17:53:08 +13:00
/// <summary>
2022-03-17 20:13:31 +13:00
/// Called when a user clicks on the little "activation" icon in the hands GUI. This is currently only used
/// by storage (backpacks, etc).
2022-01-05 17:53:08 +13:00
/// </summary>
2021-07-31 03:14:00 +02:00
public void UIHandActivate ( string handName )
{
2022-03-17 20:13:31 +13:00
EntityManager . RaisePredictiveEvent ( new RequestActivateInHandEvent ( handName ) ) ;
2022-01-05 17:53:08 +13:00
}
2022-10-12 01:16:23 -07:00
public void UIInventoryExamine ( string handName )
2022-03-02 12:29:42 +13:00
{
2022-10-12 01:16:23 -07:00
if ( ! TryGetPlayerHands ( out var hands ) | |
! hands . Hands . TryGetValue ( handName , out var hand ) | |
hand . HeldEntity is not { Valid : true } entity )
2022-03-02 12:29:42 +13:00
{
2022-10-12 01:16:23 -07:00
return ;
2022-03-02 12:29:42 +13:00
}
2022-10-12 01:16:23 -07:00
_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 ;
}
2023-01-07 21:24:52 -06:00
_ui . GetUIController < VerbMenuUIController > ( ) . OpenVerbMenu ( entity ) ;
2022-10-12 01:16:23 -07:00
}
2023-01-07 21:24:52 -06:00
2022-11-03 18:36:45 -07:00
public void UIHandAltActivateItem ( string handName )
{
2022-11-10 14:04:47 +13:00
RaisePredictiveEvent ( new RequestHandAltInteractEvent ( handName ) ) ;
2022-11-03 18:36:45 -07:00
}
2022-10-12 01:16:23 -07:00
#region visuals
private void HandleItemAdded ( EntityUid uid , SharedHandsComponent handComp , ContainerModifiedMessage args )
{
if ( ! handComp . Hands . TryGetValue ( args . Container . ID , out var hand ) )
return ;
UpdateHandVisuals ( uid , args . Entity , hand ) ;
2022-10-16 06:00:04 +13:00
_stripSys . UpdateUi ( uid ) ;
2022-10-12 01:16:23 -07:00
if ( uid ! = _playerManager . LocalPlayer ? . ControlledEntity )
return ;
OnPlayerItemAdded ? . Invoke ( hand . Name , args . Entity ) ;
if ( HasComp < HandVirtualItemComponent > ( args . Entity ) )
OnPlayerHandBlocked ? . Invoke ( hand . Name ) ;
}
private void HandleItemRemoved ( EntityUid uid , SharedHandsComponent handComp , ContainerModifiedMessage args )
{
if ( ! handComp . Hands . TryGetValue ( args . Container . ID , out var hand ) )
return ;
UpdateHandVisuals ( uid , args . Entity , hand ) ;
2022-10-16 06:00:04 +13:00
_stripSys . UpdateUi ( uid ) ;
2022-10-12 01:16:23 -07:00
if ( uid ! = _playerManager . LocalPlayer ? . ControlledEntity )
return ;
OnPlayerItemRemoved ? . Invoke ( hand . Name , args . Entity ) ;
if ( HasComp < HandVirtualItemComponent > ( args . Entity ) )
OnPlayerHandUnblocked ? . Invoke ( hand . Name ) ;
2022-03-02 12:29:42 +13:00
}
/// <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 ;
2022-10-23 05:13:25 +13:00
// visual update might involve changes to the entity's effective sprite -> need to update hands GUI.
if ( uid = = _playerManager . LocalPlayer ? . ControlledEntity )
OnPlayerItemAdded ? . Invoke ( hand . Name , held ) ;
2022-10-22 18:54:28 +13:00
2022-03-04 18:02:53 +13:00
if ( ! handComp . ShowInHands )
return ;
2022-03-02 12:29:42 +13:00
// 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 ) ;
}
2022-10-12 01:16:23 -07:00
2022-03-02 12:29:42 +13:00
revealedLayers . Clear ( ) ;
}
else
{
revealedLayers = new ( ) ;
handComp . RevealedLayers [ hand . Location ] = revealedLayers ;
}
if ( hand . HeldEntity = = null )
{
// the held item was removed.
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( held , new HeldVisualsUpdatedEvent ( uid , revealedLayers ) , true ) ;
2022-03-02 12:29:42 +13:00
return ;
}
var ev = new GetInhandVisualsEvent ( uid , hand . Location ) ;
RaiseLocalEvent ( held , ev , false ) ;
if ( ev . Layers . Count = = 0 )
{
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( held , new HeldVisualsUpdatedEvent ( uid , revealedLayers ) , true ) ;
2022-03-02 12:29:42 +13:00
return ;
}
// add the new layers
foreach ( var ( key , layerData ) in ev . Layers )
{
if ( ! revealedLayers . Add ( key ) )
{
Logger . 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
& & TryComp ( held , out SpriteComponent ? clothingSprite ) )
{
sprite . LayerSetRSI ( index , clothingSprite . BaseRSI ) ;
}
sprite . LayerSetData ( index , layerData ) ;
}
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( held , new HeldVisualsUpdatedEvent ( uid , revealedLayers ) , true ) ;
2022-03-02 12:29:42 +13:00
}
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).
2022-03-17 20:13:31 +13:00
if ( component . Hands . TryGetValue ( args . ContainerId , out var hand ) )
2022-03-02 12:29:42 +13:00
{
UpdateHandVisuals ( uid , args . Item , hand , component ) ;
}
}
#endregion
2022-01-05 17:53:08 +13:00
#region Gui
2022-10-12 01:16:23 -07:00
private void HandlePlayerAttached ( EntityUid uid , HandsComponent component , PlayerAttachedEvent args )
{
2022-10-22 18:54:28 +13:00
OnPlayerHandsAdded ? . Invoke ( component ) ;
2022-01-05 17:53:08 +13:00
}
2022-10-12 01:16:23 -07:00
private void HandlePlayerDetached ( EntityUid uid , HandsComponent component , PlayerDetachedEvent args )
2022-01-05 17:53:08 +13:00
{
2022-10-12 01:16:23 -07:00
OnPlayerHandsRemoved ? . Invoke ( ) ;
}
2022-01-05 17:53:08 +13:00
2022-10-12 01:16:23 -07:00
private void HandleCompAdd ( EntityUid uid , HandsComponent component , ComponentAdd args )
{
if ( _playerManager . LocalPlayer ? . ControlledEntity = = uid )
OnPlayerHandsAdded ? . Invoke ( component ) ;
2021-07-31 03:14:00 +02:00
}
2022-10-12 01:16:23 -07:00
private void HandleCompRemove ( EntityUid uid , HandsComponent component , ComponentRemove args )
2021-07-31 03:14:00 +02:00
{
2022-10-12 01:16:23 -07:00
if ( _playerManager . LocalPlayer ? . ControlledEntity = = uid )
OnPlayerHandsRemoved ? . Invoke ( ) ;
2021-07-31 03:14:00 +02:00
}
2022-10-12 01:16:23 -07:00
#endregion
2021-07-31 03:14:00 +02:00
2022-10-12 01:16:23 -07:00
private void AddHand ( EntityUid uid , Hand newHand , SharedHandsComponent ? handsComp = null )
2021-07-31 03:14:00 +02:00
{
2022-10-12 01:16:23 -07:00
AddHand ( uid , newHand . Name , newHand . Location , handsComp ) ;
2021-07-31 03:14:00 +02:00
}
2022-10-12 01:16:23 -07:00
public override void AddHand ( EntityUid uid , string handName , HandLocation handLocation , SharedHandsComponent ? handsComp = null )
2021-07-31 03:14:00 +02:00
{
2022-10-12 01:16:23 -07:00
base . AddHand ( uid , handName , handLocation , handsComp ) ;
if ( uid = = _playerManager . LocalPlayer ? . ControlledEntity )
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 , SharedHandsComponent ? handsComp = null )
{
if ( uid = = _playerManager . LocalPlayer ? . ControlledEntity & & handsComp ! = null & &
handsComp . Hands . ContainsKey ( handName ) & & uid = =
_playerManager . LocalPlayer ? . ControlledEntity )
{
OnPlayerRemoveHand ? . Invoke ( handName ) ;
}
base . RemoveHand ( uid , handName , handsComp ) ;
2021-07-31 03:14:00 +02:00
}
2022-10-12 01:16:23 -07:00
private void OnHandActivated ( SharedHandsComponent ? handsComponent )
2021-07-31 03:14:00 +02:00
{
2022-10-12 01:16:23 -07:00
if ( handsComponent = = null )
return ;
if ( _playerManager . LocalPlayer ? . ControlledEntity ! = handsComponent . Owner )
return ;
if ( handsComponent . ActiveHand = = null )
{
OnPlayerSetActiveHand ? . Invoke ( null ) ;
return ;
}
OnPlayerSetActiveHand ? . Invoke ( handsComponent . ActiveHand . Name ) ;
2021-07-31 03:14:00 +02:00
}
}
}