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-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 ;
using Robust.Shared.GameObjects ;
2022-01-05 17:53:08 +13:00
using Robust.Shared.GameStates ;
2021-07-31 03:14:00 +02:00
using Robust.Shared.IoC ;
2022-03-02 12:29:42 +13:00
using Robust.Shared.Log ;
2022-01-05 17:53:08 +13:00
using Robust.Shared.Map ;
using Robust.Shared.Maths ;
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 ! ;
public override void Initialize ( )
{
base . Initialize ( ) ;
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
// Do we have a NEW hand?
var handsModified = component . Hands . Count ! = state . Hands . Count ;
if ( ! handsModified )
{
for ( var i = 0 ; i < state . Hands . Count ; i + + )
{
if ( component . Hands [ i ] . Name ! = state . Hands [ i ] . Name | |
component . Hands [ i ] . Location ! = state . Hands [ i ] . Location )
{
handsModified = true ;
break ;
}
}
}
if ( handsModified )
{
// we have new hands, get the new containers.
component . Hands = state . Hands ;
UpdateHandContainers ( uid , component ) ;
}
TrySetActiveHand ( uid , state . ActiveHand , component ) ;
2021-07-31 03:14:00 +02:00
}
2022-01-05 17:53:08 +13:00
/// <summary>
/// Used to update the hand-containers when hands have been added or removed. Also updates the GUI
/// </summary>
public void UpdateHandContainers ( EntityUid uid , HandsComponent ? hands = null , ContainerManagerComponent ? containerMan = null )
2021-07-31 03:14:00 +02:00
{
2022-01-05 17:53:08 +13:00
if ( ! Resolve ( uid , ref hands , ref containerMan ) )
return ;
foreach ( var hand in hands . Hands )
{
if ( hand . Container = = null )
{
hand . Container = hands . Owner . EnsureContainer < ContainerSlot > ( hand . Name ) ;
}
}
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-01-05 17:53:08 +13:00
return TryGetPlayerHands ( out var hands ) & & hands . TryGetActiveHeldEntity ( out var entity )
? entity
: 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 )
{
if ( ! hands . TryGetHand ( handName , out var pressedHand ) )
return ;
if ( ! hands . TryGetActiveHand ( out var activeHand ) )
return ;
var pressedEntity = pressedHand . HeldEntity ;
var activeEntity = activeHand . HeldEntity ;
2021-12-30 18:27:15 -08:00
if ( pressedHand = = activeHand & & activeEntity ! = null )
2021-07-31 03:14:00 +02:00
{
// use item in hand
// it will always be attack_self() in my heart.
RaiseNetworkEvent ( new UseInHandMsg ( ) ) ;
return ;
}
2021-12-30 18:27:15 -08:00
if ( pressedHand ! = 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 ;
}
2021-12-30 18:27:15 -08:00
if ( pressedHand ! = activeHand & & pressedEntity ! = null & & activeEntity ! = null )
2021-07-31 03:14:00 +02:00
{
// use active item on held item
RaiseNetworkEvent ( new ClientInteractUsingInHandMsg ( pressedHand . Name ) ) ;
return ;
}
2021-12-05 18:09:01 +01:00
if ( pressedHand ! = activeHand & & pressedEntity ! = default & & activeEntity = = default )
2021-07-31 03:14:00 +02:00
{
// use active item on held item
RaiseNetworkEvent ( new MoveItemFromHandMsg ( pressedHand . Name ) ) ;
}
}
2022-01-05 17:53:08 +13:00
/// <summary>
/// Called when a user clicks on an item in their hands GUI.
/// </summary>
2021-07-31 03:14:00 +02:00
public void UIHandActivate ( string handName )
{
2022-01-05 17:53:08 +13:00
RaiseNetworkEvent ( new ActivateInHandMsg ( handName ) ) ;
}
2022-03-02 12:29:42 +13:00
#region visuals
protected override void HandleContainerModified ( EntityUid uid , SharedHandsComponent handComp , ContainerModifiedMessage args )
{
if ( handComp . TryGetHand ( args . Container . ID , out var hand ) )
{
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 ( ) ;
// 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).
if ( component . TryGetHand ( args . ContainerId , out var hand ) )
{
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 ;
var states = hands . Hands
. Select ( hand = > new GuiHand ( hand . Name , hand . Location , hand . HeldEntity ) )
. ToArray ( ) ;
hands . Gui . Update ( new HandsGuiState ( states , hands . ActiveHand ) ) ;
}
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
}
}