Re-add action prototypes (#7508)

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2022-04-14 16:17:34 +12:00
committed by GitHub
parent c30b38a476
commit ba75934512
34 changed files with 291 additions and 189 deletions

View File

@@ -183,7 +183,7 @@ public sealed class DecalPlacementSystem : EntitySystem
} }
} }
public sealed class PlaceDecalActionEvent : PerformWorldTargetActionEvent public sealed class PlaceDecalActionEvent : WorldTargetActionEvent
{ {
[DataField("decalId", customTypeSerializer:typeof(PrototypeIdSerializer<DecalPrototype>))] [DataField("decalId", customTypeSerializer:typeof(PrototypeIdSerializer<DecalPrototype>))]
public string DecalId = string.Empty; public string DecalId = string.Empty;

View File

@@ -138,7 +138,7 @@ public sealed partial class MappingSystem : EntitySystem
} }
} }
public sealed class StartPlacementActionEvent : PerformActionEvent public sealed class StartPlacementActionEvent : InstantActionEvent
{ {
[DataField("entityType")] [DataField("entityType")]
public string? EntityType; public string? EntityType;

View File

@@ -20,7 +20,7 @@ namespace Content.Server.Atmos.EntitySystems
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<GasTankComponent, BeforeActivatableUIOpenEvent>(BeforeUiOpen); SubscribeLocalEvent<GasTankComponent, BeforeActivatableUIOpenEvent>(BeforeUiOpen);
SubscribeLocalEvent<GasTankComponent, GetActionsEvent>(OnGetActions); SubscribeLocalEvent<GasTankComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<GasTankComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<GasTankComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<GasTankComponent, ToggleActionEvent>(OnActionToggle); SubscribeLocalEvent<GasTankComponent, ToggleActionEvent>(OnActionToggle);
SubscribeLocalEvent<GasTankComponent, DroppedEvent>(OnDropped); SubscribeLocalEvent<GasTankComponent, DroppedEvent>(OnDropped);
@@ -37,7 +37,7 @@ namespace Content.Server.Atmos.EntitySystems
component.DisconnectFromInternals(args.User); component.DisconnectFromInternals(args.User);
} }
private void OnGetActions(EntityUid uid, GasTankComponent component, GetActionsEvent args) private void OnGetActions(EntityUid uid, GasTankComponent component, GetItemActionsEvent args)
{ {
args.Actions.Add(component.ToggleAction); args.Actions.Add(component.ToggleAction);
} }

View File

@@ -29,5 +29,5 @@ namespace Content.Server.Ghost.Components
}; };
} }
public sealed class BooActionEvent : PerformActionEvent { } public sealed class BooActionEvent : InstantActionEvent { }
} }

View File

@@ -36,5 +36,5 @@ namespace Content.Server.Guardian
}; };
} }
public sealed class GuardianToggleActionEvent : PerformActionEvent { }; public sealed class GuardianToggleActionEvent : InstantActionEvent { };
} }

View File

@@ -3,6 +3,7 @@ using Content.Server.Light.Components;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Light.Component; using Content.Shared.Light.Component;
@@ -14,6 +15,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Light.EntitySystems namespace Content.Server.Light.EntitySystems
@@ -24,6 +26,7 @@ namespace Content.Server.Light.EntitySystems
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly ActionsSystem _actionSystem = default!; [Dependency] private readonly ActionsSystem _actionSystem = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
// TODO: Ideally you'd be able to subscribe to power stuff to get events at certain percentages.. or something? // TODO: Ideally you'd be able to subscribe to power stuff to get events at certain percentages.. or something?
// But for now this will be better anyway. // But for now this will be better anyway.
@@ -42,13 +45,20 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate); SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<HandheldLightComponent, GetActionsEvent>(OnGetActions); SubscribeLocalEvent<HandheldLightComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<HandheldLightComponent, ToggleActionEvent>(OnToggleAction); SubscribeLocalEvent<HandheldLightComponent, ToggleActionEvent>(OnToggleAction);
} }
private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetActionsEvent args) private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetItemActionsEvent args)
{ {
args.Actions.Add(component.ToggleAction); if (component.ToggleAction == null
&& _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act))
{
component.ToggleAction = new(act);
}
if (component.ToggleAction != null)
args.Actions.Add(component.ToggleAction);
} }
private void OnToggleAction(EntityUid uid, HandheldLightComponent component, ToggleActionEvent args) private void OnToggleAction(EntityUid uid, HandheldLightComponent component, ToggleActionEvent args)
@@ -169,7 +179,8 @@ namespace Content.Server.Light.EntitySystems
if (!component.Activated) return false; if (!component.Activated) return false;
component.Activated = false; component.Activated = false;
_actionSystem.SetToggled(component.ToggleAction, false); if (component.ToggleAction != null)
_actionSystem.SetToggled(component.ToggleAction, false);
_activeLights.Remove(component); _activeLights.Remove(component);
component.LastLevel = null; component.LastLevel = null;
component.Dirty(EntityManager); component.Dirty(EntityManager);
@@ -202,7 +213,8 @@ namespace Content.Server.Light.EntitySystems
} }
component.Activated = true; component.Activated = true;
_actionSystem.SetToggled(component.ToggleAction, true); if (component.ToggleAction != null)
_actionSystem.SetToggled(component.ToggleAction, true);
_activeLights.Add(component); _activeLights.Add(component);
component.LastLevel = GetLevel(component); component.LastLevel = GetLevel(component);
Dirty(component); Dirty(component);

