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