2018-08-16 15:57:11 -07:00
using System ;
using Content.Server.Interfaces.GameObjects ;
2018-02-05 13:57:26 -06:00
using SS14.Shared.GameObjects ;
2018-08-02 08:29:55 +02:00
using SS14.Shared.GameObjects.Systems ;
2018-02-05 13:57:26 -06:00
using SS14.Shared.Interfaces.GameObjects ;
using System.Collections.Generic ;
using System.Linq ;
2018-08-16 15:57:11 -07:00
using Content.Shared.Input ;
2018-03-03 18:07:09 -08:00
using SS14.Shared.Input ;
2018-03-09 10:59:03 -06:00
using SS14.Shared.Log ;
using SS14.Shared.Map ;
2018-08-16 15:57:11 -07:00
using SS14.Server.GameObjects.EntitySystems ;
using SS14.Server.Interfaces.Player ;
2018-08-02 08:29:55 +02:00
using SS14.Shared.Interfaces.GameObjects.Components ;
2018-08-16 15:57:11 -07:00
using SS14.Shared.Players ;
2018-02-05 13:57:26 -06:00
namespace Content.Server.GameObjects.EntitySystems
{
/// <summary>
/// This interface gives components behavior when being clicked on or "attacked" by a user with an object in their hand
/// </summary>
2019-04-05 19:22:38 +02:00
public interface IAttackBy
2018-02-05 13:57:26 -06:00
{
2018-03-09 10:59:03 -06:00
/// <summary>
/// Called when using one object on another
/// </summary>
/// <param name="user"></param>
/// <param name="attackwith"></param>
/// <returns></returns>
2019-04-05 19:22:38 +02:00
bool AttackBy ( AttackByEventArgs eventArgs ) ;
}
2019-04-05 19:29:16 +02:00
public class AttackByEventArgs : EventArgs
2019-04-05 19:22:38 +02:00
{
2019-04-05 19:29:16 +02:00
public IEntity User { get ; set ; }
2019-04-05 19:22:38 +02:00
public IEntity AttackWith { get ; set ; }
2018-02-05 13:57:26 -06:00
}
/// <summary>
/// This interface gives components behavior when being clicked on or "attacked" by a user with an empty hand
/// </summary>
public interface IAttackHand
{
2018-03-09 10:59:03 -06:00
/// <summary>
/// Called when a player directly interacts with an empty hand
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
2019-04-05 19:27:39 +02:00
bool AttackHand ( AttackHandEventArgs eventArgs ) ;
}
public class AttackHandEventArgs : EventArgs
{
public IEntity User { get ; set ; }
2018-02-05 13:57:26 -06:00
}
2018-03-09 10:59:03 -06:00
/// <summary>
/// This interface gives components behavior when being clicked by objects outside the range of direct use
/// </summary>
2019-04-05 19:32:18 +02:00
public interface IRangedAttackBy
2018-03-09 10:59:03 -06:00
{
/// <summary>
/// Called when we try to interact with an entity out of range
/// </summary>
/// <param name="user"></param>
/// <param name="attackwith"></param>
/// <param name="clicklocation"></param>
/// <returns></returns>
2019-04-05 19:32:18 +02:00
bool RangedAttackBy ( RangedAttackByEventArgs eventArgs ) ;
}
public class RangedAttackByEventArgs : EventArgs
{
public IEntity User { get ; set ; }
public IEntity Weapon { get ; set ; }
public GridCoordinates ClickLocation { get ; set ; }
2018-03-09 10:59:03 -06:00
}
/// <summary>
/// This interface gives components a behavior when clicking on another object and no interaction occurs
/// Doesn't pass what you clicked on as an argument, but if it becomes necessary we can add it later
/// </summary>
public interface IAfterAttack
{
/// <summary>
/// Called when we interact with nothing, or when we interact with an entity out of range that has no behavior
/// </summary>
/// <param name="user"></param>
/// <param name="clicklocation"></param>
2018-05-27 16:44:50 +02:00
/// <param name="attacked">The entity that was clicked on out of range. May be null if no entity was clicked on.true</param>
2019-01-18 11:40:30 +01:00
void Afterattack ( IEntity user , GridCoordinates clicklocation , IEntity attacked ) ;
2018-03-09 10:59:03 -06:00
}
/// <summary>
/// This interface gives components behavior when using the entity in your hands
/// </summary>
2018-02-05 13:57:26 -06:00
public interface IUse
{
2018-03-09 10:59:03 -06:00
/// <summary>
/// Called when we activate an object we are holding to use it
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
2018-02-05 13:57:26 -06:00
bool UseEntity ( IEntity user ) ;
}
2018-08-22 01:19:47 -07:00
/// <summary>
/// This interface gives components behavior when being activated in the world.
/// </summary>
public interface IActivate
{
/// <summary>
/// Called when this component is activated by another entity.
/// </summary>
/// <param name="user">Entity that activated this component.</param>
void Activate ( IEntity user ) ;
}
2018-02-05 13:57:26 -06:00
/// <summary>
/// Governs interactions during clicking on entities
/// </summary>
public class InteractionSystem : EntitySystem
{
2018-08-02 08:29:55 +02:00
public const float INTERACTION_RANGE = 2 ;
public const float INTERACTION_RANGE_SQUARED = INTERACTION_RANGE * INTERACTION_RANGE ;
2018-02-05 13:57:26 -06:00
2018-08-16 15:57:11 -07:00
public override void Initialize ( )
{
var inputSys = EntitySystemManager . GetEntitySystem < InputSystem > ( ) ;
inputSys . BindMap . BindFunction ( ContentKeyFunctions . UseItemInHand , new PointerInputCmdHandler ( HandleUseItemInHand ) ) ;
2018-08-22 01:19:47 -07:00
inputSys . BindMap . BindFunction ( ContentKeyFunctions . ActivateItemInWorld , new PointerInputCmdHandler ( ( HandleUseItemInWorld ) ) ) ;
}
2019-01-18 11:40:30 +01:00
private void HandleUseItemInWorld ( ICommonSession session , GridCoordinates coords , EntityUid uid )
2018-08-22 01:19:47 -07:00
{
if ( ! EntityManager . TryGetEntity ( uid , out var used ) )
return ;
if ( ! used . TryGetComponent ( out IActivate activateComp ) )
return ;
var playerEnt = ( ( IPlayerSession ) session ) . AttachedEntity ;
if ( playerEnt = = null | | ! playerEnt . IsValid ( ) )
return ;
2019-01-18 11:40:30 +01:00
if ( ! playerEnt . Transform . GridPosition . InRange ( used . Transform . GridPosition , INTERACTION_RANGE ) )
2018-08-22 01:19:47 -07:00
return ;
activateComp . Activate ( playerEnt ) ;
2018-08-16 15:57:11 -07:00
}
2019-01-18 11:40:30 +01:00
private void HandleUseItemInHand ( ICommonSession session , GridCoordinates coords , EntityUid uid )
2018-08-16 15:57:11 -07:00
{
2018-10-25 17:29:33 -07:00
// client sanitization
if ( ! coords . IsValidLocation ( ) )
{
2018-11-11 11:32:05 -08:00
Logger . InfoS ( "system.interaction" , $"Invalid Coordinates: client={session}, coords={coords}" ) ;
return ;
}
if ( uid . IsClientSide ( ) )
{
Logger . WarningS ( "system.interaction" , $"Client sent interaction with client-side entity. Session={session}, Uid={uid}" ) ;
2018-10-25 17:29:33 -07:00
return ;
}
2018-08-16 15:57:11 -07:00
UserInteraction ( ( ( IPlayerSession ) session ) . AttachedEntity , coords , uid ) ;
}
2019-01-18 11:40:30 +01:00
private void UserInteraction ( IEntity player , GridCoordinates coordinates , EntityUid clickedUid )
2018-02-05 13:57:26 -06:00
{
2018-03-09 10:59:03 -06:00
//Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null
2018-08-16 15:57:11 -07:00
if ( ! EntityManager . TryGetEntity ( clickedUid , out var attacked ) )
2018-08-22 01:19:47 -07:00
attacked = null ;
2018-05-10 19:21:15 +02:00
2018-03-09 10:59:03 -06:00
//Verify player has a transform component
2018-08-02 08:29:55 +02:00
if ( ! player . TryGetComponent < ITransformComponent > ( out var playerTransform ) )
2018-03-09 10:59:03 -06:00
{
return ;
}
//Verify player is on the same map as the entity he clicked on
2018-08-16 15:57:11 -07:00
else if ( coordinates . MapID ! = playerTransform . MapID )
2018-02-05 13:57:26 -06:00
{
2018-03-09 10:59:03 -06:00
Logger . Warning ( string . Format ( "Player named {0} clicked on a map he isn't located on" , player . Name ) ) ;
2018-02-05 13:57:26 -06:00
return ;
}
2018-03-09 10:59:03 -06:00
//Verify player has a hand, and find what object he is currently holding in his active hand
if ( ! player . TryGetComponent < IHandsComponent > ( out var hands ) )
2018-02-05 13:57:26 -06:00
{
return ;
}
2018-03-09 10:59:03 -06:00
2018-11-11 11:32:05 -08:00
var item = hands . GetActiveHand ? . Owner ;
2018-03-09 10:59:03 -06:00
2018-12-13 07:47:19 -06:00
if ( ! ActionBlockerSystem . CanInteract ( player ) )
2018-04-22 06:11:38 -05:00
return ;
2018-12-13 07:47:19 -06:00
//TODO: Check if client should be able to see that object to click on it in the first place, prevent using locaters by firing a laser or something
2018-02-05 13:57:26 -06:00
2018-05-10 19:21:15 +02:00
2018-04-22 06:11:38 -05:00
//Clicked on empty space behavior, try using ranged attack
if ( attacked = = null & & item ! = null )
2018-02-05 13:57:26 -06:00
{
2018-04-22 06:11:38 -05:00
//AFTERATTACK: Check if we clicked on an empty location, if so the only interaction we can do is afterattack
2018-08-16 15:57:11 -07:00
InteractAfterattack ( player , item , coordinates ) ;
2018-03-09 10:59:03 -06:00
return ;
}
2018-05-10 19:21:15 +02:00
else if ( attacked = = null )
2018-03-09 10:59:03 -06:00
{
2018-04-22 06:11:38 -05:00
return ;
}
//Verify attacked object is on the map if we managed to click on it somehow
2018-08-02 08:29:55 +02:00
if ( ! attacked . GetComponent < ITransformComponent > ( ) . IsMapTransform )
2018-04-22 06:11:38 -05:00
{
Logger . Warning ( string . Format ( "Player named {0} clicked on object {1} that isn't currently on the map somehow" , player . Name , attacked . Name ) ) ;
2018-02-05 13:57:26 -06:00
return ;
}
2018-03-09 10:59:03 -06:00
//Check if ClickLocation is in object bounds here, if not lets log as warning and see why
2018-04-22 06:11:38 -05:00
if ( attacked . TryGetComponent ( out BoundingBoxComponent boundingbox ) )
2018-03-09 10:59:03 -06:00
{
2018-08-16 15:57:11 -07:00
if ( ! boundingbox . WorldAABB . Contains ( coordinates . Position ) )
2018-03-09 10:59:03 -06:00
{
Logger . Warning ( string . Format ( "Player {0} clicked {1} outside of its bounding box component somehow" , player . Name , attacked . Name ) ) ;
return ;
}
}
2018-02-05 13:57:26 -06:00
2018-03-09 10:59:03 -06:00
//RANGEDATTACK/AFTERATTACK: Check distance between user and clicked item, if too large parse it in the ranged function
//TODO: have range based upon the item being used? or base it upon some variables of the player himself?
2018-08-02 08:29:55 +02:00
var distance = ( playerTransform . WorldPosition - attacked . GetComponent < ITransformComponent > ( ) . WorldPosition ) . LengthSquared ;
2018-03-09 10:59:03 -06:00
if ( distance > INTERACTION_RANGE_SQUARED )
2018-02-05 13:57:26 -06:00
{
2018-05-10 19:21:15 +02:00
if ( item ! = null )
2018-03-09 10:59:03 -06:00
{
2018-08-16 15:57:11 -07:00
RangedInteraction ( player , item , attacked , coordinates ) ;
2018-03-09 10:59:03 -06:00
return ;
}
return ; //Add some form of ranged attackhand here if you need it someday, or perhaps just ways to modify the range of attackhand
2018-02-05 13:57:26 -06:00
}
2018-05-10 19:21:15 +02:00
2018-03-09 10:59:03 -06:00
//We are close to the nearby object and the object isn't contained in our active hand
//ATTACKBY/AFTERATTACK: We will either use the item on the nearby object
if ( item ! = null )
2018-02-05 13:57:26 -06:00
{
2018-08-16 15:57:11 -07:00
Interaction ( player , item , attacked , coordinates ) ;
2018-02-05 13:57:26 -06:00
}
2018-03-09 10:59:03 -06:00
//ATTACKHAND: Since our hand is empty we will use attackhand
2018-02-05 13:57:26 -06:00
else
{
2018-03-09 10:59:03 -06:00
Interaction ( player , attacked ) ;
}
}
/// <summary>
/// We didn't click on any entity, try doing an afterattack on the click location
/// </summary>
/// <param name="user"></param>
/// <param name="weapon"></param>
/// <param name="clicklocation"></param>
2019-01-18 11:40:30 +01:00
public static void InteractAfterattack ( IEntity user , IEntity weapon , GridCoordinates clicklocation )
2018-03-09 10:59:03 -06:00
{
2018-05-10 19:21:15 +02:00
List < IAfterAttack > afterattacks = weapon . GetAllComponents < IAfterAttack > ( ) . ToList ( ) ;
2018-04-05 17:32:51 -05:00
for ( var i = 0 ; i < afterattacks . Count ; i + + )
2018-03-09 10:59:03 -06:00
{
2018-05-27 16:44:50 +02:00
afterattacks [ i ] . Afterattack ( user , clicklocation , null ) ;
2018-02-05 13:57:26 -06:00
}
}
/// <summary>
/// Uses a weapon/object on an entity
2018-03-09 10:59:03 -06:00
/// Finds interactable components with the Attackby interface and calls their function
2018-02-05 13:57:26 -06:00
/// </summary>
/// <param name="user"></param>
/// <param name="weapon"></param>
/// <param name="attacked"></param>
2019-01-18 11:40:30 +01:00
public static void Interaction ( IEntity user , IEntity weapon , IEntity attacked , GridCoordinates clicklocation )
2018-02-05 13:57:26 -06:00
{
2019-04-05 19:22:38 +02:00
List < IAttackBy > interactables = attacked . GetAllComponents < IAttackBy > ( ) . ToList ( ) ;
2018-02-05 13:57:26 -06:00
2018-05-10 19:21:15 +02:00
for ( var i = 0 ; i < interactables . Count ; i + + )
2018-02-05 13:57:26 -06:00
{
2019-04-05 19:22:38 +02:00
if ( interactables [ i ] . AttackBy ( new AttackByEventArgs { User = user , AttackWith = weapon } ) ) //If an attackby returns a status completion we finish our attack
2018-02-05 13:57:26 -06:00
{
return ;
}
}
//Else check damage component to see if we damage if not attackby, and if so can we attack object
2018-03-09 10:59:03 -06:00
2018-04-05 17:32:51 -05:00
2018-03-09 10:59:03 -06:00
//If we aren't directly attacking the nearby object, lets see if our item has an after attack we can do
2018-05-10 19:21:15 +02:00
List < IAfterAttack > afterattacks = weapon . GetAllComponents < IAfterAttack > ( ) . ToList ( ) ;
2018-04-05 17:32:51 -05:00
for ( var i = 0 ; i < afterattacks . Count ; i + + )
2018-03-09 10:59:03 -06:00
{
2018-05-27 16:44:50 +02:00
afterattacks [ i ] . Afterattack ( user , clicklocation , attacked ) ;
2018-03-09 10:59:03 -06:00
}
2018-02-05 13:57:26 -06:00
}
/// <summary>
/// Uses an empty hand on an entity
2018-03-09 10:59:03 -06:00
/// Finds interactable components with the Attackhand interface and calls their function
2018-02-05 13:57:26 -06:00
/// </summary>
/// <param name="user"></param>
/// <param name="attacked"></param>
public static void Interaction ( IEntity user , IEntity attacked )
{
2018-05-10 19:21:15 +02:00
List < IAttackHand > interactables = attacked . GetAllComponents < IAttackHand > ( ) . ToList ( ) ;
2018-02-05 13:57:26 -06:00
for ( var i = 0 ; i < interactables . Count ; i + + )
{
2019-04-05 19:27:39 +02:00
if ( interactables [ i ] . AttackHand ( new AttackHandEventArgs { User = user } ) ) //If an attackby returns a status completion we finish our attack
2018-02-05 13:57:26 -06:00
{
return ;
}
}
//Else check damage component to see if we damage if not attackby, and if so can we attack object
}
2018-04-22 06:11:38 -05:00
/// <summary>
/// Activates the Use behavior of an object
/// Verifies that the user is capable of doing the use interaction first
/// </summary>
/// <param name="user"></param>
/// <param name="used"></param>
public static void TryUseInteraction ( IEntity user , IEntity used )
{
2018-12-13 07:47:19 -06:00
if ( user ! = null & & used ! = null & & ActionBlockerSystem . CanUse ( user ) )
2018-04-22 06:11:38 -05:00
{
UseInteraction ( user , used ) ;
}
}
2018-02-05 13:57:26 -06:00
/// <summary>
/// Activates/Uses an object in control/possession of a user
2018-03-09 10:59:03 -06:00
/// If the item has the IUse interface on one of its components we use the object in our hand
2018-02-05 13:57:26 -06:00
/// </summary>
/// <param name="user"></param>
/// <param name="attacked"></param>
public static void UseInteraction ( IEntity user , IEntity used )
{
2018-05-10 19:21:15 +02:00
List < IUse > usables = used . GetAllComponents < IUse > ( ) . ToList ( ) ;
2018-02-05 13:57:26 -06:00
2018-03-09 10:59:03 -06:00
//Try to use item on any components which have the interface
2018-02-05 13:57:26 -06:00
for ( var i = 0 ; i < usables . Count ; i + + )
{
if ( usables [ i ] . UseEntity ( user ) ) //If an attackby returns a status completion we finish our attack
{
return ;
}
}
}
2018-03-09 10:59:03 -06:00
/// <summary>
/// Will have two behaviors, either "uses" the weapon at range on the entity if it is capable of accepting that action
/// Or it will use the weapon itself on the position clicked, regardless of what was there
/// </summary>
/// <param name="user"></param>
/// <param name="weapon"></param>
/// <param name="attacked"></param>
2019-01-18 11:40:30 +01:00
public static void RangedInteraction ( IEntity user , IEntity weapon , IEntity attacked , GridCoordinates clicklocation )
2018-03-09 10:59:03 -06:00
{
2019-04-05 19:32:18 +02:00
List < IRangedAttackBy > rangedusables = attacked . GetAllComponents < IRangedAttackBy > ( ) . ToList ( ) ;
2018-03-09 10:59:03 -06:00
//See if we have a ranged attack interaction
for ( var i = 0 ; i < rangedusables . Count ; i + + )
{
2019-04-05 19:32:18 +02:00
if ( rangedusables [ i ] . RangedAttackBy ( new RangedAttackByEventArgs { User = user , Weapon = weapon , ClickLocation = clicklocation } ) ) //If an attackby returns a status completion we finish our attack
2018-03-09 10:59:03 -06:00
{
return ;
}
}
2018-05-10 19:21:15 +02:00
if ( weapon ! = null )
2018-03-09 10:59:03 -06:00
{
2018-05-10 19:21:15 +02:00
List < IAfterAttack > afterattacks = weapon . GetAllComponents < IAfterAttack > ( ) . ToList ( ) ;
2018-04-05 17:32:51 -05:00
//See if we have a ranged attack interaction
for ( var i = 0 ; i < afterattacks . Count ; i + + )
{
2018-05-27 16:44:50 +02:00
afterattacks [ i ] . Afterattack ( user , clicklocation , attacked ) ;
2018-04-05 17:32:51 -05:00
}
2018-03-09 10:59:03 -06:00
}
}
2018-02-05 13:57:26 -06:00
}
}