Re-add action prototypes (#7508)
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
This commit is contained in:
@@ -1,21 +1,44 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
public sealed class GetActionsEvent : EntityEventArgs
|
||||
/// <summary>
|
||||
/// Event raised directed at items or clothing when they are equipped or held. In order for an item to grant actions some
|
||||
/// system can subscribe to this event and add actions to the <see cref="Actions"/> list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that a system could also just manually add actions as a result of a <see cref="GotEquippedEvent"/> or <see
|
||||
/// cref="GotEquippedHandEvent"/>. This exists mostly as a convenience event, while also helping to keep
|
||||
/// action-granting logic separate from general equipment behavior.
|
||||
/// </remarks>
|
||||
public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
{
|
||||
public SortedSet<ActionType> Actions = new();
|
||||
|
||||
/// <summary>
|
||||
/// Slot flags for the inventory slot that this item got equipped to. Null if not in a slot (i.e., if equipped to hands).
|
||||
/// </summary>
|
||||
public SlotFlags? SlotFlags;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the item was equipped to a users hands.
|
||||
/// </summary>
|
||||
public bool InHands => SlotFlags == null;
|
||||
|
||||
public GetItemActionsEvent(SlotFlags? slotFlags = null)
|
||||
{
|
||||
SlotFlags = slotFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event used to communicate with the client that the user wishes to perform some action.
|
||||
/// Event used to communicate with the server that a client wishes to perform some action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Basically a wrapper for <see cref="PerformActionEvent"/> that the action system will validate before performing
|
||||
/// (check cooldown, target, enabling-entity)
|
||||
/// </remarks>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RequestPerformActionEvent : EntityEventArgs
|
||||
{
|
||||
@@ -41,21 +64,56 @@ public sealed class RequestPerformActionEvent : EntityEventArgs
|
||||
}
|
||||
}
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public abstract class PerformActionEvent : HandledEntityEventArgs
|
||||
/// <summary>
|
||||
/// This is the type of event that gets raised when an <see cref="InstantAction"/> is performed. The <see
|
||||
/// cref="Performer"/> field is automatically filled out by the <see cref="SharedActionsSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To define a new action for some system, you need to create an event that inherits from this class.
|
||||
/// </remarks>
|
||||
public abstract class InstantActionEvent : BaseActionEvent { }
|
||||
|
||||
/// <summary>
|
||||
/// This is the type of event that gets raised when an <see cref="EntityTargetAction"/> is performed. The <see
|
||||
/// cref="Performer"/> and <see cref="Target"/> fields will automatically be filled out by the <see
|
||||
/// cref="SharedActionsSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To define a new action for some system, you need to create an event that inherits from this class.
|
||||
/// </remarks>
|
||||
public abstract class EntityTargetActionEvent : BaseActionEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The user performing the action
|
||||
/// The entity that the user targeted.
|
||||
/// </summary>
|
||||
public EntityUid Performer;
|
||||
}
|
||||
|
||||
public abstract class PerformEntityTargetActionEvent : PerformActionEvent
|
||||
{
|
||||
public EntityUid Target;
|
||||
}
|
||||
|
||||
public abstract class PerformWorldTargetActionEvent : PerformActionEvent
|
||||
/// <summary>
|
||||
/// This is the type of event that gets raised when an <see cref="WorldTargetAction"/> is performed. The <see
|
||||
/// cref="Performer"/> and <see cref="Target"/> fields will automatically be filled out by the <see
|
||||
/// cref="SharedActionsSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To define a new action for some system, you need to create an event that inherits from this class.
|
||||
/// </remarks>
|
||||
public abstract class WorldTargetActionEvent : BaseActionEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The coordinates of the location that the user targeted.
|
||||
/// </summary>
|
||||
public MapCoordinates Target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for events that are raised when an action gets performed. This should not generally be used outside of the action
|
||||
/// system.
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public abstract class BaseActionEvent : HandledEntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The user performing the action.
|
||||
/// </summary>
|
||||
public EntityUid Performer;
|
||||
}
|
||||
|
||||
56
Content.Shared/Actions/ActionTypes/ActionPrototypes.cs
Normal file
56
Content.Shared/Actions/ActionTypes/ActionPrototypes.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Actions.ActionTypes;
|
||||
|
||||
// These are just prototype definitions for actions. Allows actions to be defined once in yaml and re-used elsewhere.
|
||||
// Note that you still need to create a new instance of each action to properly track the state (cooldown, toggled,
|
||||
// enabled, etc). The prototypes should not be modified directly.
|
||||
//
|
||||
// If ever action states data is separated from the rest of the data, this might not be required
|
||||
// anymore.
|
||||
|
||||
[Prototype("worldTargetAction")]
|
||||
public sealed class WorldTargetActionPrototype : WorldTargetAction, IPrototype
|
||||
{
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
// This is a shitty hack to get around the fact that action-prototypes should not in general be sever-exclusive
|
||||
// prototypes, but some actions may need to use server-exclusive events, and there is no way to specify on a
|
||||
// per-prototype basis whether the client should ignore it when validating yaml.
|
||||
[DataField("serverEvent", serverOnly: true)]
|
||||
public WorldTargetActionEvent? ServerEvent
|
||||
{
|
||||
get => Event;
|
||||
set => Event = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Prototype("entityTargetAction")]
|
||||
public sealed class EntityTargetActionPrototype : EntityTargetAction, IPrototype
|
||||
{
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("serverEvent", serverOnly: true)]
|
||||
public EntityTargetActionEvent? ServerEvent
|
||||
{
|
||||
get => Event;
|
||||
set => Event = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Prototype("instantAction")]
|
||||
public sealed class InstantActionPrototype : InstantAction, IPrototype
|
||||
{
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("serverEvent", serverOnly: true)]
|
||||
public InstantActionEvent? ServerEvent
|
||||
{
|
||||
get => Event;
|
||||
set => Event = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Shared.Actions.ActionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Instantaneous action with no extra targeting information. Will result in <see cref="PerformActionEvent"/> being raised.
|
||||
/// Instantaneous action with no extra targeting information. Will result in <see cref="InstantActionEvent"/> being raised.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Friend(typeof(SharedActionsSystem))]
|
||||
[Virtual]
|
||||
public class InstantAction : ActionType
|
||||
{
|
||||
@@ -15,7 +14,7 @@ public class InstantAction : ActionType
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public PerformActionEvent? Event;
|
||||
public InstantActionEvent? Event;
|
||||
|
||||
public InstantAction() { }
|
||||
public InstantAction(InstantAction toClone)
|
||||
|
||||
@@ -67,10 +67,9 @@ public abstract class TargetedAction : ActionType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that targets some entity. Will result in <see cref="PerformEntityTargetActionEvent"/> being raised.
|
||||
/// Action that targets some entity. Will result in <see cref="EntityTargetActionEvent"/> being raised.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Friend(typeof(SharedActionsSystem))]
|
||||
[Virtual]
|
||||
public class EntityTargetAction : TargetedAction
|
||||
{
|
||||
@@ -79,7 +78,7 @@ public class EntityTargetAction : TargetedAction
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
[DataField("event")]
|
||||
public PerformEntityTargetActionEvent? Event;
|
||||
public EntityTargetActionEvent? Event;
|
||||
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
@@ -115,10 +114,9 @@ public class EntityTargetAction : TargetedAction
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that targets some map coordinates. Will result in <see cref="PerformWorldTargetActionEvent"/> being raised.
|
||||
/// Action that targets some map coordinates. Will result in <see cref="WorldTargetActionEvent"/> being raised.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Friend(typeof(SharedActionsSystem))]
|
||||
[Virtual]
|
||||
public class WorldTargetAction : TargetedAction
|
||||
{
|
||||
@@ -127,7 +125,7 @@ public class WorldTargetAction : TargetedAction
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public PerformWorldTargetActionEvent? Event;
|
||||
public WorldTargetActionEvent? Event;
|
||||
|
||||
public WorldTargetAction() { }
|
||||
public WorldTargetAction(WorldTargetAction toClone)
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
|
||||
@@ -99,7 +100,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
#region Execution
|
||||
/// <summary>
|
||||
/// When receiving a request to perform an action, this validates whether the action is allowed. If it is, it
|
||||
/// will raise the relevant <see cref="PerformActionEvent"/>
|
||||
/// will raise the relevant <see cref="InstantActionEvent"/>
|
||||
/// </summary>
|
||||
private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArgs args)
|
||||
{
|
||||
@@ -124,7 +125,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (act.Cooldown.HasValue && act.Cooldown.Value.End > curTime)
|
||||
return;
|
||||
|
||||
PerformActionEvent? performEvent = null;
|
||||
BaseActionEvent? performEvent = null;
|
||||
|
||||
// Validate request by checking action blockers and the like:
|
||||
var name = Loc.GetString(act.Name);
|
||||
@@ -273,7 +274,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
|
||||
}
|
||||
|
||||
protected void PerformAction(ActionsComponent component, ActionType action, PerformActionEvent? actionEvent, TimeSpan curTime)
|
||||
protected void PerformAction(ActionsComponent component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime)
|
||||
{
|
||||
var handled = false;
|
||||
|
||||
@@ -356,6 +357,13 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
||||
public virtual void AddAction(EntityUid uid, ActionType action, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
{
|
||||
// Because action classes have state data, e.g. cooldowns and uses-remaining, people should not be adding prototypes directly
|
||||
if (action is IPrototype)
|
||||
{
|
||||
Logger.Error("Attempted to directly add a prototype action. You need to clone a prototype in order to use it.");
|
||||
return;
|
||||
}
|
||||
|
||||
comp ??= EnsureComp<ActionsComponent>(uid);
|
||||
action.Provider = provider;
|
||||
action.AttachedEntity = comp.Owner;
|
||||
@@ -426,7 +434,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
#region EquipHandlers
|
||||
private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
|
||||
{
|
||||
var ev = new GetActionsEvent();
|
||||
var ev = new GetItemActionsEvent(args.SlotFlags);
|
||||
RaiseLocalEvent(args.Equipment, ev, false);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
@@ -437,7 +445,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
|
||||
private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
|
||||
{
|
||||
var ev = new GetActionsEvent();
|
||||
var ev = new GetItemActionsEvent();
|
||||
RaiseLocalEvent(args.Equipped, ev, false);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
|
||||
Reference in New Issue
Block a user