2021-02-03 22:07:13 +00:00
using System ;
2021-03-16 15:50:20 +01:00
using System.Diagnostics.CodeAnalysis ;
2020-08-13 14:40:27 +02:00
using System.Linq ;
using Content.Server.GameObjects.Components.GUI ;
2021-03-08 04:09:59 +11:00
using Content.Server.GameObjects.Components.Items ;
2020-08-13 14:40:27 +02:00
using Content.Server.GameObjects.Components.Items.Storage ;
2018-10-30 01:13:10 -07:00
using Content.Server.GameObjects.Components.Stack ;
2020-08-13 14:40:27 +02:00
using Content.Server.GameObjects.EntitySystems.Click ;
using Content.Server.Interfaces.GameObjects.Components.Items ;
2021-03-08 12:10:56 +11:00
using Content.Shared.GameObjects.Components.Movement ;
2020-08-13 14:40:27 +02:00
using Content.Shared.GameObjects.EntitySystems ;
2018-08-22 01:19:47 -07:00
using Content.Shared.Input ;
2020-09-01 12:34:53 +02:00
using Content.Shared.Interfaces ;
2019-05-05 13:09:21 +02:00
using JetBrains.Annotations ;
2021-02-11 01:13:03 -08:00
using Robust.Server.Player ;
2021-03-01 15:24:46 -08:00
using Robust.Shared.Containers ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.GameObjects ;
2020-05-31 14:32:05 -07:00
using Robust.Shared.Input.Binding ;
2020-05-05 00:39:15 +02:00
using Robust.Shared.Localization ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Map ;
2021-01-20 00:16:41 +11:00
using Robust.Shared.Maths ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Players ;
2020-09-13 14:23:52 +02:00
using static Content . Shared . GameObjects . Components . Inventory . EquipmentSlotDefines ;
2018-08-18 15:28:02 -07:00
2020-08-13 14:40:27 +02:00
namespace Content.Server.GameObjects.EntitySystems
2018-08-18 15:28:02 -07:00
{
2019-05-05 13:09:21 +02:00
[UsedImplicitly]
internal sealed class HandsSystem : EntitySystem
2018-08-18 15:28:02 -07:00
{
2019-04-20 16:20:18 -07:00
2018-10-30 01:13:10 -07:00
private const float ThrowForce = 1.5f ; // Throwing force of mobs in Newtons
2018-08-22 01:19:47 -07:00
2018-08-18 15:28:02 -07:00
/// <inheritdoc />
public override void Initialize ( )
{
base . Initialize ( ) ;
2020-02-19 17:08:59 -08:00
SubscribeLocalEvent < EntRemovedFromContainerMessage > ( HandleContainerModified ) ;
SubscribeLocalEvent < EntInsertedIntoContainerMessage > ( HandleContainerModified ) ;
2020-02-18 19:43:54 -08:00
2020-05-31 14:32:05 -07:00
CommandBinds . Builder
. Bind ( ContentKeyFunctions . SwapHands , InputCmdHandler . FromDelegate ( HandleSwapHands ) )
. Bind ( ContentKeyFunctions . Drop , new PointerInputCmdHandler ( HandleDrop ) )
. Bind ( ContentKeyFunctions . ActivateItemInHand , InputCmdHandler . FromDelegate ( HandleActivateItem ) )
. Bind ( ContentKeyFunctions . ThrowItemInHand , new PointerInputCmdHandler ( HandleThrowItem ) )
. Bind ( ContentKeyFunctions . SmartEquipBackpack , InputCmdHandler . FromDelegate ( HandleSmartEquipBackpack ) )
2020-07-27 00:54:32 +02:00
. Bind ( ContentKeyFunctions . SmartEquipBelt , InputCmdHandler . FromDelegate ( HandleSmartEquipBelt ) )
. Register < HandsSystem > ( ) ;
2018-08-18 15:28:02 -07:00
}
2018-11-21 20:58:11 +01:00
2018-08-18 15:28:02 -07:00
/// <inheritdoc />
public override void Shutdown ( )
{
base . Shutdown ( ) ;
2021-04-09 16:08:12 +02:00
UnsubscribeLocalEvent < EntRemovedFromContainerMessage > ( ) ;
UnsubscribeLocalEvent < EntInsertedIntoContainerMessage > ( ) ;
CommandBinds . Unregister < HandsSystem > ( ) ;
2018-08-18 15:28:02 -07:00
}
2020-02-19 14:39:00 -08:00
private static void HandleContainerModified ( ContainerModifiedMessage args )
2018-08-22 01:19:47 -07:00
{
2021-03-16 15:50:20 +01:00
if ( args . Container . Owner . TryGetComponent ( out IHandsComponent ? handsComponent ) )
2018-11-11 11:32:05 -08:00
{
2019-05-05 13:09:21 +02:00
handsComponent . HandleSlotModifiedMaybe ( args ) ;
2018-11-11 11:32:05 -08:00
}
2018-08-22 01:19:47 -07:00
}
2021-03-16 15:50:20 +01:00
private static bool TryGetAttachedComponent < T > ( IPlayerSession ? session , [ NotNullWhen ( true ) ] out T ? component )
2018-08-18 15:28:02 -07:00
where T : Component
{
2018-09-13 20:09:53 +02:00
component = default ;
2018-08-18 15:28:02 -07:00
2021-03-16 15:50:20 +01:00
var ent = session ? . AttachedEntity ;
2018-08-18 15:28:02 -07:00
2021-03-16 15:50:20 +01:00
if ( ent = = null | | ! ent . IsValid ( ) | | ! ent . TryGetComponent ( out T ? comp ) )
2020-07-25 15:11:16 +02:00
{
2018-08-18 15:28:02 -07:00
return false ;
2020-07-25 15:11:16 +02:00
}
2018-08-18 15:28:02 -07:00
component = comp ;
return true ;
}
2021-03-16 15:50:20 +01:00
private static void HandleSwapHands ( ICommonSession ? session )
2018-08-18 15:28:02 -07:00
{
2021-03-16 15:50:20 +01:00
if ( ! TryGetAttachedComponent ( session as IPlayerSession , out HandsComponent ? handsComp ) )
2020-07-25 15:11:16 +02:00
{
2018-08-18 15:28:02 -07:00
return ;
2020-07-25 15:11:16 +02:00
}
2018-08-18 15:28:02 -07:00
2020-07-25 15:11:16 +02:00
var interactionSystem = Get < InteractionSystem > ( ) ;
2019-11-25 00:11:47 +01:00
var oldItem = handsComp . GetActiveHand ;
2018-08-18 15:28:02 -07:00
handsComp . SwapHands ( ) ;
2019-11-25 00:11:47 +01:00
var newItem = handsComp . GetActiveHand ;
2020-07-25 15:11:16 +02:00
if ( oldItem ! = null )
{
2019-11-25 00:11:47 +01:00
interactionSystem . HandDeselectedInteraction ( handsComp . Owner , oldItem . Owner ) ;
2020-07-25 15:11:16 +02:00
}
2019-11-25 00:11:47 +01:00
2020-07-25 15:11:16 +02:00
if ( newItem ! = null )
{
2019-11-25 00:11:47 +01:00
interactionSystem . HandSelectedInteraction ( handsComp . Owner , newItem . Owner ) ;
2020-07-25 15:11:16 +02:00
}
2018-08-18 15:28:02 -07:00
}
2021-03-16 15:50:20 +01:00
private bool HandleDrop ( ICommonSession ? session , EntityCoordinates coords , EntityUid uid )
2018-08-18 15:28:02 -07:00
{
2021-03-16 15:50:20 +01:00
var ent = ( ( IPlayerSession ? ) session ) ? . AttachedEntity ;
2018-08-22 01:19:47 -07:00
2019-04-17 23:26:00 +02:00
if ( ent = = null | | ! ent . IsValid ( ) )
2019-09-17 16:08:45 -07:00
return false ;
2018-08-18 15:28:02 -07:00
2021-03-16 15:50:20 +01:00
if ( ! ent . TryGetComponent ( out HandsComponent ? handsComp ) )
2019-09-17 16:08:45 -07:00
return false ;
2019-11-25 00:11:47 +01:00
2021-01-20 00:16:41 +11:00
if ( handsComp . ActiveHand = = null | | handsComp . GetActiveHand = = null )
2019-11-25 00:11:47 +01:00
return false ;
2021-05-19 18:26:01 +01:00
// It's important to note that the calculations are done in map coordinates (they're absolute).
// They're translated back to EntityCoordinates at the end.
2021-01-20 00:16:41 +11:00
var entMap = ent . Transform . MapPosition ;
var targetPos = coords . ToMapPos ( EntityManager ) ;
var dropVector = targetPos - entMap . Position ;
var targetVector = Vector2 . Zero ;
2020-02-12 00:01:44 +01:00
2021-01-20 00:16:41 +11:00
if ( dropVector ! = Vector2 . Zero )
{
var targetLength = MathF . Min ( dropVector . Length , SharedInteractionSystem . InteractionRange - 0.001f ) ; // InteractionRange is reduced due to InRange not dealing with floating point error
2021-05-19 18:26:01 +01:00
var newCoords = new MapCoordinates ( dropVector . Normalized * targetLength + entMap . Position , entMap . MapId ) ;
2021-01-20 00:16:41 +11:00
var rayLength = Get < SharedInteractionSystem > ( ) . UnobstructedDistance ( entMap , newCoords , ignoredEnt : ent ) ;
targetVector = dropVector . Normalized * rayLength ;
}
2021-05-19 18:26:01 +01:00
var resultMapCoordinates = new MapCoordinates ( entMap . Position + targetVector , entMap . MapId ) ;
var resultEntCoordinates = EntityCoordinates . FromMap ( coords . GetParent ( EntityManager ) , resultMapCoordinates ) ;
handsComp . Drop ( handsComp . ActiveHand , resultEntCoordinates ) ;
2019-09-17 16:08:45 -07:00
return true ;
2018-08-18 15:28:02 -07:00
}
2021-03-16 15:50:20 +01:00
private static void HandleActivateItem ( ICommonSession ? session )
2018-08-18 15:28:02 -07:00
{
2021-03-16 15:50:20 +01:00
if ( ! TryGetAttachedComponent ( session as IPlayerSession , out HandsComponent ? handsComp ) )
2018-08-18 15:28:02 -07:00
return ;
handsComp . ActivateItem ( ) ;
}
2018-08-22 01:19:47 -07:00
2021-03-16 15:50:20 +01:00
private bool HandleThrowItem ( ICommonSession ? session , EntityCoordinates coords , EntityUid uid )
2018-08-22 01:19:47 -07:00
{
2021-03-16 15:50:20 +01:00
var playerEnt = ( ( IPlayerSession ? ) session ) ? . AttachedEntity ;
2018-08-22 01:19:47 -07:00
2021-03-08 04:09:59 +11:00
if ( playerEnt = = null | | ! playerEnt . IsValid ( ) )
2019-09-17 16:08:45 -07:00
return false ;
2018-08-22 01:19:47 -07:00
2021-03-16 15:50:20 +01:00
if ( ! playerEnt . TryGetComponent ( out HandsComponent ? handsComp ) )
2019-09-17 16:08:45 -07:00
return false ;
2018-08-22 01:19:47 -07:00
2021-03-16 15:50:20 +01:00
if ( handsComp . ActiveHand = = null | | ! handsComp . CanDrop ( handsComp . ActiveHand ) )
2019-09-17 16:08:45 -07:00
return false ;
2018-10-30 01:13:10 -07:00
2021-03-16 15:50:20 +01:00
var throwEnt = handsComp . GetItem ( handsComp . ActiveHand ) ? . Owner ;
if ( throwEnt = = null )
return false ;
2018-10-30 01:13:10 -07:00
2019-07-18 23:33:02 +02:00
if ( ! handsComp . ThrowItem ( ) )
2019-09-17 16:08:45 -07:00
return false ;
2019-07-18 23:33:02 +02:00
2020-02-13 22:43:02 +00:00
// throw the item, split off from a stack if it's meant to be thrown individually
2021-03-16 15:50:20 +01:00
if ( ! throwEnt . TryGetComponent ( out StackComponent ? stackComp ) | | stackComp . Count < 2 | | ! stackComp . ThrowIndividually )
2018-08-22 01:19:47 -07:00
{
2020-07-25 15:11:16 +02:00
handsComp . Drop ( handsComp . ActiveHand ) ;
2018-10-30 01:13:10 -07:00
}
else
{
stackComp . Use ( 1 ) ;
2021-03-16 15:50:20 +01:00
throwEnt = throwEnt . EntityManager . SpawnEntity ( throwEnt . Prototype ? . ID , playerEnt . Transform . Coordinates ) ;
2019-09-01 16:23:30 -07:00
// can only throw one item at a time, regardless of what the prototype stack size is.
if ( throwEnt . TryGetComponent < StackComponent > ( out var newStackComp ) )
newStackComp . Count = 1 ;
2018-10-30 01:13:10 -07:00
}
2018-11-21 20:58:11 +01:00
2021-03-08 04:09:59 +11:00
var direction = coords . ToMapPos ( EntityManager ) - playerEnt . Transform . WorldPosition ;
if ( direction = = Vector2 . Zero ) return true ;
direction = direction . Normalized * MathF . Min ( direction . Length , 8.0f ) ;
2021-03-08 12:10:56 +11:00
var yeet = direction * ThrowForce * 15 ;
2021-03-08 04:09:59 +11:00
2021-03-08 12:10:56 +11:00
// Softer yeet in weightlessness
if ( playerEnt . IsWeightless ( ) )
{
throwEnt . TryThrow ( yeet / 4 , playerEnt , 10.0f ) ;
}
else
{
throwEnt . TryThrow ( yeet , playerEnt ) ;
}
2019-07-18 23:33:02 +02:00
2019-09-17 16:08:45 -07:00
return true ;
2018-08-22 01:19:47 -07:00
}
2020-05-05 00:39:15 +02:00
2021-03-16 15:50:20 +01:00
private void HandleSmartEquipBackpack ( ICommonSession ? session )
2020-05-05 00:39:15 +02:00
{
2020-09-13 14:23:52 +02:00
HandleSmartEquip ( session , Slots . BACKPACK ) ;
2020-05-05 00:39:15 +02:00
}
2021-03-16 15:50:20 +01:00
private void HandleSmartEquipBelt ( ICommonSession ? session )
2020-05-05 00:39:15 +02:00
{
2020-09-13 14:23:52 +02:00
HandleSmartEquip ( session , Slots . BELT ) ;
2020-05-05 00:39:15 +02:00
}
2021-03-16 15:50:20 +01:00
private void HandleSmartEquip ( ICommonSession ? session , Slots equipmentSlot )
2020-05-05 00:39:15 +02:00
{
2021-03-16 15:50:20 +01:00
var plyEnt = ( ( IPlayerSession ? ) session ) ? . AttachedEntity ;
2020-05-05 00:39:15 +02:00
if ( plyEnt = = null | | ! plyEnt . IsValid ( ) )
return ;
2021-03-16 15:50:20 +01:00
if ( ! plyEnt . TryGetComponent ( out HandsComponent ? handsComp ) | |
! plyEnt . TryGetComponent ( out InventoryComponent ? inventoryComp ) )
2020-05-05 00:39:15 +02:00
return ;
2021-03-16 15:50:20 +01:00
if ( ! inventoryComp . TryGetSlotItem ( equipmentSlot , out ItemComponent ? equipmentItem )
2020-05-05 00:39:15 +02:00
| | ! equipmentItem . Owner . TryGetComponent < ServerStorageComponent > ( out var storageComponent ) )
{
2020-09-01 12:34:53 +02:00
plyEnt . PopupMessage ( Loc . GetString ( "You have no {0} to take something out of!" ,
2020-09-13 14:23:52 +02:00
SlotNames [ equipmentSlot ] . ToLower ( ) ) ) ;
2020-05-05 00:39:15 +02:00
return ;
}
2020-07-25 15:11:16 +02:00
var heldItem = handsComp . GetItem ( handsComp . ActiveHand ) ? . Owner ;
2020-05-05 00:39:15 +02:00
if ( heldItem ! = null )
{
2021-02-03 22:07:13 +00:00
storageComponent . PlayerInsertHeldEntity ( plyEnt ) ;
2020-05-05 00:39:15 +02:00
}
2021-03-16 15:50:20 +01:00
else if ( storageComponent . StoredEntities ! = null )
2020-05-05 00:39:15 +02:00
{
if ( storageComponent . StoredEntities . Count = = 0 )
{
2020-09-01 12:34:53 +02:00
plyEnt . PopupMessage ( Loc . GetString ( "There's nothing in your {0} to take out!" ,
2020-09-13 14:23:52 +02:00
SlotNames [ equipmentSlot ] . ToLower ( ) ) ) ;
2020-05-05 00:39:15 +02:00
}
else
{
var lastStoredEntity = Enumerable . Last ( storageComponent . StoredEntities ) ;
if ( storageComponent . Remove ( lastStoredEntity ) )
handsComp . PutInHandOrDrop ( lastStoredEntity . GetComponent < ItemComponent > ( ) ) ;
}
}
}
2018-08-18 15:28:02 -07:00
}
}