Full interaction system (#43)

* Full interaction system

Has support for ranged interactions, and afterattacks, no longer relies on the clickable comopnent and you can click on no component and it will still function

* yes
This commit is contained in:
clusterfack
2018-03-09 10:59:03 -06:00
committed by Pieter-Jan Briers
parent 3f89f3f0f7
commit 7554d51b0a
2 changed files with 195 additions and 21 deletions

View File

@@ -6,6 +6,12 @@ using SS14.Shared.Interfaces.GameObjects;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using SS14.Shared.Input; 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 namespace Content.Server.GameObjects.EntitySystems
{ {
@@ -14,6 +20,12 @@ namespace Content.Server.GameObjects.EntitySystems
/// </summary> /// </summary>
public interface IAttackby public interface IAttackby
{ {
/// <summary>
/// Called when using one object on another
/// </summary>
/// <param name="user"></param>
/// <param name="attackwith"></param>
/// <returns></returns>
bool Attackby(IEntity user, IEntity attackwith); bool Attackby(IEntity user, IEntity attackwith);
} }
@@ -22,11 +34,53 @@ namespace Content.Server.GameObjects.EntitySystems
/// </summary> /// </summary>
public interface IAttackHand public interface IAttackHand
{ {
/// <summary>
/// Called when a player directly interacts with an empty hand
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool Attackhand(IEntity user); bool Attackhand(IEntity user);
} }
/// <summary>
/// This interface gives components behavior when being clicked by objects outside the range of direct use
/// </summary>
public interface IRangedAttackby
{
/// <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>
bool RangedAttackby(IEntity user, IEntity attackwith, LocalCoordinates clicklocation);
}
/// <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>
void Afterattack(IEntity user, LocalCoordinates clicklocation);
}
/// <summary>
/// This interface gives components behavior when using the entity in your hands
/// </summary>
public interface IUse public interface IUse
{ {
/// <summary>
/// Called when we activate an object we are holding to use it
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool UseEntity(IEntity user); bool UseEntity(IEntity user);
} }
@@ -38,61 +92,145 @@ namespace Content.Server.GameObjects.EntitySystems
private const float INTERACTION_RANGE = 2; private const float INTERACTION_RANGE = 2;
private const float INTERACTION_RANGE_SQUARED = INTERACTION_RANGE * INTERACTION_RANGE; private const float INTERACTION_RANGE_SQUARED = INTERACTION_RANGE * INTERACTION_RANGE;
public override void Initialize() /// <inheritdoc />
public override void RegisterMessageTypes()
{ {
base.Initialize(); base.RegisterMessageTypes();
SubscribeEvent<ClickedOnEntityMessage>(UserInteraction); RegisterMessageType<ClickEventMessage>();
} }
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; base.HandleNetMessage(channel, message);
if (e.MouseButton != ClickType.Left)
var playerMan = IoCManager.Resolve<IPlayerManager>();
var session = playerMan.GetSessionByChannel(channel);
var playerentity = session.AttachedEntity;
if (playerentity == null)
return; return;
var user = EntityManager.GetEntity(e.Owner); switch (message)
var attacked = EntityManager.GetEntity(e.Clicked); {
case ClickEventMessage msg:
UserInteraction(msg, playerentity);
break;
}
}
if (!user.TryGetComponent<IServerTransformComponent>(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<IServerTransformComponent>(out var playerTransform))
{ {
return; return;
} }
//Verify player is on the same map as the entity he clicked on
var distance = (userTransform.WorldPosition - attacked.GetComponent<IServerTransformComponent>().WorldPosition).LengthSquared; else if (msg.Coordinates.MapID != playerTransform.MapID)
if (distance > INTERACTION_RANGE_SQUARED)
{ {
Logger.Warning(string.Format("Player named {0} clicked on a map he isn't located on", player.Name));
return; return;
} }
if (!user.TryGetComponent<IHandsComponent>(out var hands)) //Verify player has a hand, and find what object he is currently holding in his active hand
if (!player.TryGetComponent<IHandsComponent>(out var hands))
{ {
return; return;
} }
var item = hands.GetHand(hands.ActiveIndex)?.Owner; 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<IServerTransformComponent>().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 else
{ {
Interaction(user, attacked); 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>
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);
} }
} }
/// <summary> /// <summary>
/// Uses a weapon/object on an entity /// Uses a weapon/object on an entity
/// Finds interactable components with the Attackby interface and calls their function
/// </summary> /// </summary>
/// <param name="user"></param> /// <param name="user"></param>
/// <param name="weapon"></param> /// <param name="weapon"></param>
/// <param name="attacked"></param> /// <param name="attacked"></param>
public static void Interaction(IEntity user, IEntity weapon, IEntity attacked) public static void Interaction(IEntity user, IEntity weapon, IEntity attacked, LocalCoordinates clicklocation)
{ {
List<IAttackby> interactables = attacked.GetComponents<IAttackby>().ToList(); List<IAttackby> interactables = attacked.GetComponents<IAttackby>().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 //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);
}
} }
/// <summary> /// <summary>
/// Uses an empty hand on an entity /// Uses an empty hand on an entity
/// Finds interactable components with the Attackhand interface and calls their function
/// </summary> /// </summary>
/// <param name="user"></param> /// <param name="user"></param>
/// <param name="attacked"></param> /// <param name="attacked"></param>
@@ -129,6 +274,7 @@ namespace Content.Server.GameObjects.EntitySystems
/// <summary> /// <summary>
/// Activates/Uses an object in control/possession of a user /// 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
/// </summary> /// </summary>
/// <param name="user"></param> /// <param name="user"></param>
/// <param name="attacked"></param> /// <param name="attacked"></param>
@@ -136,6 +282,7 @@ namespace Content.Server.GameObjects.EntitySystems
{ {
List<IUse> usables = used.GetComponents<IUse>().ToList(); List<IUse> usables = used.GetComponents<IUse>().ToList();
//Try to use item on any components which have the interface
for (var i = 0; i < usables.Count; i++) for (var i = 0; i < usables.Count; i++)
{ {
if (usables[i].UseEntity(user)) //If an attackby returns a status completion we finish our attack 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
} }
} }
} }
/// <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>
public static void RangedInteraction(IEntity user, IEntity weapon, IEntity attacked, LocalCoordinates clicklocation)
{
List<IRangedAttackby> rangedusables = attacked.GetComponents<IRangedAttackby>().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);
}
}
} }
} }

2
engine

Submodule engine updated: 659b57940c...fc361882b8