diff --git a/Content.Server/GameObjects/EntitySystems/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/InteractionSystem.cs
index 2d54e7e8c0..d2631b7539 100644
--- a/Content.Server/GameObjects/EntitySystems/InteractionSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/InteractionSystem.cs
@@ -6,6 +6,12 @@ using SS14.Shared.Interfaces.GameObjects;
using System.Collections.Generic;
using System.Linq;
using SS14.Shared.Input;
+using SS14.Shared.Interfaces.Network;
+using SS14.Shared.IoC;
+using SS14.Server.Interfaces.Player;
+using SS14.Shared.Log;
+using SS14.Shared.Map;
+using SS14.Server.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
{
@@ -14,6 +20,12 @@ namespace Content.Server.GameObjects.EntitySystems
///
public interface IAttackby
{
+ ///
+ /// Called when using one object on another
+ ///
+ ///
+ ///
+ ///
bool Attackby(IEntity user, IEntity attackwith);
}
@@ -22,11 +34,53 @@ namespace Content.Server.GameObjects.EntitySystems
///
public interface IAttackHand
{
+ ///
+ /// Called when a player directly interacts with an empty hand
+ ///
+ ///
+ ///
bool Attackhand(IEntity user);
}
+ ///
+ /// This interface gives components behavior when being clicked by objects outside the range of direct use
+ ///
+ public interface IRangedAttackby
+ {
+ ///
+ /// Called when we try to interact with an entity out of range
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool RangedAttackby(IEntity user, IEntity attackwith, LocalCoordinates clicklocation);
+ }
+
+ ///
+ /// 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
+ ///
+ public interface IAfterAttack
+ {
+ ///
+ /// Called when we interact with nothing, or when we interact with an entity out of range that has no behavior
+ ///
+ ///
+ ///
+ void Afterattack(IEntity user, LocalCoordinates clicklocation);
+ }
+
+ ///
+ /// This interface gives components behavior when using the entity in your hands
+ ///
public interface IUse
{
+ ///
+ /// Called when we activate an object we are holding to use it
+ ///
+ ///
+ ///
bool UseEntity(IEntity user);
}
@@ -38,61 +92,145 @@ namespace Content.Server.GameObjects.EntitySystems
private const float INTERACTION_RANGE = 2;
private const float INTERACTION_RANGE_SQUARED = INTERACTION_RANGE * INTERACTION_RANGE;
- public override void Initialize()
+ ///
+ public override void RegisterMessageTypes()
{
- base.Initialize();
+ base.RegisterMessageTypes();
- SubscribeEvent(UserInteraction);
+ RegisterMessageType();
}
- private void UserInteraction(object sender, EntityEventArgs arg)
+ //Grab click events sent from the client input system
+ public override void HandleNetMessage(INetChannel channel, EntitySystemMessage message)
{
- var e = (ClickedOnEntityMessage)arg;
- if (e.MouseButton != ClickType.Left)
+ base.HandleNetMessage(channel, message);
+
+ var playerMan = IoCManager.Resolve();
+ var session = playerMan.GetSessionByChannel(channel);
+ var playerentity = session.AttachedEntity;
+
+ if (playerentity == null)
return;
- var user = EntityManager.GetEntity(e.Owner);
- var attacked = EntityManager.GetEntity(e.Clicked);
+ switch (message)
+ {
+ case ClickEventMessage msg:
+ UserInteraction(msg, playerentity);
+ break;
+ }
+ }
- if (!user.TryGetComponent(out var userTransform))
+ private void UserInteraction(ClickEventMessage msg, IEntity player)
+ {
+ //Verify click type
+ if (msg.Click != ClickType.Left)
+ return;
+
+ //Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null
+ IEntity attacked = null;
+ if (msg.Uid.IsValid())
+ attacked = EntityManager.GetEntity(msg.Uid);
+
+ //Verify player has a transform component
+ if (!player.TryGetComponent(out var playerTransform))
{
return;
}
-
- var distance = (userTransform.WorldPosition - attacked.GetComponent().WorldPosition).LengthSquared;
- if (distance > INTERACTION_RANGE_SQUARED)
+ //Verify player is on the same map as the entity he clicked on
+ else if (msg.Coordinates.MapID != playerTransform.MapID)
{
+ Logger.Warning(string.Format("Player named {0} clicked on a map he isn't located on", player.Name));
return;
}
- if (!user.TryGetComponent(out var hands))
+ //Verify player has a hand, and find what object he is currently holding in his active hand
+ if (!player.TryGetComponent(out var hands))
{
return;
}
-
var item = hands.GetHand(hands.ActiveIndex)?.Owner;
- if (item != null && attacked != item)
+
+ //TODO: Mob status code that allows or rejects interactions based on current mob status
+ //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
+
+ //Off entity click handling
+ if (attacked == null)
{
- Interaction(user, item, attacked);
+ if(item != null)
+ {
+ //AFTERATTACK: Check if we clicked on an empty location, if so the only interaction we can do is afterattack
+ InteractAfterattack(player, item, msg.Coordinates);
+ return;
+ }
+ return;
}
- else if(attacked == item)
+ //USE: Check if we clicked on the item we are holding in our active hand to use it
+ else if(attacked == item && item != null)
{
- UseInteraction(user, item);
+ UseInteraction(player, item);
+ return;
}
+
+ //Check if ClickLocation is in object bounds here, if not lets log as warning and see why
+ if(attacked != null && attacked.TryGetComponent(out BoundingBoxComponent boundingbox))
+ {
+ if (!boundingbox.WorldAABB.Contains(msg.Coordinates.Position))
+ {
+ Logger.Warning(string.Format("Player {0} clicked {1} outside of its bounding box component somehow", player.Name, attacked.Name));
+ return;
+ }
+ }
+
+ //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?
+ var distance = (playerTransform.WorldPosition - attacked.GetComponent().WorldPosition).LengthSquared;
+ if (distance > INTERACTION_RANGE_SQUARED)
+ {
+ if(item != null)
+ {
+ RangedInteraction(player, item, attacked, msg.Coordinates);
+ return;
+ }
+ return; //Add some form of ranged attackhand here if you need it someday, or perhaps just ways to modify the range of attackhand
+ }
+
+ //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)
+ {
+ Interaction(player, item, attacked, msg.Coordinates);
+ }
+ //ATTACKHAND: Since our hand is empty we will use attackhand
else
{
- Interaction(user, attacked);
+ Interaction(player, attacked);
+ }
+ }
+
+ ///
+ /// We didn't click on any entity, try doing an afterattack on the click location
+ ///
+ ///
+ ///
+ ///
+ private void InteractAfterattack(IEntity user, IEntity weapon, LocalCoordinates clicklocation)
+ {
+ //If not lets attempt to use afterattack from the held item on the click location
+ if (weapon.TryGetComponent(out IAfterAttack attacker))
+ {
+ attacker.Afterattack(user, clicklocation);
}
}
///
/// Uses a weapon/object on an entity
+ /// Finds interactable components with the Attackby interface and calls their function
///
///
///
///
- public static void Interaction(IEntity user, IEntity weapon, IEntity attacked)
+ public static void Interaction(IEntity user, IEntity weapon, IEntity attacked, LocalCoordinates clicklocation)
{
List interactables = attacked.GetComponents().ToList();
@@ -105,10 +243,17 @@ namespace Content.Server.GameObjects.EntitySystems
}
//Else check damage component to see if we damage if not attackby, and if so can we attack object
+
+ //If we aren't directly attacking the nearby object, lets see if our item has an after attack we can do
+ if (weapon.TryGetComponent(out IAfterAttack attacker))
+ {
+ attacker.Afterattack(user, clicklocation);
+ }
}
///
/// Uses an empty hand on an entity
+ /// Finds interactable components with the Attackhand interface and calls their function
///
///
///
@@ -129,6 +274,7 @@ namespace Content.Server.GameObjects.EntitySystems
///
/// Activates/Uses an object in control/possession of a user
+ /// If the item has the IUse interface on one of its components we use the object in our hand
///
///
///
@@ -136,6 +282,7 @@ namespace Content.Server.GameObjects.EntitySystems
{
List usables = used.GetComponents().ToList();
+ //Try to use item on any components which have the interface
for (var i = 0; i < usables.Count; i++)
{
if (usables[i].UseEntity(user)) //If an attackby returns a status completion we finish our attack
@@ -144,5 +291,32 @@ namespace Content.Server.GameObjects.EntitySystems
}
}
}
+
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ ///
+ public static void RangedInteraction(IEntity user, IEntity weapon, IEntity attacked, LocalCoordinates clicklocation)
+ {
+ List rangedusables = attacked.GetComponents().ToList();
+
+ //See if we have a ranged attack interaction
+ for (var i = 0; i < rangedusables.Count; i++)
+ {
+ if (rangedusables[i].RangedAttackby(user, weapon, clicklocation)) //If an attackby returns a status completion we finish our attack
+ {
+ return;
+ }
+ }
+
+ //If not lets attempt to use afterattack from the held item on the click location
+ if (weapon != null && weapon.TryGetComponent(out IAfterAttack attacker))
+ {
+ attacker.Afterattack(user, clicklocation);
+ }
+ }
}
}
diff --git a/engine b/engine
index 659b57940c..fc361882b8 160000
--- a/engine
+++ b/engine
@@ -1 +1 @@
-Subproject commit 659b57940c2d2e5c27731c9aeab640fb5a35a484
+Subproject commit fc361882b879dd3183d00546a89b35df15d7ddb5