ECS verbs and update context menu (#4594)
* Functioning ECS verbs Currently only ID card console works. * Changed verb types and allow ID card insertions * Verb GUI sorting and verb networking * More networking, and shared components * Clientside verbs work now. * Verb enums changed to bitmask flags * Verb Categories redo * Fix range check * GasTank Verb * Remove unnecessary bodypart verb * Buckle Verb * buckle & unbuckle verbs * Updated range checks * Item cabinet verbs * Add range user override * construction verb * Chemistry machine verbs * Climb Verb * Generalise pulled entity verbs * ViewVariables Verb * rejuvenate, delete, sentient, control verbs * Outfit verb * inrangeunoccluded and tubedirection verbs * attach-to verbs * remove unused verbs and move VV * Rename DebugVerbSystem * Ghost role and pointing verbs * Remove global verbs * Allow verbs to raise events * Changing categories and simplifying debug verbs * Add rotate and flip verbs * fix rejuvenate test * redo context menu * new Add Gas debug verb * Add Set Temperature debug verb * Uncuff verb * Disposal unit verbs * Add pickup verb * lock/unlock verb * Remove verb type, add specific verb events * rename verb messages -> events * Context menu displays verbs by interaction type * Updated context menu HandleMove previously, checked if entities moved 1 tile from click location. Now checks if entities moved out of view. Now you can actually right-click interact with yourself while walking! * Misc Verb menu GUI changes * Fix non-human/ghost verbs * Update types and categories * Allow non-ghost/human to open context menu * configuration verb * tagger verb * Morgue Verbs * Medical Scanner Verbs * Fix solution refactor merge issues * Fix context menu in-view check * Remove prepare GUI * Redo verb restrictions * Fix context menu UI * Disposal Verbs * Spill verb * Light verb * Hand Held light verb * power cell verbs * storage verbs and adding names to insert/eject * Pulling verb * Close context menu on verb execution * Strip verb * AmmoBox verb * fix pull verb * gun barrel verbs revolver verb energy weapon verbs Bolt action verb * Magazine gun barrel verbs * Add charger verbs * PDA verbs * Transfer amount verb * Add reagent verb * make alt-click use ECS verbs * Delete old verb files * Magboot verb * finalising tweaks * context menu visibility changes * code cleanup * Update AdminAddReagentUI.cs * Remove HasFlag * Consistent verb keys * Remove Linq, add comment * Fix in-inventory check * Update GUI text alignment and padding * Added close-menu option * Changed some "interaction" verbs to "activation" * Remove verb keys, use sorted sets * fix master merge * update some verb text * Undo Changes Remove some new verbs that can be added later undid some .ftl bugfixes, can and should be done separately * fix merge * Undo file rename * fix merge * Misc Cleanup * remove contraction * Fix keybinding issue * fix comment * merge fix * fix merge * fix merge * fix merge * fix merge * fix open-close verbs * adjust uncuff verb * fix merge and undo the renaming of SharedPullableComponent to PullableComponent. I'm tired of all of those merge conflicts
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// A verb is an action in the right click menu of an entity.
|
||||
/// Global verbs are visible on all entities, regardless of their components.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To add a global verb to all entities,
|
||||
/// define it and mark it with <see cref="GlobalVerbAttribute"/>
|
||||
/// </remarks>
|
||||
public abstract class GlobalVerb : VerbBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the visible verb data for the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations should write into <paramref name="data"/> to return their data.
|
||||
/// </remarks>
|
||||
/// <param name="user">The entity of the user opening this menu.</param>
|
||||
/// <param name="target">The entity this verb is being evaluated for.</param>
|
||||
/// <param name="data">The data that must be filled in.</param>
|
||||
/// <returns>The text string that is shown in the right click menu for this verb.</returns>
|
||||
public abstract void GetData(IEntity user, IEntity target, VerbData data);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this verb is activated from the right click menu.
|
||||
/// </summary>
|
||||
/// <param name="user">The entity of the user opening this menu.</param>
|
||||
/// <param name="target">The entity that is being acted upon.</param>
|
||||
public abstract void Activate(IEntity user, IEntity target);
|
||||
|
||||
public VerbData GetData(IEntity user, IEntity target)
|
||||
{
|
||||
var data = new VerbData();
|
||||
GetData(user, target, data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This attribute should be used on <see cref="GlobalVerb"/>. These are verbs which are on visible for all entities,
|
||||
/// regardless of the components they contain.
|
||||
/// </summary>
|
||||
[MeansImplicitUse]
|
||||
[BaseTypeRequired(typeof(GlobalVerb))]
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class GlobalVerbAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class HideContextMenuComponent : Component, IShowContextMenu
|
||||
{
|
||||
public override string Name => "HideContextMenu";
|
||||
|
||||
public bool ShowContextMenu(IEntity examiner)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
public interface IShowContextMenu : IComponent
|
||||
{
|
||||
bool ShowContextMenu(IEntity examiner);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public class PlayerContainerVisibilityMessage : EntityEventArgs
|
||||
{
|
||||
public readonly bool CanSeeThrough;
|
||||
|
||||
public PlayerContainerVisibilityMessage(bool canSeeThrough)
|
||||
{
|
||||
CanSeeThrough = canSeeThrough;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -15,18 +16,20 @@ namespace Content.Shared.Verbs
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Get all of the entities relevant for the contextmenu
|
||||
/// Get all of the entities in an area for displaying on the context menu.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
/// <param name="targetPos"></param>
|
||||
/// <param name="contextEntities"></param>
|
||||
/// <param name="buffer">Whether we should slightly extend out the ignored range for the ray predicated</param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, [NotNullWhen(true)] out List<IEntity>? contextEntities, bool buffer = false)
|
||||
/// <param name="buffer">Whether we should slightly extend the entity search area.</param>
|
||||
public bool TryGetContextEntities(IEntity player, MapCoordinates targetPos,
|
||||
[NotNullWhen(true)] out List<IEntity>? contextEntities, bool buffer = false, bool ignoreVisibility = false)
|
||||
{
|
||||
contextEntities = null;
|
||||
var length = buffer ? 1.0f: 0.5f;
|
||||
|
||||
// Check if we have LOS to the clicked-location.
|
||||
if (!ignoreVisibility && !player.InRangeUnOccluded(targetPos, range: ExamineSystemShared.ExamineRange))
|
||||
return false;
|
||||
|
||||
// Get entities
|
||||
var length = buffer ? 1.0f : 0.5f;
|
||||
var entities = _lookup.GetEntitiesIntersecting(
|
||||
targetPos.MapId,
|
||||
Box2.CenteredAround(targetPos.Position, (length, length)))
|
||||
@@ -34,34 +37,113 @@ namespace Content.Shared.Verbs
|
||||
|
||||
if (entities.Count == 0) return false;
|
||||
|
||||
// TODO: Can probably do a faster distance check with EntityCoordinates given we don't need to get map stuff.
|
||||
if (ignoreVisibility)
|
||||
{
|
||||
contextEntities = entities;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if we have LOS to the clicked-location, otherwise no popup.
|
||||
// perform visibility checks
|
||||
var playerPos = player.Transform.MapPosition;
|
||||
var vectorDiff = playerPos.Position - targetPos.Position;
|
||||
var distance = vectorDiff.Length + 0.01f;
|
||||
|
||||
bool Ignored(IEntity entity)
|
||||
foreach (var entity in entities.ToList())
|
||||
{
|
||||
return entities.Contains(entity) ||
|
||||
entity == player ||
|
||||
!entity.TryGetComponent(out OccluderComponent? occluder) ||
|
||||
!occluder.Enabled;
|
||||
if (entity.HasTag("HideContextMenu"))
|
||||
{
|
||||
entities.Remove(entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ExamineSystemShared.InRangeUnOccluded(
|
||||
playerPos,
|
||||
entity.Transform.MapPosition,
|
||||
ExamineSystemShared.ExamineRange,
|
||||
null) )
|
||||
{
|
||||
entities.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
var mask = player.TryGetComponent(out SharedEyeComponent? eye) && eye.DrawFov
|
||||
? CollisionGroup.Opaque
|
||||
: CollisionGroup.None;
|
||||
|
||||
var result = Get<SharedInteractionSystem>().InRangeUnobstructed(playerPos, targetPos, distance, mask, Ignored);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
if (entities.Count == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
contextEntities = entities;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a number of events in order to get all verbs of the given type(s)
|
||||
/// </summary>
|
||||
public Dictionary<VerbType, SortedSet<Verb>> GetVerbs(IEntity target, IEntity user, VerbType verbTypes)
|
||||
{
|
||||
Dictionary<VerbType, SortedSet<Verb>> verbs = new();
|
||||
|
||||
if ((verbTypes & VerbType.Interaction) == VerbType.Interaction)
|
||||
{
|
||||
GetInteractionVerbsEvent getVerbEvent = new(user, target);
|
||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||
verbs.Add(VerbType.Interaction, getVerbEvent.Verbs);
|
||||
}
|
||||
|
||||
if ((verbTypes & VerbType.Activation) == VerbType.Activation)
|
||||
{
|
||||
GetActivationVerbsEvent getVerbEvent = new(user, target);
|
||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||
verbs.Add(VerbType.Activation, getVerbEvent.Verbs);
|
||||
}
|
||||
|
||||
if ((verbTypes & VerbType.Alternative) == VerbType.Alternative)
|
||||
{
|
||||
GetAlternativeVerbsEvent getVerbEvent = new(user, target);
|
||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||
verbs.Add(VerbType.Alternative, getVerbEvent.Verbs);
|
||||
}
|
||||
|
||||
if ((verbTypes & VerbType.Other) == VerbType.Other)
|
||||
{
|
||||
GetOtherVerbsEvent getVerbEvent = new(user, target);
|
||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||
verbs.Add(VerbType.Other, getVerbEvent.Verbs);
|
||||
}
|
||||
|
||||
return verbs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute actions associated with the given verb.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will try to call delegates and raise any events for the given verb.
|
||||
/// </remarks>
|
||||
public bool TryExecuteVerb(Verb verb)
|
||||
{
|
||||
var executed = false;
|
||||
|
||||
// Maybe run a delegate
|
||||
if (verb.Act != null)
|
||||
{
|
||||
executed = true;
|
||||
verb.Act.Invoke();
|
||||
}
|
||||
|
||||
// Maybe raise a local event
|
||||
if (verb.LocalVerbEventArgs != null)
|
||||
{
|
||||
executed = true;
|
||||
if (verb.LocalEventTarget.IsValid())
|
||||
RaiseLocalEvent(verb.LocalEventTarget, verb.LocalVerbEventArgs);
|
||||
else
|
||||
RaiseLocalEvent(verb.LocalVerbEventArgs);
|
||||
}
|
||||
|
||||
// maybe raise a network event
|
||||
if (verb.NetworkVerbEventArgs != null)
|
||||
{
|
||||
executed = true;
|
||||
RaiseNetworkEvent(verb.NetworkVerbEventArgs);
|
||||
}
|
||||
|
||||
// return false if all of these were null
|
||||
return executed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +1,166 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// A verb is an action in the right click menu of an entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To add a verb to an entity, define it as a nested class inside the owning component,
|
||||
/// and mark it with <see cref="VerbAttribute"/>
|
||||
/// </remarks>
|
||||
[UsedImplicitly]
|
||||
public abstract class Verb : VerbBase
|
||||
[Flags]
|
||||
public enum VerbType
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the visible verb data for the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations should write into <paramref name="data"/> to return their data.
|
||||
/// </remarks>
|
||||
/// <param name="user">The entity of the user opening this menu.</param>
|
||||
/// <param name="component">The component instance for which this verb is being loaded.</param>
|
||||
/// <param name="data">The data that must be filled into.</param>
|
||||
protected abstract void GetData(IEntity user, IComponent component, VerbData data);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this verb is activated from the right click menu.
|
||||
/// </summary>
|
||||
/// <param name="user">The entity of the user opening this menu.</param>
|
||||
/// <param name="component">The component instance for which this verb is being loaded.</param>
|
||||
public abstract void Activate(IEntity user, IComponent component);
|
||||
|
||||
public VerbData GetData(IEntity user, IComponent component)
|
||||
{
|
||||
var data = new VerbData();
|
||||
GetData(user, component, data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Sub class of <see cref="T:Content.Shared.Verbs.Verb" /> that works on a specific type of component,
|
||||
/// to reduce casting boiler plate for implementations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of component that this verb will run on.</typeparam>
|
||||
public abstract class Verb<T> : Verb where T : IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the visible verb data for the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations should write into <paramref name="data"/> to return their data.
|
||||
/// </remarks>
|
||||
/// <param name="user">The entity of the user opening this menu.</param>
|
||||
/// <param name="component">The component instance for which this verb is being loaded.</param>
|
||||
/// <param name="data">The data that must be filled into.</param>
|
||||
protected abstract void GetData(IEntity user, T component, VerbData data);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this verb is activated from the right click menu.
|
||||
/// </summary>
|
||||
/// <param name="user">The entity of the user opening this menu.</param>
|
||||
/// <param name="component">The component instance for which this verb is being loaded.</param>
|
||||
protected abstract void Activate(IEntity user, T component);
|
||||
|
||||
protected sealed override void GetData(IEntity user, IComponent component, VerbData data)
|
||||
{
|
||||
GetData(user, (T) component, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override void Activate(IEntity user, IComponent component)
|
||||
{
|
||||
Activate(user, (T) component);
|
||||
}
|
||||
Interaction = 1,
|
||||
Activation = 2,
|
||||
Alternative = 4,
|
||||
Other = 8,
|
||||
All = 1+2+4+8
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This attribute should be used on <see cref="Verb"/> implementations nested inside component classes,
|
||||
/// so that they're automatically detected.
|
||||
/// Verb objects describe actions that a user can take. The actions can be specified via an Action, local
|
||||
/// events, or networked events. Verbs also provide text, icons, and categories for displaying in the
|
||||
/// context-menu.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class VerbAttribute : Attribute
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class Verb : IComparable
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an action that will be run when the verb is "acted" out.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This delegate probably just points to some function in the system assembling this verb. This delegate
|
||||
/// will be run regardless of whether <see cref="LocalVerbEventArgs"/> or <see cref="NetworkVerbEventArgs"/>
|
||||
/// are defined.
|
||||
/// </remarks>
|
||||
[NonSerialized]
|
||||
public Action? Act;
|
||||
|
||||
/// <summary>
|
||||
/// This is local event that will be raised when the verb is executed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event will be raised regardless of whether <see cref="NetworkVerbEventArgs"/> or <see cref="Act"/>
|
||||
/// are defined.
|
||||
/// </remarks>
|
||||
[NonSerialized]
|
||||
public object? LocalVerbEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Where do direct the local event.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public EntityUid LocalEventTarget = EntityUid.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// This is networked event that will be raised when the verb is executed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event will be raised regardless of whether <see cref="LocalVerbEventArgs"/> or <see cref="Act"/>
|
||||
/// are defined.
|
||||
/// </remarks>
|
||||
[NonSerialized]
|
||||
public EntityEventArgs? NetworkVerbEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// The text that the user sees on the verb button.
|
||||
/// </summary>
|
||||
public string Text = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Sprite of the icon that the user sees on the verb button.
|
||||
/// </summary>
|
||||
public SpriteSpecifier? Icon
|
||||
{
|
||||
get => _icon ??=
|
||||
IconTexture == null ? null : new SpriteSpecifier.Texture(new ResourcePath(IconTexture));
|
||||
set => _icon = value;
|
||||
}
|
||||
private SpriteSpecifier? _icon;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the category this button is under. Used to group verbs in the context menu.
|
||||
/// </summary>
|
||||
public VerbCategory? Category;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this verb is disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Disabled verbs are shown in the context menu with a slightly darker background color, and cannot be
|
||||
/// executed. It is recommended that a <see cref="Tooltip"/> message be provided outlining why this verb is
|
||||
/// disabled.
|
||||
/// </remarks>
|
||||
public bool Disabled;
|
||||
|
||||
/// <summary>
|
||||
/// Optional tooltip to show when hovering over this verb.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful for disabled verbs as a replacement for informative pop-up messages.
|
||||
/// </remarks>
|
||||
public string? Tooltip;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the priority of the verb. This affects both how the verb is displayed in the context menu
|
||||
/// GUI, and which verb is actually executed when left/alt clicking.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Bigger is higher priority (appears first, gets executed preferentially).
|
||||
/// </remarks>
|
||||
public int Priority;
|
||||
|
||||
/// <summary>
|
||||
/// Raw texture path used to load the <see cref="Icon"/>.
|
||||
/// </summary>
|
||||
public string? IconTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to close the context menu after using it to run this verb.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to false may be useful for repeatable actions, like rotating an object or maybe knocking on
|
||||
/// a window.
|
||||
/// </remarks>
|
||||
public bool CloseMenu = true;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two verbs based on their <see cref="Priority"/>, <see cref="Category"/>, <see cref="Text"/>,
|
||||
/// and <see cref="IconTexture"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is comparison is used when storing verbs in a SortedSet. The ordering of verbs determines both how
|
||||
/// the verbs are displayed in the context menu, and the order in which alternative action verbs are
|
||||
/// executed when alt-clicking.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If two verbs are equal according to this comparison, they cannot both be added to the same sorted set of
|
||||
/// verbs. This is desirable, given that these verbs would also appear identical in the context menu.
|
||||
/// Distinct verbs should always have a unique and descriptive combination of text, icon, and category.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is not Verb otherVerb)
|
||||
return -1;
|
||||
|
||||
// Sort first by priority
|
||||
if (Priority != otherVerb.Priority)
|
||||
return otherVerb.Priority - Priority;
|
||||
|
||||
// Then try use alphabetical verb categories. Uncategorized verbs always appear first.
|
||||
if (Category?.Text != otherVerb.Category?.Text)
|
||||
{
|
||||
return string.Compare(Category?.Text, otherVerb.Category?.Text, StringComparison.CurrentCulture);
|
||||
}
|
||||
|
||||
// Then try use alphabetical verb text.
|
||||
if (Text != otherVerb.Text)
|
||||
{
|
||||
return string.Compare(Text, otherVerb.Text, StringComparison.CurrentCulture);
|
||||
}
|
||||
|
||||
// Finally, compare icon texture paths. Note that this matters for verbs that don't have any text (e.g., the rotate-verbs)
|
||||
return string.Compare(IconTexture, otherVerb.IconTexture, StringComparison.CurrentCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
public abstract class VerbBase
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, this verb requires the user to be inside within
|
||||
/// <see cref="VerbUtility.InteractionRange"/> meters from the entity on which this verb resides.
|
||||
/// </summary>
|
||||
public virtual bool RequireInteractionRange => true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this verb requires both the user and the entity on which
|
||||
/// this verb resides to be in the same container or no container.
|
||||
/// OR the user can be the entity's container
|
||||
/// </summary>
|
||||
public virtual bool BlockedByContainers => true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this verb can be activated by alt-clicking on the entity.
|
||||
/// </summary>
|
||||
public virtual bool AlternativeInteraction => false;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard verb categories.
|
||||
/// </summary>
|
||||
public static class VerbCategories
|
||||
{
|
||||
public static readonly VerbCategoryData Debug =
|
||||
("Debug", "/Textures/Interface/VerbIcons/debug.svg.192dpi.png");
|
||||
|
||||
public static readonly VerbCategoryData Rotate = ("Rotate", null);
|
||||
public static readonly VerbCategoryData Construction =
|
||||
("Construction", "/Textures/Interface/hammer_scaled.svg.192dpi.png");
|
||||
public static readonly VerbCategoryData SetTransferAmount =
|
||||
("Set Transfer Amount", "/Textures/Interface/VerbIcons/spill.svg.192dpi.png");
|
||||
}
|
||||
}
|
||||
54
Content.Shared/Verbs/VerbCategory.cs
Normal file
54
Content.Shared/Verbs/VerbCategory.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains combined name and icon information for a verb category.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class VerbCategory
|
||||
{
|
||||
public readonly string Text;
|
||||
|
||||
public readonly SpriteSpecifier? Icon;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this verb category is shown in the context menu as a row of icons without any text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, the 'Rotate' category simply shows two icons for rotating left and right.
|
||||
/// </remarks>
|
||||
public readonly bool IconsOnly;
|
||||
|
||||
public VerbCategory(string text, string? icon, bool iconsOnly = false)
|
||||
{
|
||||
Text = Loc.GetString(text);
|
||||
Icon = icon == null ? null : new SpriteSpecifier.Texture(new ResourcePath(icon));
|
||||
IconsOnly = iconsOnly;
|
||||
}
|
||||
|
||||
public static readonly VerbCategory Debug =
|
||||
new("verb-categories-debug", "/Textures/Interface/VerbIcons/debug.svg.192dpi.png");
|
||||
|
||||
public static readonly VerbCategory Eject =
|
||||
new("verb-categories-eject", "/Textures/Interface/VerbIcons/eject.svg.192dpi.png");
|
||||
|
||||
public static readonly VerbCategory Insert =
|
||||
new("verb-categories-insert", "/Textures/Interface/VerbIcons/insert.svg.192dpi.png");
|
||||
|
||||
public static readonly VerbCategory Buckle =
|
||||
new("verb-categories-buckle", "/Textures/Interface/VerbIcons/buckle.svg.192dpi.png");
|
||||
|
||||
public static readonly VerbCategory Unbuckle =
|
||||
new("verb-categories-unbuckle", "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png");
|
||||
|
||||
public static readonly VerbCategory Rotate =
|
||||
new("verb-categories-rotate", "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png", iconsOnly: true);
|
||||
|
||||
public static readonly VerbCategory SetTransferAmount =
|
||||
new("verb-categories-transfer", "/Textures/Interface/VerbIcons/spill.svg.192dpi.png");
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains combined name and icon information for a verb category.
|
||||
/// </summary>
|
||||
public readonly struct VerbCategoryData
|
||||
{
|
||||
public VerbCategoryData(string name, SpriteSpecifier? icon)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public SpriteSpecifier? Icon { get; }
|
||||
|
||||
public static implicit operator VerbCategoryData((string name, string? icon) tuple)
|
||||
{
|
||||
var (name, icon) = tuple;
|
||||
|
||||
if (icon == null)
|
||||
{
|
||||
return new VerbCategoryData(name, null);
|
||||
}
|
||||
|
||||
return new VerbCategoryData(name, new SpriteSpecifier.Texture(new ResourcePath(icon)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores visual data for a verb.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An instance of this class gets instantiated by the verb system and should be filled in by implementations of
|
||||
/// <see cref="Verb.GetData(IEntity, IComponent, VerbData)"/>.
|
||||
/// </remarks>
|
||||
public sealed class VerbData
|
||||
{
|
||||
/// <summary>
|
||||
/// The text that the user sees on the verb button.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This string is automatically passed through Loc.GetString().
|
||||
/// </remarks>
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Sprite of the icon that the user sees on the verb button.
|
||||
/// </summary>
|
||||
public SpriteSpecifier? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the category this button is under.
|
||||
/// </summary>
|
||||
public string Category { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Sprite of the icon that the user sees on the verb button.
|
||||
/// </summary>
|
||||
public SpriteSpecifier? CategoryIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this verb is visible, disabled (greyed out) or hidden.
|
||||
/// </summary>
|
||||
public VerbVisibility Visibility { get; set; } = VerbVisibility.Visible;
|
||||
|
||||
public bool IsInvisible => Visibility == VerbVisibility.Invisible;
|
||||
public bool IsDisabled => Visibility == VerbVisibility.Disabled;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property to set verb category and icon at once.
|
||||
/// </summary>
|
||||
[ValueProvider("Content.Shared.GameObjects.VerbCategories")]
|
||||
public VerbCategoryData CategoryData
|
||||
{
|
||||
set
|
||||
{
|
||||
Category = value.Name;
|
||||
CategoryIcon = value.Icon;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property to set <see cref="Icon"/> to a raw texture path.
|
||||
/// </summary>
|
||||
public string IconTexture
|
||||
{
|
||||
set => Icon = new SpriteSpecifier.Texture(new ResourcePath(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
211
Content.Shared/Verbs/VerbEvents.cs
Normal file
211
Content.Shared/Verbs/VerbEvents.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using Content.Shared.Interaction;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public class RequestServerVerbsEvent : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid EntityUid;
|
||||
public readonly VerbType Type;
|
||||
|
||||
public RequestServerVerbsEvent(EntityUid entityUid, VerbType type)
|
||||
{
|
||||
EntityUid = entityUid;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class VerbsResponseEvent : EntityEventArgs
|
||||
{
|
||||
public readonly Dictionary<VerbType, List<Verb>>? Verbs;
|
||||
public readonly EntityUid Entity;
|
||||
|
||||
public VerbsResponseEvent(EntityUid entity, Dictionary<VerbType, SortedSet<Verb>>? verbs)
|
||||
{
|
||||
Entity = entity;
|
||||
|
||||
if (verbs == null)
|
||||
return;
|
||||
|
||||
// Apparently SortedSet is not serlializable. Cast to List<Verb>.
|
||||
Verbs = new();
|
||||
foreach (var entry in verbs)
|
||||
{
|
||||
Verbs.Add(entry.Key, new List<Verb>(entry.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class TryExecuteVerbEvent : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid Target;
|
||||
public readonly Verb RequestedVerb;
|
||||
|
||||
/// <summary>
|
||||
/// The type of verb to try execute. Avoids having to get a list of all verbs on the receiving end.
|
||||
/// </summary>
|
||||
public readonly VerbType Type;
|
||||
|
||||
public TryExecuteVerbEvent(EntityUid target, Verb requestedVerb, VerbType type)
|
||||
{
|
||||
Target = target;
|
||||
RequestedVerb = requestedVerb;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event used to toggle visibility of all context menu entities.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class SetSeeAllContextEvent : EntityEventArgs
|
||||
{
|
||||
public bool CanSeeAllContext = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request primary interaction verbs. This includes both use-in-hand and interacting with external entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These verbs those that involve using the hands or the currently held item on some entity. These verbs usually
|
||||
/// correspond to interactions that can be triggered by left-clicking or using 'Z', and often depend on the
|
||||
/// currently held item. These verbs are collectively shown first in the context menu.
|
||||
/// </remarks>
|
||||
public class GetInteractionVerbsEvent : GetVerbsEvent
|
||||
{
|
||||
public GetInteractionVerbsEvent(IEntity user, IEntity target) : base(user, target) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request activation verbs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These are verbs that activate an item in the world but are independent of the currently held items. For
|
||||
/// example, opening a door or a GUI. These verbs should correspond to interactions that can be triggered by
|
||||
/// using 'E', though many of those can also be triggered by left-mouse or 'Z' if there is no other interaction.
|
||||
/// These verbs are collectively shown second in the context menu.
|
||||
/// </remarks>
|
||||
public class GetActivationVerbsEvent : GetVerbsEvent
|
||||
{
|
||||
public GetActivationVerbsEvent(IEntity user, IEntity target) : base(user, target) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request alternative-interaction verbs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When interacting with an entity via alt + left-click/E/Z the highest priority alt-interact verb is executed.
|
||||
/// These verbs are collectively shown second-to-last in the context menu.
|
||||
/// </remarks>
|
||||
public class GetAlternativeVerbsEvent : GetVerbsEvent
|
||||
{
|
||||
public GetAlternativeVerbsEvent(IEntity user, IEntity target) : base(user, target) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request Miscellaneous verbs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Includes (nearly) global interactions like "examine", "pull", or "debug". These verbs are collectively shown
|
||||
/// last in the context menu.
|
||||
/// </remarks>
|
||||
public class GetOtherVerbsEvent : GetVerbsEvent
|
||||
{
|
||||
public GetOtherVerbsEvent(IEntity user, IEntity target) : base(user, target) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directed event that requests verbs from any systems/components on a target entity.
|
||||
/// </summary>
|
||||
public class GetVerbsEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Event output. Set of verbs that can be executed.
|
||||
/// </summary>
|
||||
public SortedSet<Verb> Verbs = new();
|
||||
|
||||
/// <summary>
|
||||
/// Can the user physically access the target?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a combination of <see cref="ContainerHelpers.IsInSameOrParentContainer"/> and
|
||||
/// <see cref="SharedInteractionSystem.InRangeUnobstructed"/>.
|
||||
/// </remarks>
|
||||
public bool CanAccess;
|
||||
|
||||
/// <summary>
|
||||
/// The entity being targeted for the verb.
|
||||
/// </summary>
|
||||
public IEntity Target;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that will be "performing" the verb.
|
||||
/// </summary>
|
||||
public IEntity User;
|
||||
|
||||
/// <summary>
|
||||
/// Can the user physically interact?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a just a cached <see cref="ActionBlockerSystem.CanInteract"/> result. Given that many verbs need
|
||||
/// to check this, it prevents it from having to be repeatedly called by each individual system that might
|
||||
/// contribute a verb.
|
||||
/// </remarks>
|
||||
public bool CanInteract;
|
||||
|
||||
/// <summary>
|
||||
/// The User's hand component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may be null if the user has no hands.
|
||||
/// </remarks>
|
||||
public SharedHandsComponent? Hands;
|
||||
|
||||
/// <summary>
|
||||
/// The entity currently being held by the active hand.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only ever not null when <see cref="ActionBlockerSystem.CanUse(IEntity)"/> is true and the user
|
||||
/// has hands.
|
||||
/// </remarks>
|
||||
public IEntity? Using;
|
||||
|
||||
public GetVerbsEvent(IEntity user, IEntity target)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
|
||||
CanAccess = (Target == User) || user.IsInSameOrParentContainer(target) &&
|
||||
EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(user, target);
|
||||
|
||||
// A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
|
||||
// call ActionBlocker checks, just cache it for the verb request.
|
||||
var actionBlockerSystem = EntitySystem.Get<ActionBlockerSystem>();
|
||||
CanInteract = actionBlockerSystem.CanInteract(user);
|
||||
|
||||
if (!user.TryGetComponent(out Hands) ||
|
||||
!actionBlockerSystem.CanUse(user))
|
||||
return;
|
||||
|
||||
Hands.TryGetActiveHeldEntity(out Using);
|
||||
|
||||
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
|
||||
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
|
||||
// their sprite.
|
||||
if (Using != null && Using.TryGetComponent<HandVirtualItemComponent>(out var pull))
|
||||
{
|
||||
Using = IoCManager.Resolve<IEntityManager>().GetEntity(pull.BlockingEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
public static class VerbSystemMessages
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public class RequestVerbsMessage : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid EntityUid;
|
||||
|
||||
public RequestVerbsMessage(EntityUid entityUid)
|
||||
{
|
||||
EntityUid = entityUid;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class VerbsResponseMessage : EntityEventArgs
|
||||
{
|
||||
public readonly NetVerbData[] Verbs;
|
||||
public readonly EntityUid Entity;
|
||||
|
||||
public VerbsResponseMessage(NetVerbData[] verbs, EntityUid entity)
|
||||
{
|
||||
Verbs = verbs;
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct NetVerbData
|
||||
{
|
||||
public readonly string Text;
|
||||
public readonly string Key;
|
||||
public readonly string Category;
|
||||
public readonly SpriteSpecifier? Icon;
|
||||
public readonly SpriteSpecifier? CategoryIcon;
|
||||
public readonly bool Available;
|
||||
|
||||
public NetVerbData(VerbData data, string key)
|
||||
{
|
||||
Text = data.Text;
|
||||
Key = key;
|
||||
Category = data.Category;
|
||||
CategoryIcon = data.CategoryIcon;
|
||||
Icon = data.Icon;
|
||||
Available = data.Visibility == VerbVisibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class UseVerbMessage : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid EntityUid;
|
||||
public readonly string VerbKey;
|
||||
|
||||
public UseVerbMessage(EntityUid entityUid, string verbKey)
|
||||
{
|
||||
EntityUid = entityUid;
|
||||
VerbKey = verbKey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
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)
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactory>();
|
||||
|
||||
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 = typeFactory.CreateInstance<Verb>(nestedType);
|
||||
yield return (component, verb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of all classes inheriting <see cref="GlobalVerb"/> with the <see cref="GlobalVerbAttribute"/> attribute.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to search for global verbs in.</param>
|
||||
public static IEnumerable<GlobalVerb> GetGlobalVerbs(Assembly assembly)
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactory>();
|
||||
|
||||
foreach (Type type in assembly.GetTypes())
|
||||
{
|
||||
if (Attribute.IsDefined(type, typeof(GlobalVerbAttribute)))
|
||||
{
|
||||
if (!typeof(GlobalVerb).IsAssignableFrom(type) || type.IsAbstract)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
yield return typeFactory.CreateInstance<GlobalVerb>(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerbAccessChecks(IEntity user, IEntity target, VerbBase verb)
|
||||
{
|
||||
if (verb.RequireInteractionRange && !InVerbUseRange(user, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (verb.BlockedByContainers && !VerbContainerCheck(user, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 VerbContainerCheck(IEntity user, IEntity target)
|
||||
{
|
||||
if (!user.IsInSameOrNoContainer(target))
|
||||
{
|
||||
if (!target.TryGetContainer(out var container) ||
|
||||
container.Owner != user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
namespace Content.Shared.Verbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible states of visibility for the verb in the right click menu.
|
||||
/// </summary>
|
||||
public enum VerbVisibility
|
||||
{
|
||||
/// <summary>
|
||||
/// The verb will be listed in the right click menu.
|
||||
/// </summary>
|
||||
Visible,
|
||||
|
||||
/// <summary>
|
||||
/// The verb will be listed, but it will be grayed out and unable to be clicked on.
|
||||
/// </summary>
|
||||
Disabled,
|
||||
|
||||
/// <summary>
|
||||
/// The verb will not be listed in the right click menu.
|
||||
/// </summary>
|
||||
Invisible
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user