2022-01-05 17:53:08 +13:00
using System.Diagnostics.CodeAnalysis ;
2021-07-31 03:14:00 +02:00
using System.Linq ;
using Content.Client.Animations ;
using Content.Client.HUD ;
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 ;
namespace Content.Client.Hands
{
[UsedImplicitly]
public sealed class HandsSystem : SharedHandsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
[Dependency] private readonly IGameHud _gameHud = default ! ;
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
2022-03-17 20:13:31 +13:00
[Dependency] private readonly SharedContainerSystem _containerSystem = default ! ;
2021-07-31 03:14:00 +02:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2022-03-17 20:13:31 +13:00
SubscribeLocalEvent < SharedHandsComponent , EntRemovedFromContainerMessage > ( HandleContainerModified ) ;
SubscribeLocalEvent < SharedHandsComponent , EntInsertedIntoContainerMessage > ( HandleContainerModified ) ;
2021-07-31 03:14:00 +02:00
SubscribeLocalEvent < HandsComponent , PlayerAttachedEvent > ( HandlePlayerAttached ) ;
SubscribeLocalEvent < HandsComponent , PlayerDetachedEvent > ( HandlePlayerDetached ) ;
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 ) ;
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 ) ;
foreach ( var hand in state . Hands )
2022-01-05 17:53:08 +13:00
{
2022-03-17 20:13:31 +13:00
if ( component . Hands . TryAdd ( hand . Name , hand ) )
2022-01-05 17:53:08 +13:00
{
2022-03-17 20:13:31 +13:00
hand . Container = _containerSystem . EnsureContainer < ContainerSlot > ( uid , hand . Name , manager ) ;
handsModified = true ;
2022-01-05 17:53:08 +13:00
}
}
if ( handsModified )
{
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 ) )
component . Hands . Remove ( name ) ;
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-03-17 20:13:31 +13:00
TrySetActiveHand ( uid , state . ActiveHand , component ) ;
2021-12-03 15:53:09 +01:00
if ( uid = = _playerManager . LocalPlayer ? . ControlledEntity )
2022-01-05 17:53:08 +13:00
UpdateGui ( ) ;
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
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-03-02 12:29:42 +13:00
#region visuals
2022-03-17 20:13:31 +13:00
private void HandleContainerModified ( EntityUid uid , SharedHandsComponent handComp , ContainerModifiedMessage args )
2022-03-02 12:29:42 +13:00
{
2022-03-17 20:13:31 +13:00
if ( handComp . Hands . TryGetValue ( args . Container . ID , out var hand ) )
2022-03-02 12:29:42 +13:00
{
UpdateHandVisuals ( uid , args . Entity , hand ) ;
}
}
/// <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 ;
if ( uid = = _playerManager . LocalPlayer ? . ControlledEntity )
UpdateGui ( ) ;
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 ) ;
}
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 ) ) ;
return ;
}
var ev = new GetInhandVisualsEvent ( uid , hand . Location ) ;
RaiseLocalEvent ( held , ev , false ) ;
if ( ev . Layers . Count = = 0 )
{
RaiseLocalEvent ( held , new HeldVisualsUpdatedEvent ( uid , revealedLayers ) ) ;
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 ) ;
}
RaiseLocalEvent ( held , new HeldVisualsUpdatedEvent ( uid , revealedLayers ) ) ;
}
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
public void UpdateGui ( HandsComponent ? hands = null )
{
if ( hands = = null & & ! TryGetPlayerHands ( out hands ) | | hands . Gui = = null )
return ;
2022-03-17 20:13:31 +13:00
var states = hands . Hands . Values
2022-01-05 17:53:08 +13:00
. Select ( hand = > new GuiHand ( hand . Name , hand . Location , hand . HeldEntity ) )
. ToArray ( ) ;
2022-03-17 20:13:31 +13:00
hands . Gui . Update ( new HandsGuiState ( states , hands . ActiveHand ? . Name ) ) ;
2022-01-05 17:53:08 +13:00
}
public override bool TrySetActiveHand ( EntityUid uid , string? value , SharedHandsComponent ? handComp = null )
{
if ( ! base . TrySetActiveHand ( uid , value , handComp ) )
return false ;
if ( uid = = _playerManager . LocalPlayer ? . ControlledEntity )
UpdateGui ( ) ;
return true ;
2021-07-31 03:14:00 +02:00
}
private void HandlePlayerAttached ( EntityUid uid , HandsComponent component , PlayerAttachedEvent args )
{
component . Gui = new HandsGui ( component , this ) ;
_gameHud . HandsContainer . AddChild ( component . Gui ) ;
2021-08-22 20:14:52 -07:00
component . Gui . SetPositionFirst ( ) ;
2022-01-05 17:53:08 +13:00
UpdateGui ( component ) ;
2021-07-31 03:14:00 +02:00
}
private static void HandlePlayerDetached ( EntityUid uid , HandsComponent component , PlayerDetachedEvent args )
{
ClearGui ( component ) ;
}
private static void HandleCompRemove ( EntityUid uid , HandsComponent component , ComponentRemove args )
{
ClearGui ( component ) ;
}
private static void ClearGui ( HandsComponent comp )
{
comp . Gui ? . Orphan ( ) ;
comp . Gui = null ;
}
2022-01-05 17:53:08 +13:00
#endregion
2021-07-31 03:14:00 +02:00
}
}