View File

@@ -19,7 +19,7 @@ namespace Content.Server.Light.EntitySystems
base.Initialize(); base.Initialize();
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerbs); SubscribeLocalEvent<UnpoweredFlashlightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerbs);
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetActionsEvent>(OnGetActions); SubscribeLocalEvent<UnpoweredFlashlightComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<UnpoweredFlashlightComponent, ToggleActionEvent>(OnToggleAction); SubscribeLocalEvent<UnpoweredFlashlightComponent, ToggleActionEvent>(OnToggleAction);
} }
@@ -33,7 +33,7 @@ namespace Content.Server.Light.EntitySystems
args.Handled = true; args.Handled = true;
} }
private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetActionsEvent args) private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetItemActionsEvent args)
{ {
args.Actions.Add(component.ToggleAction); args.Actions.Add(component.ToggleAction);
} }

View File

@@ -2,8 +2,7 @@ using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes; using Content.Shared.Actions.ActionTypes;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Utility; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Speech.Components; namespace Content.Server.Speech.Components;
@@ -25,19 +24,16 @@ public sealed class VocalComponent : Component
[DataField("audioParams")] [DataField("audioParams")]
public AudioParams AudioParams = AudioParams.Default.WithVolume(4f); public AudioParams AudioParams = AudioParams.Default.WithVolume(4f);
[DataField("wilhelmProbability")]
public float WilhelmProbability = 0.01f;
public const float Variation = 0.125f; public const float Variation = 0.125f;
// Not using the in-build sound support for actions, given that the sound is modified non-prototype specific factors like gender. [DataField("actionId", customTypeSerializer:typeof(PrototypeIdSerializer<InstantActionPrototype>))]
[DataField("action")] public string ActionId = "Scream";
public InstantAction Action = new()
{ [DataField("action")] // must be a data-field to properly save cooldown when saving game state.
UseDelay = TimeSpan.FromSeconds(10), public InstantAction? ScreamAction = null;
Icon = new SpriteSpecifier.Texture(new ResourcePath("Interface/Actions/scream.png")),
Name = "action-name-scream",
Description = "AAAAAAAAAAAAAAAAAAAAAAAAA",
Event = new ScreamActionEvent(),
CheckCanInteract = false, // system checks a speech related action blocker.
};
} }
public sealed class ScreamActionEvent : PerformActionEvent { }; public sealed class ScreamActionEvent : InstantActionEvent { };

View File

