diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
index 026a7e9fb1..9dfbebd8d6 100644
--- a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.Input;
@@ -160,41 +161,25 @@ namespace Content.Client.GameObjects.EntitySystems
}
var user = GetUserEntity();
+ //Get verbs, component dependent.
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
- if (verb.RequireInteractionRange)
- {
- var distanceSquared = (user.Transform.WorldPosition - entity.Transform.WorldPosition)
- .LengthSquared;
- if (distanceSquared > Verb.InteractionRangeSquared)
- {
- continue;
- }
- }
+ if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity))
+ continue;
var disabled = verb.GetVisibility(user, component) != VerbVisibility.Visible;
- var button = new Button
- {
- Text = verb.GetText(user, component),
- Disabled = disabled
- };
- if (!disabled)
- {
- button.OnPressed += _ =>
- {
- _closeContextMenu();
- try
- {
- verb.Activate(user, component);
- }
- catch (Exception e)
- {
- Logger.ErrorS("verb", "Exception in verb {0} on {1}:\n{2}", verb, entity, e);
- }
- };
- }
+ buttons.Add(CreateVerbButton(verb.GetText(user, component), disabled, verb.ToString(),
+ entity.ToString(), () => verb.Activate(user, component)));
+ }
+ //Get global verbs. Visible for all entities regardless of their components.
+ foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
+ {
+ if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity))
+ continue;
- buttons.Add(button);
+ var disabled = globalVerb.GetVisibility(user, entity) != VerbVisibility.Visible;
+ buttons.Add(CreateVerbButton(globalVerb.GetText(user, entity), disabled, globalVerb.ToString(),
+ entity.ToString(), () => globalVerb.Activate(user, entity)));
}
if (buttons.Count > 0)
@@ -224,6 +209,31 @@ namespace Content.Client.GameObjects.EntitySystems
}
}
+ private Button CreateVerbButton(string text, bool disabled, string verbName, string ownerName, Action action)
+ {
+ var button = new Button
+ {
+ Text = text,
+ Disabled = disabled
+ };
+ if (!disabled)
+ {
+ button.OnPressed += _ =>
+ {
+ _closeContextMenu();
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorS("verb", "Exception in verb {0} on {1}:\n{2}", verbName, ownerName, e);
+ }
+ };
+ }
+ return button;
+ }
+
private void _closeContextMenu()
{
_currentPopup?.Dispose();
diff --git a/Content.Client/GlobalVerbs/ViewVariablesVerb.cs b/Content.Client/GlobalVerbs/ViewVariablesVerb.cs
new file mode 100644
index 0000000000..482f615dc9
--- /dev/null
+++ b/Content.Client/GlobalVerbs/ViewVariablesVerb.cs
@@ -0,0 +1,32 @@
+using Content.Shared.GameObjects;
+using Robust.Client.Console;
+using Robust.Client.ViewVariables;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+
+namespace Content.Client.GlobalVerbs
+{
+ ///
+ /// Global verb that opens a view variables window for the entity in question.
+ ///
+ [GlobalVerb]
+ class ViewVariablesVerb : GlobalVerb
+ {
+ public override string GetText(IEntity user, IEntity target) => "View variables";
+ public override bool RequireInteractionRange => false;
+
+ public override VerbVisibility GetVisibility(IEntity user, IEntity target)
+ {
+ var groupController = IoCManager.Resolve();
+ if (groupController.CanViewVar())
+ return VerbVisibility.Visible;
+ return VerbVisibility.Invisible;
+ }
+
+ public override void Activate(IEntity user, IEntity target)
+ {
+ var vvm = IoCManager.Resolve();
+ vvm.OpenVV(target);
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs
index fbf68f1edf..8214cb9a9f 100644
--- a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Reflection;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystemMessages;
using Robust.Server.Interfaces.Player;
@@ -50,20 +51,12 @@ namespace Content.Server.GameObjects.EntitySystems
var userEntity = session.AttachedEntity;
var data = new List();
+ //Get verbs, component dependent.
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
- if (verb.RequireInteractionRange)
- {
- var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition)
- .LengthSquared;
- if (distanceSquared > Verb.InteractionRangeSquared)
- {
- continue;
- }
- }
-
- var vis = verb.GetVisibility(userEntity, component);
- if(vis == VerbVisibility.Invisible)
+ if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
+ continue;
+ if(VerbUtility.IsVerbInvisible(verb, userEntity, component, out var vis))
continue;
// TODO: These keys being giant strings is inefficient as hell.
@@ -71,6 +64,17 @@ namespace Content.Server.GameObjects.EntitySystems
$"{component.GetType()}:{verb.GetType()}",
vis == VerbVisibility.Visible));
}
+ //Get global verbs. Visible for all entities regardless of their components.
+ foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
+ {
+ if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
+ continue;
+ if(VerbUtility.IsVerbInvisible(globalVerb, userEntity, entity, out var vis))
+ continue;
+
+ data.Add(new VerbsResponseMessage.VerbData(globalVerb.GetText(userEntity, entity),
+ globalVerb.GetType().ToString(), vis == VerbVisibility.Visible));
+ }
var response = new VerbsResponseMessage(data, req.EntityUid);
RaiseNetworkEvent(response, channel);
@@ -99,7 +103,7 @@ namespace Content.Server.GameObjects.EntitySystems
{
var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition)
.LengthSquared;
- if (distanceSquared > Verb.InteractionRangeSquared)
+ if (distanceSquared > VerbUtility.InteractionRangeSquared)
{
break;
}
diff --git a/Content.Shared/GameObjects/Verbs/GlobalVerb.cs b/Content.Shared/GameObjects/Verbs/GlobalVerb.cs
new file mode 100644
index 0000000000..a1f683828e
--- /dev/null
+++ b/Content.Shared/GameObjects/Verbs/GlobalVerb.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Robust.Shared.Interfaces.GameObjects;
+
+namespace Content.Shared.GameObjects
+{
+ ///
+ /// A verb is an action in the right click menu of an entity.
+ /// Global verbs are visible on all entities, regardless of their components.
+ ///
+ ///
+ /// To add a global verb to all entities,
+ /// define it and mark it with
+ ///
+ public abstract class GlobalVerb
+ {
+ ///
+ /// If true, this verb requires the user to be within
+ /// meters from the entity on which this verb resides.
+ ///
+ public virtual bool RequireInteractionRange => true;
+
+ ///
+ /// Gets the text string that will be shown to in the right click menu.
+ ///
+ /// The entity of the user opening this menu.
+ /// The text string that is shown in the right click menu for this verb.
+ public abstract string GetText(IEntity user, IEntity target);
+
+ ///
+ /// Gets the visibility level of this verb in the right click menu.
+ ///
+ /// The entity of the user opening this menu.
+ /// The visibility level of the verb in the client's right click menu.
+ public abstract VerbVisibility GetVisibility(IEntity user, IEntity target);
+
+ ///
+ /// Invoked when this verb is activated from the right click menu.
+ ///
+ /// The entity of the user opening this menu.
+ /// The entity that is being acted upon.
+ public abstract void Activate(IEntity user, IEntity target);
+ }
+
+ ///
+ /// This attribute should be used on . These are verbs which are on visible for all entities,
+ /// regardless of the components they contain.
+ ///
+ [MeansImplicitUse]
+ [BaseTypeRequired(typeof(GlobalVerb))]
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GlobalVerbAttribute : Attribute
+ {
+ }
+}
diff --git a/Content.Shared/GameObjects/Verb.cs b/Content.Shared/GameObjects/Verbs/Verb.cs
similarity index 85%
rename from Content.Shared/GameObjects/Verb.cs
rename to Content.Shared/GameObjects/Verbs/Verb.cs
index 18c47e9c46..d4f88ef12b 100644
--- a/Content.Shared/GameObjects/Verb.cs
+++ b/Content.Shared/GameObjects/Verbs/Verb.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
@@ -22,9 +23,6 @@ namespace Content.Shared.GameObjects
///
public virtual bool RequireInteractionRange => true;
- public const float InteractionRange = 2;
- public const float InteractionRangeSquared = InteractionRange * InteractionRange;
-
///
/// Gets the text string that will be shown to in the right click menu.
///
@@ -109,29 +107,6 @@ namespace Content.Shared.GameObjects
{
}
- public static class VerbUtility
- {
- // TODO: This is a quick hack. Verb objects should absolutely be cached properly.
- // This works for now though.
- public static IEnumerable<(IComponent, Verb)> GetVerbs(IEntity entity)
- {
- foreach (var component in entity.GetAllComponents())
- {
- var type = component.GetType();
- foreach (var nestedType in type.GetAllNestedTypes())
- {
- if (!typeof(Verb).IsAssignableFrom(nestedType) || nestedType.IsAbstract)
- {
- continue;
- }
-
- var verb = (Verb) Activator.CreateInstance(nestedType);
- yield return (component, verb);
- }
- }
- }
- }
-
///
/// Possible states of visibility for the verb in the right click menu.
///
diff --git a/Content.Shared/GameObjects/Verbs/VerbUtility.cs b/Content.Shared/GameObjects/Verbs/VerbUtility.cs
new file mode 100644
index 0000000000..aa76c66f77
--- /dev/null
+++ b/Content.Shared/GameObjects/Verbs/VerbUtility.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.GameObjects
+{
+ public static class VerbUtility
+ {
+ public const float InteractionRange = 2;
+ public const float InteractionRangeSquared = InteractionRange * InteractionRange;
+
+ // TODO: This is a quick hack. Verb objects should absolutely be cached properly.
+ // This works for now though.
+ public static IEnumerable<(IComponent, Verb)> GetVerbs(IEntity entity)
+ {
+ foreach (var component in entity.GetAllComponents())
+ {
+ var type = component.GetType();
+ foreach (var nestedType in type.GetAllNestedTypes())
+ {
+ if (!typeof(Verb).IsAssignableFrom(nestedType) || nestedType.IsAbstract)
+ {
+ continue;
+ }
+
+ var verb = (Verb)Activator.CreateInstance(nestedType);
+ yield return (component, verb);
+ }
+ }
+ }
+
+ ///
+ /// Returns an IEnumerable of all classes inheriting with the attribute.
+ ///
+ /// The assembly to search for global verbs in.
+ public static IEnumerable GetGlobalVerbs(Assembly assembly)
+ {
+ foreach (Type type in assembly.GetTypes())
+ {
+ if (Attribute.IsDefined(type, typeof(GlobalVerbAttribute)))
+ {
+ if (!typeof(GlobalVerb).IsAssignableFrom(type) || type.IsAbstract)
+ {
+ continue;
+ }
+ yield return (GlobalVerb)Activator.CreateInstance(type);
+ }
+ }
+ }
+
+ public static bool InVerbUseRange(IEntity user, IEntity target)
+ {
+ var distanceSquared = (user.Transform.WorldPosition - target.Transform.WorldPosition)
+ .LengthSquared;
+ if (distanceSquared > InteractionRangeSquared)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool IsVerbInvisible(Verb verb, IEntity user, IComponent target, out VerbVisibility visibility)
+ {
+ visibility = verb.GetVisibility(user, target);
+ return visibility == VerbVisibility.Invisible;
+ }
+
+ public static bool IsVerbInvisible(GlobalVerb verb, IEntity user, IEntity target, out VerbVisibility visibility)
+ {
+ visibility = verb.GetVisibility(user, target);
+ return visibility == VerbVisibility.Invisible;
+ }
+ }
+}