@@ -1,10 +1,12 @@
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.CharacterAppearance; using Content.Shared.CharacterAppearance;
using Content.Shared.CharacterAppearance.Components; using Content.Shared.CharacterAppearance.Components;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Speech; namespace Content.Server.Speech;
@@ -18,6 +20,7 @@ namespace Content.Server.Speech;
public sealed class VocalSystem : EntitySystem public sealed class VocalSystem : EntitySystem
{ {
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!;
@@ -32,12 +35,20 @@ public sealed class VocalSystem : EntitySystem
private void OnStartup(EntityUid uid, VocalComponent component, ComponentStartup args) private void OnStartup(EntityUid uid, VocalComponent component, ComponentStartup args)
{ {
_actions.AddAction(uid, component.Action, null); if (component.ScreamAction == null
&& _proto.TryIndex(component.ActionId, out InstantActionPrototype? act))
{
component.ScreamAction = new(act);
}
if (component.ScreamAction != null)
_actions.AddAction(uid, component.ScreamAction, null);
} }
private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args) private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args)
{ {
_actions.RemoveAction(uid, component.Action); if (component.ScreamAction != null)
_actions.RemoveAction(uid, component.ScreamAction);
} }
private void OnActionPerform(EntityUid uid, VocalComponent component, ScreamActionEvent args) private void OnActionPerform(EntityUid uid, VocalComponent component, ScreamActionEvent args)
@@ -60,7 +71,7 @@ public sealed class VocalSystem : EntitySystem
if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid)) if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid))
return false; return false;
if (_random.Prob(.01f)) if (_random.Prob(component.WilhelmProbability))
{ {
SoundSystem.Play(Filter.Pvs(uid), component.Wilhelm.GetSound(), uid, component.AudioParams); SoundSystem.Play(Filter.Pvs(uid), component.Wilhelm.GetSound(), uid, component.AudioParams);
return true; return true;

View File

@@ -70,7 +70,7 @@ public sealed class IntrinsicUISystem : EntitySystem
} }
[UsedImplicitly] [UsedImplicitly]
public sealed class ToggleIntrinsicUIEvent : PerformActionEvent public sealed class ToggleIntrinsicUIEvent : InstantActionEvent
{ {
[ViewVariables] [ViewVariables]
public Enum? Key { get; set; } public Enum? Key { get; set; }

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
namespace Content.Server.UserInterface; namespace Content.Server.UserInterface;
public sealed class OpenUiActionEvent : PerformActionEvent, ISerializationHooks public sealed class OpenUiActionEvent : InstantActionEvent, ISerializationHooks
{ {
[ViewVariables] [ViewVariables]
public Enum? Key { get; set; } public Enum? Key { get; set; }

View File

@@ -1,21 +1,44 @@
using Content.Shared.Actions.ActionTypes; 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.Map;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Actions; 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(); 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> /// <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> /// </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] [Serializable, NetSerializable]
public sealed class RequestPerformActionEvent : EntityEventArgs public sealed class RequestPerformActionEvent : EntityEventArgs
{ {
@@ -41,21 +64,56 @@ public sealed class RequestPerformActionEvent : EntityEventArgs
} }
} }
[ImplicitDataDefinitionForInheritors] /// <summary>
public abstract class PerformActionEvent : HandledEntityEventArgs /// 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> /// <summary>
/// The user performing the action /// The entity that the user targeted.
/// </summary> /// </summary>
public EntityUid Performer;
}
public abstract class PerformEntityTargetActionEvent : PerformActionEvent
{
public EntityUid Target; 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; 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;
}

View 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;
}
}

View File

@@ -3,10 +3,9 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Actions.ActionTypes; namespace Content.Shared.Actions.ActionTypes;
/// <summary> /// <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> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
[Friend(typeof(SharedActionsSystem))]
[Virtual] [Virtual]
public class InstantAction : ActionType public class InstantAction : ActionType
{ {
@@ -15,7 +14,7 @@ public class InstantAction : ActionType
/// </summary> /// </summary>
[DataField("event")] [DataField("event")]
[NonSerialized] [NonSerialized]
public PerformActionEvent? Event; public InstantActionEvent? Event;
public InstantAction() { } public InstantAction() { }
public InstantAction(InstantAction toClone) public InstantAction(InstantAction toClone)

View File

@@ -67,10 +67,9 @@ public abstract class TargetedAction : ActionType
} }
/// <summary> /// <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> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
[Friend(typeof(SharedActionsSystem))]
[Virtual] [Virtual]
public class EntityTargetAction : TargetedAction public class EntityTargetAction : TargetedAction
{ {
@@ -79,7 +78,7 @@ public class EntityTargetAction : TargetedAction
/// </summary> /// </summary>
[NonSerialized] [NonSerialized]
[DataField("event")] [DataField("event")]
public PerformEntityTargetActionEvent? Event; public EntityTargetActionEvent? Event;
[DataField("whitelist")] [DataField("whitelist")]
public EntityWhitelist? Whitelist; public EntityWhitelist? Whitelist;
@@ -115,10 +114,9 @@ public class EntityTargetAction : TargetedAction
} }
/// <summary> /// <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> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
[Friend(typeof(SharedActionsSystem))]
[Virtual] [Virtual]
public class WorldTargetAction : TargetedAction public class WorldTargetAction : TargetedAction
{ {
@@ -127,7 +125,7 @@ public class WorldTargetAction : TargetedAction
/// </summary> /// </summary>
[DataField("event")] [DataField("event")]
[NonSerialized] [NonSerialized]
public PerformWorldTargetActionEvent? Event; public WorldTargetActionEvent? Event;
public WorldTargetAction() { } public WorldTargetAction() { }
public WorldTargetAction(WorldTargetAction toClone) public WorldTargetAction(WorldTargetAction toClone)

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Containers;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using System.Linq; using System.Linq;
@@ -99,7 +100,7 @@ public abstract class SharedActionsSystem : EntitySystem
#region Execution #region Execution
/// <summary> /// <summary>
/// When receiving a request to perform an action, this validates whether the action is allowed. If it is, it /// 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> /// </summary>
private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArgs args) 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) if (act.Cooldown.HasValue && act.Cooldown.Value.End > curTime)
return; return;
PerformActionEvent? performEvent = null; BaseActionEvent? performEvent = null;
// Validate request by checking action blockers and the like: // Validate request by checking action blockers and the like:
var name = Loc.GetString(act.Name); var name = Loc.GetString(act.Name);
@@ -273,7 +274,7 @@ public abstract class SharedActionsSystem : EntitySystem
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range); 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; 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> /// <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) 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); comp ??= EnsureComp<ActionsComponent>(uid);
action.Provider = provider; action.Provider = provider;
action.AttachedEntity = comp.Owner; action.AttachedEntity = comp.Owner;
@@ -426,7 +434,7 @@ public abstract class SharedActionsSystem : EntitySystem
#region EquipHandlers #region EquipHandlers
private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args) private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
{ {
var ev = new GetActionsEvent(); var ev = new GetItemActionsEvent(args.SlotFlags);
RaiseLocalEvent(args.Equipment, ev, false); RaiseLocalEvent(args.Equipment, ev, false);
if (ev.Actions.Count == 0) if (ev.Actions.Count == 0)
@@ -437,7 +445,7 @@ public abstract class SharedActionsSystem : EntitySystem
private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args) private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
{ {
var ev = new GetActionsEvent(); var ev = new GetItemActionsEvent();
RaiseLocalEvent(args.Equipped, ev, false); RaiseLocalEvent(args.Equipped, ev, false);
if (ev.Actions.Count == 0) if (ev.Actions.Count == 0)

View File

@@ -13,7 +13,7 @@ public abstract class SharedMagbootsSystem : EntitySystem
SubscribeLocalEvent<SharedMagbootsComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb); SubscribeLocalEvent<SharedMagbootsComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb);
SubscribeLocalEvent<SharedMagbootsComponent, SlipAttemptEvent>(OnSlipAttempt); SubscribeLocalEvent<SharedMagbootsComponent, SlipAttemptEvent>(OnSlipAttempt);
SubscribeLocalEvent<SharedMagbootsComponent, GetActionsEvent>(OnGetActions); SubscribeLocalEvent<SharedMagbootsComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<SharedMagbootsComponent, ToggleActionEvent>(OnToggleAction); SubscribeLocalEvent<SharedMagbootsComponent, ToggleActionEvent>(OnToggleAction);
} }
@@ -35,7 +35,7 @@ public abstract class SharedMagbootsSystem : EntitySystem
args.Cancel(); args.Cancel();
} }
private void OnGetActions(EntityUid uid, SharedMagbootsComponent component, GetActionsEvent args) private void OnGetActions(EntityUid uid, SharedMagbootsComponent component, GetItemActionsEvent args)
{ {
args.Actions.Add(component.ToggleAction); args.Actions.Add(component.ToggleAction);
} }

View File

@@ -5,7 +5,7 @@ using Content.Shared.Sound;
using Content.Shared.Targeting; using Content.Shared.Targeting;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.CombatMode namespace Content.Shared.CombatMode
{ {
@@ -27,22 +27,17 @@ namespace Content.Shared.CombatMode
[DataField("disarmSuccessSound")] [DataField("disarmSuccessSound")]
public readonly SoundSpecifier DisarmSuccessSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"); public readonly SoundSpecifier DisarmSuccessSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
// These are chonky default definitions for combat actions. But its a pain to add a yaml version of this for [DataField("disarmActionId", customTypeSerializer:typeof(PrototypeIdSerializer<EntityTargetActionPrototype>))]
// every entity that wants combat mode, especially given that they're currently all identical... so ummm.. yeah. public readonly string DisarmActionId = "Disarm";
[DataField("disarmAction")]
public readonly EntityTargetAction DisarmAction = new(); [DataField("disarmAction")] // must be a data-field to properly save cooldown when saving game state.
public EntityTargetAction? DisarmAction;
[DataField("combatToggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
public readonly string CombatToggleActionId = "CombatModeToggle";
[DataField("combatToggleAction")] [DataField("combatToggleAction")]
public readonly InstantAction CombatToggleAction = new() public InstantAction? CombatToggleAction;
{
Icon = new SpriteSpecifier.Texture(new ResourcePath("Interface/Actions/harmOff.png")),
IconOn = new SpriteSpecifier.Texture(new ResourcePath("Interface/Actions/harm.png")),
UserPopup = "action-popup-combat",
PopupToggleSuffix = "-disabling",
Name = "action-name-combat",
Description = "action-description-combat",
Event = new ToggleCombatActionEvent(),
};
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public virtual bool IsInCombatMode public virtual bool IsInCombatMode
@@ -52,7 +47,8 @@ namespace Content.Shared.CombatMode
{ {
if (_isInCombatMode == value) return; if (_isInCombatMode == value) return;
_isInCombatMode = value; _isInCombatMode = value;
EntitySystem.Get<SharedActionsSystem>().SetToggled(CombatToggleAction, _isInCombatMode); if (CombatToggleAction != null)
EntitySystem.Get<SharedActionsSystem>().SetToggled(CombatToggleAction, _isInCombatMode);
Dirty(); Dirty();
// Regenerate physics contacts -> Can probably just selectively check // Regenerate physics contacts -> Can probably just selectively check

View File

@@ -1,10 +1,13 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Prototypes;
namespace Content.Shared.CombatMode namespace Content.Shared.CombatMode
{ {
public abstract class SharedCombatModeSystem : EntitySystem public abstract class SharedCombatModeSystem : EntitySystem
{ {
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -20,14 +23,32 @@ namespace Content.Shared.CombatMode
private void OnStartup(EntityUid uid, SharedCombatModeComponent component, ComponentStartup args) private void OnStartup(EntityUid uid, SharedCombatModeComponent component, ComponentStartup args)
{ {
_actionsSystem.AddAction(uid, component.CombatToggleAction, null); if (component.CombatToggleAction == null
_actionsSystem.AddAction(uid, component.DisarmAction, null); && _protoMan.TryIndex(component.CombatToggleActionId, out InstantActionPrototype? toggleProto))
{
component.CombatToggleAction = new(toggleProto);
}
if (component.CombatToggleAction != null)
_actionsSystem.AddAction(uid, component.CombatToggleAction, null);
if (component.DisarmAction == null
&& _protoMan.TryIndex(component.DisarmActionId, out EntityTargetActionPrototype? disarmProto))
{
component.DisarmAction = new(disarmProto);
}
if (component.DisarmAction != null)
_actionsSystem.AddAction(uid, component.DisarmAction, null);
} }
private void OnShutdown(EntityUid uid, SharedCombatModeComponent component, ComponentShutdown args) private void OnShutdown(EntityUid uid, SharedCombatModeComponent component, ComponentShutdown args)
{ {
_actionsSystem.RemoveAction(uid, component.CombatToggleAction); if (component.CombatToggleAction != null)
_actionsSystem.RemoveAction(uid, component.DisarmAction); _actionsSystem.RemoveAction(uid, component.CombatToggleAction);
if (component.DisarmAction != null)
_actionsSystem.RemoveAction(uid, component.DisarmAction);
} }
private void OnActionPerform(EntityUid uid, SharedCombatModeComponent component, ToggleCombatActionEvent args) private void OnActionPerform(EntityUid uid, SharedCombatModeComponent component, ToggleCombatActionEvent args)
@@ -52,6 +73,6 @@ namespace Content.Shared.CombatMode
} }
} }
public sealed class ToggleCombatActionEvent : PerformActionEvent { } public sealed class ToggleCombatActionEvent : InstantActionEvent { }
public sealed class DisarmActionEvent : PerformEntityTargetActionEvent { } public sealed class DisarmActionEvent : EntityTargetActionEvent { }
} }

View File

@@ -1,4 +1,4 @@
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Shared.Inventory.Events; namespace Content.Shared.Inventory.Events;

View File

@@ -1,14 +1,18 @@
using Content.Shared.Actions.ActionTypes; using Content.Shared.Actions.ActionTypes;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Light.Component namespace Content.Shared.Light.Component
{ {
[NetworkedComponent] [NetworkedComponent]
public abstract class SharedHandheldLightComponent : Robust.Shared.GameObjects.Component public abstract class SharedHandheldLightComponent : Robust.Shared.GameObjects.Component
{ {
[DataField("toggleAction", required: true)] [DataField("toggleActionId", customTypeSerializer:typeof(PrototypeIdSerializer<InstantActionPrototype>))]
public InstantAction ToggleAction = new(); public string ToggleActionId = "ToggleLight";
[DataField("toggleAction")]
public InstantAction? ToggleAction;
public const int StatusLevels = 6; public const int StatusLevels = 6;

View File

@@ -5,4 +5,4 @@ namespace Content.Shared.Toggleable;
/// <summary> /// <summary>
/// Generic action-event for toggle-able components. /// Generic action-event for toggle-able components.
/// </summary> /// </summary>
public sealed class ToggleActionEvent : PerformActionEvent { } public sealed class ToggleActionEvent : InstantActionEvent { }

View File

@@ -0,0 +1,42 @@
- type: instantAction
id: Scream
useDelay: 10
icon: Interface/Actions/scream.png
name: action-name-scream
description: AAAAAAAAAAAAAAAAAAAAAAAAA
serverEvent: !type:ScreamActionEvent
checkCanInteract: false
- type: instantAction
id: ToggleLight
name: action-name-toggle-light
description: action-description-toggle-light
icon: Objects/Tools/flashlight.rsi/flashlight.png
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: entityTargetAction
id: Disarm
name: action-name-disarm
description: action-description-disarm
icon: Interface/Actions/disarmOff.png
iconOn: Interface/Actions/disarm.png
repeat: true
useDelay: 1.5
interactOnMiss: true
event: !type:DisarmActionEvent
canTargetSelf: false
whitelist:
components:
- Hands
- StatusEffects
- type: instantAction
id: CombatModeToggle
name: action-name-combat
description: action-description-combat
icon: Interface/Actions/harmOff.png
iconOn: Interface/Actions/harm.png
userPopup: action-popup-combat
popupToggleSuffix: -disabling
event: !type:ToggleCombatActionEvent

View File

@@ -98,12 +98,6 @@
- type: FlashLightVisualizer - type: FlashLightVisualizer
- type: HandheldLight - type: HandheldLight
addPrefix: true addPrefix: true
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: Objects/Tools/flashlight.rsi/flashlight.png
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: PowerCellSlot - type: PowerCellSlot
cellSlot: cellSlot:
startingItem: PowerCellHardsuitHelmet # self recharging startingItem: PowerCellHardsuitHelmet # self recharging

View File

@@ -21,12 +21,6 @@
- type: FlashLightVisualizer - type: FlashLightVisualizer
- type: HandheldLight - type: HandheldLight
addPrefix: true addPrefix: true
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: Objects/Tools/flashlight.rsi/flashlight.png
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: PowerCellSlot - type: PowerCellSlot
cellSlot: cellSlot:
startingItem: PowerCellSmallHigh startingItem: PowerCellSmallHigh

View File

@@ -705,6 +705,7 @@
# mice are gender neutral who cares # mice are gender neutral who cares
maleScream: /Audio/Animals/mouse_squeak.ogg maleScream: /Audio/Animals/mouse_squeak.ogg
femaleScream: /Audio/Animals/mouse_squeak.ogg femaleScream: /Audio/Animals/mouse_squeak.ogg
wilhelmProbability: 0.001
# TODO: Remove CombatMode when Prototype Composition is added # TODO: Remove CombatMode when Prototype Composition is added
- type: CombatMode - type: CombatMode
combatToggleAction: combatToggleAction:

View File

@@ -95,20 +95,6 @@
100: !type:DeadMobState {} 100: !type:DeadMobState {}
- type: HeatResistance - type: HeatResistance
- type: CombatMode - type: CombatMode
disarmAction:
name: action-name-disarm
description: action-description-disarm
icon: Interface/Actions/disarmOff.png
iconOn: Interface/Actions/disarm.png
repeat: true
useDelay: 1.5
interactOnMiss: true
event: !type:DisarmActionEvent
canTargetSelf: false
whitelist:
components:
- Hands
- StatusEffects
- type: Internals - type: Internals
- type: StatusEffects - type: StatusEffects
allowed: allowed:

View File

@@ -12,20 +12,6 @@
- type: Hands - type: Hands
- type: DoAfter - type: DoAfter
- type: CombatMode - type: CombatMode
disarmAction:
name: action-name-disarm
description: action-description-disarm
icon: Interface/Actions/disarmOff.png
iconOn: Interface/Actions/disarm.png
repeat: true
useDelay: 1.5
interactOnMiss: true
event: !type:DisarmActionEvent
canTargetSelf: false
whitelist:
components:
- Hands
- StatusEffects
- type: Actions - type: Actions
- type: PlayerInputMover - type: PlayerInputMover
- type: MovementSpeedModifier - type: MovementSpeedModifier

View File

@@ -53,20 +53,6 @@
0: !type:NormalMobState {} 0: !type:NormalMobState {}
- type: HeatResistance - type: HeatResistance
- type: CombatMode - type: CombatMode
disarmAction:
name: action-name-disarm
description: action-description-disarm
icon: Interface/Actions/disarmOff.png
iconOn: Interface/Actions/disarm.png
repeat: true
useDelay: 1.5
interactOnMiss: true
event: !type:DisarmActionEvent
canTargetSelf: false
whitelist:
components:
- Hands
- StatusEffects
- type: Internals - type: Internals
- type: Examiner - type: Examiner
- type: Speech - type: Speech

View File

@@ -228,17 +228,3 @@
normal: onestar_boss normal: onestar_boss
dead: onestar_boss_wrecked dead: onestar_boss_wrecked
- type: CombatMode - type: CombatMode
disarmAction:
name: action-name-disarm
description: action-description-disarm
icon: Interface/Actions/disarmOff.png
iconOn: Interface/Actions/disarm.png
repeat: true
useDelay: 1.5
interactOnMiss: true
event: !type:DisarmActionEvent
canTargetSelf: false
whitelist:
components:
- Hands
- StatusEffects

View File

@@ -258,20 +258,6 @@
Burn: Burn:
sprite: Mobs/Effects/burn_damage.rsi sprite: Mobs/Effects/burn_damage.rsi
- type: CombatMode - type: CombatMode
disarmAction:
name: action-name-disarm
description: action-description-disarm
icon: Interface/Actions/disarmOff.png
iconOn: Interface/Actions/disarm.png
repeat: true
useDelay: 1.5
interactOnMiss: true
event: !type:DisarmActionEvent
canTargetSelf: false
whitelist:
components:
- Hands
- StatusEffects
- type: Climbing - type: Climbing
- type: Cuffable - type: Cuffable
- type: CharacterInfo - type: CharacterInfo

View File

@@ -6,12 +6,6 @@
components: components:
- type: HandheldLight - type: HandheldLight
addPrefix: true addPrefix: true
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: Objects/Tools/flashlight.rsi/flashlight.png
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: PowerCellSlot - type: PowerCellSlot
- type: Sprite - type: Sprite
sprite: Objects/Misc/Lights/lights.rsi sprite: Objects/Misc/Lights/lights.rsi

View File

@@ -17,12 +17,6 @@
right: right:
- state: inhand-right-light - state: inhand-right-light
shader: unshaded shader: unshaded
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: Objects/Tools/flashlight.rsi/flashlight.png
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: PowerCellSlot - type: PowerCellSlot
cellSlot: cellSlot:
startingItem: PowerCellSmallHigh startingItem: PowerCellSmallHigh

View File

@@ -6,12 +6,6 @@
components: components:
- type: HandheldLight - type: HandheldLight
addPrefix: true addPrefix: true
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: Objects/Tools/flashlight.rsi/flashlight.png
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: Sprite - type: Sprite
sprite: Objects/Tools/lantern.rsi sprite: Objects/Tools/lantern.rsi
layers: layers: