diff --git a/Content.Client/Actions/ActionEvents.cs b/Content.Client/Actions/ActionEvents.cs
index 2fdf25c976..73bee331be 100644
--- a/Content.Client/Actions/ActionEvents.cs
+++ b/Content.Client/Actions/ActionEvents.cs
@@ -1,3 +1,6 @@
+using Content.Shared.Actions.Components;
+using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
+
namespace Content.Client.Actions;
///
@@ -7,3 +10,17 @@ public sealed class FillActionSlotEvent : EntityEventArgs
{
public EntityUid? Action;
}
+
+///
+/// Client-side event used to attempt to trigger a targeted action.
+/// This only gets raised if the has .
+/// Handlers must set Handled to true, then if the action has been performed,
+/// i.e. a target is found, then FoundTarget must be set to true.
+///
+[ByRefEvent]
+public record struct ActionTargetAttemptEvent(
+ PointerInputCmdArgs Input,
+ Entity User,
+ ActionComponent Action,
+ bool Handled = false,
+ bool FoundTarget = false);
diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index 31350a6a5d..91baa3b1a9 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -1,18 +1,23 @@
using System.IO;
using System.Linq;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Charges.Systems;
+using Content.Shared.Mapping;
+using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.GameStates;
using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -25,6 +30,7 @@ namespace Content.Client.Actions
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceManager _resources = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
@@ -38,131 +44,67 @@ namespace Content.Client.Actions
public event Action>? AssignSlot;
private readonly List _removed = new();
- private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
+ private readonly List> _added = new();
+
+ public static readonly EntProtoId MappingEntityAction = "BaseMappingEntityAction";
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
- SubscribeLocalEvent(HandleComponentState);
+ SubscribeLocalEvent(OnHandleState);
- SubscribeLocalEvent(OnInstantHandleState);
- SubscribeLocalEvent(OnEntityTargetHandleState);
- SubscribeLocalEvent(OnWorldTargetHandleState);
- SubscribeLocalEvent(OnEntityWorldTargetHandleState);
+ SubscribeLocalEvent(OnActionAutoHandleState);
+
+ SubscribeLocalEvent(OnEntityTargetAttempt);
+ SubscribeLocalEvent(OnWorldTargetAttempt);
}
- private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not InstantActionComponentState state)
- return;
- BaseHandleState(uid, component, state);
+ private void OnActionAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ UpdateAction(ent);
}
- private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
+ public override void UpdateAction(Entity ent)
{
- if (args.Current is not EntityTargetActionComponentState state)
- return;
-
- component.Whitelist = state.Whitelist;
- component.Blacklist = state.Blacklist;
- component.CanTargetSelf = state.CanTargetSelf;
- BaseHandleState(uid, component, state);
- }
-
- private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not WorldTargetActionComponentState state)
- return;
-
- BaseHandleState(uid, component, state);
- }
-
- private void OnEntityWorldTargetHandleState(EntityUid uid,
- EntityWorldTargetActionComponent component,
- ref ComponentHandleState args)
- {
- if (args.Current is not EntityWorldTargetActionComponentState state)
- return;
-
- component.Whitelist = state.Whitelist;
- component.CanTargetSelf = state.CanTargetSelf;
- BaseHandleState(uid, component, state);
- }
-
- private void BaseHandleState(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
- {
- // TODO ACTIONS use auto comp states
- component.Icon = state.Icon;
- component.IconOn = state.IconOn;
- component.IconColor = state.IconColor;
- component.OriginalIconColor = state.OriginalIconColor;
- component.DisabledIconColor = state.DisabledIconColor;
- component.Keywords.Clear();
- component.Keywords.UnionWith(state.Keywords);
- component.Enabled = state.Enabled;
- component.Toggled = state.Toggled;
- component.Cooldown = state.Cooldown;
- component.UseDelay = state.UseDelay;
- component.Container = EnsureEntity(state.Container, uid);
- component.EntityIcon = EnsureEntity(state.EntityIcon, uid);
- component.CheckCanInteract = state.CheckCanInteract;
- component.CheckConsciousness = state.CheckConsciousness;
- component.ClientExclusive = state.ClientExclusive;
- component.Priority = state.Priority;
- component.AttachedEntity = EnsureEntity(state.AttachedEntity, uid);
- component.RaiseOnUser = state.RaiseOnUser;
- component.RaiseOnAction = state.RaiseOnAction;
- component.AutoPopulate = state.AutoPopulate;
- component.Temporary = state.Temporary;
- component.ItemIconStyle = state.ItemIconStyle;
- component.Sound = state.Sound;
-
- UpdateAction(uid, component);
- }
-
- public override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
- {
- if (!ResolveActionData(actionId, ref action))
- return;
-
// TODO: Decouple this.
- action.IconColor = _sharedCharges.GetCurrentCharges(actionId.Value) == 0 ? action.DisabledIconColor : action.OriginalIconColor;
-
- base.UpdateAction(actionId, action);
- if (_playerManager.LocalEntity != action.AttachedEntity)
+ ent.Comp.IconColor = _sharedCharges.GetCurrentCharges(ent.Owner) == 0 ? ent.Comp.DisabledIconColor : ent.Comp.OriginalIconColor;
+ base.UpdateAction(ent);
+ if (_playerManager.LocalEntity != ent.Comp.AttachedEntity)
return;
ActionsUpdated?.Invoke();
}
- private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
+ private void OnHandleState(Entity ent, ref ComponentHandleState args)
{
if (args.Current is not ActionsComponentState state)
return;
+ var (uid, comp) = ent;
_added.Clear();
_removed.Clear();
var stateEnts = EnsureEntitySet(state.Actions, uid);
- foreach (var act in component.Actions)
+ foreach (var act in comp.Actions)
{
if (!stateEnts.Contains(act) && !IsClientSide(act))
_removed.Add(act);
}
- component.Actions.ExceptWith(_removed);
+ comp.Actions.ExceptWith(_removed);
foreach (var actionId in stateEnts)
{
if (!actionId.IsValid())
continue;
- if (!component.Actions.Add(actionId))
+ if (!comp.Actions.Add(actionId))
continue;
- TryGetActionData(actionId, out var action);
- _added.Add((actionId, action));
+ if (GetAction(actionId) is {} action)
+ _added.Add(action);
}
if (_playerManager.LocalEntity != uid)
@@ -177,47 +119,46 @@ namespace Content.Client.Actions
foreach (var action in _added)
{
- OnActionAdded?.Invoke(action.Item1);
+ OnActionAdded?.Invoke(action);
}
ActionsUpdated?.Invoke();
}
- public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b)
+ public static int ActionComparer(Entity a, Entity b)
{
- var priorityA = a.Item2?.Priority ?? 0;
- var priorityB = b.Item2?.Priority ?? 0;
+ var priorityA = a.Comp?.Priority ?? 0;
+ var priorityB = b.Comp?.Priority ?? 0;
if (priorityA != priorityB)
return priorityA - priorityB;
- priorityA = a.Item2?.Container?.Id ?? 0;
- priorityB = b.Item2?.Container?.Id ?? 0;
+ priorityA = a.Comp?.Container?.Id ?? 0;
+ priorityB = b.Comp?.Container?.Id ?? 0;
return priorityA - priorityB;
}
- protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp,
- BaseActionComponent action)
+ protected override void ActionAdded(Entity performer, Entity action)
{
- if (_playerManager.LocalEntity != performer)
+ if (_playerManager.LocalEntity != performer.Owner)
return;
- OnActionAdded?.Invoke(actionId);
+ OnActionAdded?.Invoke(action);
ActionsUpdated?.Invoke();
}
- protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
+ protected override void ActionRemoved(Entity performer, Entity action)
{
- if (_playerManager.LocalEntity != performer)
+ if (_playerManager.LocalEntity != performer.Owner)
return;
- OnActionRemoved?.Invoke(actionId);
+ OnActionRemoved?.Invoke(action);
ActionsUpdated?.Invoke();
}
- public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
+ public IEnumerable> GetClientActions()
{
if (_playerManager.LocalEntity is not { } user)
- return Enumerable.Empty<(EntityUid, BaseActionComponent)>();
+ return Enumerable.Empty>();
return GetActions(user);
}
@@ -254,24 +195,23 @@ namespace Content.Client.Actions
CommandBinds.Unregister();
}
- public void TriggerAction(EntityUid actionId, BaseActionComponent action)
+ public void TriggerAction(Entity action)
{
- if (_playerManager.LocalEntity is not { } user ||
- !TryComp(user, out ActionsComponent? actions))
- {
- return;
- }
-
- if (action is not InstantActionComponent instantAction)
+ if (_playerManager.LocalEntity is not { } user)
return;
- if (action.ClientExclusive)
+ // TODO: unhardcode this somehow
+
+ if (!HasComp(action))
+ return;
+
+ if (action.Comp.ClientExclusive)
{
- PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
+ PerformAction(user, action);
}
else
{
- var request = new RequestPerformActionEvent(GetNetEntity(actionId));
+ var request = new RequestPerformActionEvent(GetNetEntity(action));
EntityManager.RaisePredictiveEvent(request);
}
}
@@ -295,39 +235,137 @@ namespace Content.Client.Actions
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
return;
+ var actions = EnsureComp(user);
+
ClearAssignments?.Invoke();
var assignments = new List();
-
foreach (var entry in sequence.Sequence)
{
if (entry is not MappingDataNode map)
continue;
- if (!map.TryGet("action", out var actionNode))
- continue;
-
- var action = _serialization.Read(actionNode, notNullableOverride: true);
- var actionId = Spawn();
- AddComp(actionId, action);
- AddActionDirect(user, actionId);
-
- if (map.TryGet("name", out var nameNode))
- _metaData.SetEntityName(actionId, nameNode.Value);
-
if (!map.TryGet("assignments", out var assignmentNode))
continue;
- var nodeAssignments = _serialization.Read>(assignmentNode, notNullableOverride: true);
-
- foreach (var index in nodeAssignments)
+ var actionId = EntityUid.Invalid;
+ if (map.TryGet("action", out var actionNode))
{
- var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
- assignments.Add(assignment);
+ var id = new EntProtoId(actionNode.Value);
+ actionId = Spawn(id);
}
+ else if (map.TryGet("entity", out var entityNode))
+ {
+ var id = new EntProtoId(entityNode.Value);
+ var proto = _proto.Index(id);
+ actionId = Spawn(MappingEntityAction);
+ SetIcon(actionId, new SpriteSpecifier.EntityPrototype(id));
+ SetEvent(actionId, new StartPlacementActionEvent()
+ {
+ PlacementOption = "SnapgridCenter",
+ EntityType = id
+ });
+ _metaData.SetEntityName(actionId, proto.Name);
+ }
+ else if (map.TryGet("tileId", out var tileNode))
+ {
+ var id = new ProtoId(tileNode.Value);
+ var proto = _proto.Index(id);
+ actionId = Spawn(MappingEntityAction);
+ if (proto.Sprite is {} sprite)
+ SetIcon(actionId, new SpriteSpecifier.Texture(sprite));
+ SetEvent(actionId, new StartPlacementActionEvent()
+ {
+ PlacementOption = "AlignTileAny",
+ TileId = id
+ });
+ _metaData.SetEntityName(actionId, Loc.GetString(proto.Name));
+ }
+ else
+ {
+ Log.Error($"Mapping actions from {path} had unknown action data!");
+ continue;
+ }
+
+ AddActionDirect((user, actions), actionId);
+ }
+ }
+
+ private void OnWorldTargetAttempt(Entity ent, ref ActionTargetAttemptEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+
+ var (uid, comp) = ent;
+ var action = args.Action;
+ var coords = args.Input.Coordinates;
+ var user = args.User;
+
+ if (!ValidateWorldTarget(user, coords, ent))
+ return;
+
+ // optionally send the clicked entity too, if it matches its whitelist etc
+ // this is the actual entity-world targeting magic
+ EntityUid? targetEnt = null;
+ if (TryComp(ent, out var entity) &&
+ args.Input.EntityUid != null &&
+ ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
+ {
+ targetEnt = args.Input.EntityUid;
}
- AssignSlot?.Invoke(assignments);
+ if (action.ClientExclusive)
+ {
+ // TODO: abstract away from single event or maybe just RaiseLocalEvent?
+ if (comp.Event is {} ev)
+ {
+ ev.Target = coords;
+ ev.Entity = targetEnt;
+ }
+
+ PerformAction((user, user.Comp), (uid, action));
+ }
+ else
+ RaisePredictiveEvent(new RequestPerformActionEvent(GetNetEntity(uid), GetNetEntity(targetEnt), GetNetCoordinates(coords)));
+
+ args.FoundTarget = true;
+ }
+
+ private void OnEntityTargetAttempt(Entity ent, ref ActionTargetAttemptEvent args)
+ {
+ if (args.Handled || args.Input.EntityUid is not { Valid: true } entity)
+ return;
+
+ // let world target component handle it
+ var (uid, comp) = ent;
+ if (comp.Event is not {} ev)
+ {
+ DebugTools.Assert(HasComp(ent), $"Action {ToPrettyString(ent)} requires WorldTargetActionComponent for entity-world targeting");
+ return;
+ }
+
+ args.Handled = true;
+
+ var action = args.Action;
+ var user = args.User;
+
+ if (!ValidateEntityTarget(user, entity, ent))
+ return;
+
+ if (action.ClientExclusive)
+ {
+ ev.Target = entity;
+
+ PerformAction((user, user.Comp), (uid, action));
+ }
+ else
+ {
+ RaisePredictiveEvent(new RequestPerformActionEvent(GetNetEntity(uid), GetNetEntity(entity)));
+ }
+
+ args.FoundTarget = true;
}
public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);
diff --git a/Content.Client/Charges/ChargesSystem.cs b/Content.Client/Charges/ChargesSystem.cs
index 2c7e0536cd..890ff207ac 100644
--- a/Content.Client/Charges/ChargesSystem.cs
+++ b/Content.Client/Charges/ChargesSystem.cs
@@ -25,16 +25,14 @@ public sealed class ChargesSystem : SharedChargesSystem
while (query.MoveNext(out var uid, out var recharge, out var charges))
{
- BaseActionComponent? actionComp = null;
-
- if (!_actions.ResolveActionData(uid, ref actionComp, logError: false))
+ if (_actions.GetAction(uid, false) is not {} action)
continue;
var current = GetCurrentCharges((uid, charges, recharge));
if (!_lastCharges.TryGetValue(uid, out var last) || current != last)
{
- _actions.UpdateAction(uid, actionComp);
+ _actions.UpdateAction(action);
}
_tempLastCharges[uid] = current;
diff --git a/Content.Client/Decals/DecalPlacementSystem.cs b/Content.Client/Decals/DecalPlacementSystem.cs
index a4495042c6..db00534a38 100644
--- a/Content.Client/Decals/DecalPlacementSystem.cs
+++ b/Content.Client/Decals/DecalPlacementSystem.cs
@@ -2,6 +2,7 @@ using System.Numerics;
using Content.Client.Actions;
using Content.Client.Decals.Overlays;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Decals;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -21,9 +22,12 @@ public sealed class DecalPlacementSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
+ public static readonly EntProtoId DecalAction = "BaseMappingDecalAction";
+
private string? _decalId;
private Color _decalColor = Color.White;
private Angle _decalAngle = Angle.Zero;
@@ -152,19 +156,12 @@ public sealed class DecalPlacementSystem : EntitySystem
Cleanable = _cleanable,
};
- var actionId = Spawn(null);
- AddComp(actionId, new WorldTargetActionComponent
- {
- // non-unique actions may be considered duplicates when saving/loading.
- Icon = decalProto.Sprite,
- Repeat = true,
- ClientExclusive = true,
- CheckCanAccess = false,
- CheckCanInteract = false,
- Range = -1,
- Event = actionEvent,
- IconColor = _decalColor,
- });
+ var actionId = Spawn(DecalAction);
+ var action = Comp(actionId);
+ var ent = (actionId, action);
+ _actions.SetEvent(actionId, actionEvent);
+ _actions.SetIcon(ent, decalProto.Sprite);
+ _actions.SetIconColor(ent, _decalColor);
_metaData.SetEntityName(actionId, $"{_decalId} ({_decalColor.ToHex()}, {(int) _decalAngle.Degrees})");
diff --git a/Content.Client/Mapping/MappingSystem.cs b/Content.Client/Mapping/MappingSystem.cs
index 80189fbdfc..627977a526 100644
--- a/Content.Client/Mapping/MappingSystem.cs
+++ b/Content.Client/Mapping/MappingSystem.cs
@@ -4,8 +4,8 @@ using Content.Shared.Mapping;
using Content.Shared.Maps;
using Robust.Client.Placement;
using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
-using static Robust.Shared.Utility.SpriteSpecifier;
namespace Content.Client.Mapping;
@@ -14,16 +14,10 @@ public sealed partial class MappingSystem : EntitySystem
[Dependency] private readonly IPlacementManager _placementMan = default!;
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
- ///
- /// The icon to use for space tiles.
- ///
- private readonly SpriteSpecifier _spaceIcon = new Texture(new ("Tiles/cropped_parallax.png"));
-
- ///
- /// The icon to use for entity-eraser.
- ///
- private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
+ public static readonly EntProtoId SpawnAction = "BaseMappingSpawnAction";
+ public static readonly EntProtoId EraserAction = "ActionMappingEraser";
public override void Initialize()
{
@@ -38,90 +32,46 @@ public sealed partial class MappingSystem : EntitySystem
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd
/// prefer if it were to function more like DecalPlacementSystem.
///
- private void OnFillActionSlot(FillActionSlotEvent ev)
+ private void OnFillActionSlot(FillActionSlotEvent args)
{
if (!_placementMan.IsActive)
return;
- if (ev.Action != null)
+ if (args.Action != null)
return;
- var actionEvent = new StartPlacementActionEvent();
- ITileDefinition? tileDef = null;
-
- if (_placementMan.CurrentPermission != null)
+ if (_placementMan.CurrentPermission is {} permission)
{
- actionEvent.EntityType = _placementMan.CurrentPermission.EntityType;
- actionEvent.PlacementOption = _placementMan.CurrentPermission.PlacementOption;
+ var ev = new StartPlacementActionEvent()
+ {
+ EntityType = permission.EntityType,
+ PlacementOption = permission.PlacementOption,
+ };
+ var action = Spawn(SpawnAction);
if (_placementMan.CurrentPermission.IsTile)
{
- tileDef = _tileMan[_placementMan.CurrentPermission.TileType];
- actionEvent.TileId = tileDef.ID;
+ if (_tileMan[_placementMan.CurrentPermission.TileType] is not ContentTileDefinition tileDef)
+ return;
+
+ if (!tileDef.MapAtmosphere && tileDef.Sprite is {} sprite)
+ _actions.SetIcon(action, new SpriteSpecifier.Texture(sprite));
+ ev.TileId = tileDef.ID;
+ _metaData.SetEntityName(action, Loc.GetString(tileDef.Name));
}
+ else if (permission.EntityType is {} id)
+ {
+ _actions.SetIcon(action, new SpriteSpecifier.EntityPrototype(id));
+ _metaData.SetEntityName(action, id);
+ }
+
+ _actions.SetEvent(action, ev);
+ args.Action = action;
}
else if (_placementMan.Eraser)
{
- actionEvent.Eraser = true;
+ args.Action = Spawn(EraserAction);
}
- else
- return;
-
- InstantActionComponent action;
- string name;
-
- if (tileDef != null)
- {
- if (tileDef is not ContentTileDefinition contentTileDef)
- return;
-
- var tileIcon = contentTileDef.MapAtmosphere
- ? _spaceIcon
- : new Texture(contentTileDef.Sprite!.Value);
-
- action = new InstantActionComponent
- {
- ClientExclusive = true,
- CheckCanInteract = false,
- Event = actionEvent,
- Icon = tileIcon
- };
-
- name = Loc.GetString(tileDef.Name);
- }
- else if (actionEvent.Eraser)
- {
- action = new InstantActionComponent
- {
- ClientExclusive = true,
- CheckCanInteract = false,
- Event = actionEvent,
- Icon = _deleteIcon,
- };
-
- name = Loc.GetString("action-name-mapping-erase");
- }
- else
- {
- if (string.IsNullOrWhiteSpace(actionEvent.EntityType))
- return;
-
- action = new InstantActionComponent
- {
- ClientExclusive = true,
- CheckCanInteract = false,
- Event = actionEvent,
- Icon = new EntityPrototype(actionEvent.EntityType),
- };
-
- name = actionEvent.EntityType;
- }
-
- var actionId = Spawn(null);
- AddComp(actionId, action);
- _metaData.SetEntityName(actionId, name);
-
- ev.Action = actionId;
}
private void OnStartPlacementAction(StartPlacementActionEvent args)
diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs
index 260bcfe4f2..5b5d8739f7 100644
--- a/Content.Client/Store/Ui/StoreMenu.xaml.cs
+++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs
@@ -141,11 +141,8 @@ public sealed partial class StoreMenu : DefaultWindow
else if (listing.ProductAction != null)
{
var actionId = _entityManager.Spawn(listing.ProductAction);
- if (_entityManager.System().TryGetActionData(actionId, out var action) &&
- action.Icon != null)
- {
- texture = spriteSys.Frame0(action.Icon);
- }
+ if (_entityManager.System().GetAction(actionId)?.Comp?.Icon is {} icon)
+ texture = spriteSys.Frame0(icon);
}
var listingInStock = GetListingPriceString(listing);
diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
index f6fc2c997f..dc62dbe897 100644
--- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
+++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
@@ -12,6 +12,7 @@ using Content.Client.UserInterface.Systems.Actions.Widgets;
using Content.Client.UserInterface.Systems.Actions.Windows;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Input;
using Robust.Client.GameObjects;
@@ -162,142 +163,33 @@ public sealed class ActionUIController : UIController, IOnStateChanged(user, out var comp))
return false;
- if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
- baseAction is not BaseTargetActionComponent action)
+ if (_actionsSystem.GetAction(actionId) is not {} action ||
+ !EntityManager.TryGetComponent(action, out var target))
{
return false;
}
// Is the action currently valid?
- if (!action.Enabled
- || action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
+ if (!_actionsSystem.ValidAction(action))
{
// The user is targeting with this action, but it is not valid. Maybe mark this click as
// handled and prevent further interactions.
- return !action.InteractOnMiss;
+ return !target.InteractOnMiss;
}
- switch (action)
+ var ev = new ActionTargetAttemptEvent(args, (user, comp), action);
+ EntityManager.EventBus.RaiseLocalEvent(action, ref ev);
+ if (!ev.Handled)
{
- case WorldTargetActionComponent mapTarget:
- return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;
-
- case EntityTargetActionComponent entTarget:
- return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;
-
- case EntityWorldTargetActionComponent entMapTarget:
- return TryTargetEntityWorld(args, actionId, entMapTarget, user, comp) || !entMapTarget.InteractOnMiss;
-
- default:
- Log.Error($"Unknown targeting action: {actionId.GetType()}");
- return false;
- }
- }
-
- private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, WorldTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
- {
- if (_actionsSystem == null)
- return false;
-
- var coords = args.Coordinates;
-
- if (!_actionsSystem.ValidateWorldTarget(user, coords, (actionId, action)))
- {
- // Invalid target.
- if (action.DeselectOnMiss)
- StopTargeting();
-
+ Log.Error($"Action {EntityManager.ToPrettyString(actionId)} did not handle ActionTargetAttemptEvent!");
return false;
}
- if (action.ClientExclusive)
- {
- if (action.Event != null)
- {
- action.Event.Target = coords;
- }
-
- _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
- }
- else
- EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetCoordinates(coords)));
-
- if (!action.Repeat)
- StopTargeting();
-
- return true;
- }
-
- private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, EntityTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
- {
- if (_actionsSystem == null)
- return false;
-
- var entity = args.EntityUid;
-
- if (!_actionsSystem.ValidateEntityTarget(user, entity, (actionId, action)))
- {
- if (action.DeselectOnMiss)
- StopTargeting();
-
- return false;
- }
-
- if (action.ClientExclusive)
- {
- if (action.Event != null)
- {
- action.Event.Target = entity;
- }
-
- _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
- }
- else
- EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(args.EntityUid)));
-
- if (!action.Repeat)
- StopTargeting();
-
- return true;
- }
-
- private bool TryTargetEntityWorld(in PointerInputCmdArgs args,
- EntityUid actionId,
- EntityWorldTargetActionComponent action,
- EntityUid user,
- ActionsComponent actionComp)
- {
- if (_actionsSystem == null)
- return false;
-
- var entity = args.EntityUid;
- var coords = args.Coordinates;
-
- if (!_actionsSystem.ValidateEntityWorldTarget(user, entity, coords, (actionId, action)))
- {
- if (action.DeselectOnMiss)
- StopTargeting();
-
- return false;
- }
-
- if (action.ClientExclusive)
- {
- if (action.Event != null)
- {
- action.Event.Entity = entity;
- action.Event.Coords = coords;
- }
-
- _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
- }
- else
- EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(args.EntityUid), EntityManager.GetNetCoordinates(coords)));
-
- if (!action.Repeat)
+ // stop targeting when needed
+ if (ev.FoundTarget ? !target.Repeat : target.DeselectOnMiss)
StopTargeting();
return true;
@@ -305,36 +197,26 @@ public sealed class ActionUIController : UIController, IOnStateChanged(action, out var target))
+ ToggleTargeting((action, action, target));
else
- _actionsSystem?.TriggerAction(actionId.Value, baseAction);
+ _actionsSystem?.TriggerAction(action);
}
private void OnActionAdded(EntityUid actionId)
{
- if (_actionsSystem == null ||
- !_actionsSystem.TryGetActionData(actionId, out var action))
- {
+ if (_actionsSystem?.GetAction(actionId) is not {} action)
return;
- }
+ // TODO: event
// if the action is toggled when we add it, start targeting
- if (action is BaseTargetActionComponent targetAction && action.Toggled)
- StartTargeting(actionId, targetAction);
+ if (action.Comp.Toggled && EntityManager.TryGetComponent(actionId, out var target))
+ StartTargeting((action, action, target));
- if (_actions.Contains(actionId))
+ if (_actions.Contains(action))
return;
- _actions.Add(actionId);
+ _actions.Add(action);
}
private void OnActionRemoved(EntityUid actionId)
@@ -437,15 +317,16 @@ public sealed class ActionUIController : UIController, IOnStateChanged ent, Filters filter)
{
+ var (uid, comp) = ent;
return filter switch
{
- Filters.Enabled => action.Enabled,
- Filters.Item => action.Container != null && action.Container != _playerManager.LocalEntity,
- Filters.Innate => action.Container == null || action.Container == _playerManager.LocalEntity,
- Filters.Instant => action is InstantActionComponent,
- Filters.Targeted => action is BaseTargetActionComponent,
+ Filters.Enabled => comp.Enabled,
+ Filters.Item => comp.Container != null && comp.Container != _playerManager.LocalEntity,
+ Filters.Innate => comp.Container == null || comp.Container == _playerManager.LocalEntity,
+ Filters.Instant => EntityManager.HasComponent(uid),
+ Filters.Targeted => EntityManager.HasComponent(uid),
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
};
}
@@ -456,7 +337,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged actions)
+ private void PopulateActions(IEnumerable> actions)
{
if (_window is not { Disposed: false, IsOpen: true })
return;
@@ -478,7 +359,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged
{
- if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action.Comp, filter)))
+ if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action, filter)))
return false;
if (action.Comp.Keywords.Any(keyword => search.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
return true;
- var name = EntityManager.GetComponent(action.Id).EntityName;
+ var name = EntityManager.GetComponent(action).EntityName;
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
return true;
@@ -581,7 +462,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged(action, out var target))
{
- _actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
+ _actionsSystem?.TriggerAction(action);
return;
}
@@ -714,7 +593,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged
- private void ToggleTargeting(EntityUid actionId, BaseTargetActionComponent action)
+ private void ToggleTargeting(Entity ent)
{
- if (SelectingTargetFor == actionId)
+ if (SelectingTargetFor == ent)
{
StopTargeting();
return;
}
- StartTargeting(actionId, action);
+ StartTargeting(ent);
}
///
/// Puts us in targeting mode, where we need to pick either a target point or entity
///
- private void StartTargeting(EntityUid actionId, BaseTargetActionComponent action)
+ private void StartTargeting(Entity ent)
{
+ var (uid, action, target) = ent;
+
// If we were targeting something else we should stop
StopTargeting();
- SelectingTargetFor = actionId;
+ SelectingTargetFor = uid;
// TODO inform the server
- action.Toggled = true;
+ _actionsSystem?.SetToggled(uid, true);
// override "held-item" overlay
var provider = action.Container;
- if (action.TargetingIndicator && _overlays.TryGetOverlay(out var handOverlay))
+ if (target.TargetingIndicator && _overlays.TryGetOverlay(out var handOverlay))
{
if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Container != null)
{
@@ -940,7 +821,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged(uid, out var entity))
return;
Func? predicate = null;
- var attachedEnt = entityAction.AttachedEntity;
+ var attachedEnt = action.AttachedEntity;
- if (!entityAction.CanTargetSelf)
+ if (!entity.CanTargetSelf)
predicate = e => e != attachedEnt;
- var range = entityAction.CheckCanAccess ? action.Range : -1;
+ var range = target.CheckCanAccess ? target.Range : -1;
_interactionOutline?.SetEnabled(false);
- _targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
+ _targetOutline?.Enable(range, target.CheckCanAccess, predicate, entity.Whitelist, entity.Blacklist, null);
}
///
@@ -974,11 +855,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged? Action { get; private set; }
public bool Locked { get; set; }
public event Action? ActionPressed;
@@ -193,7 +193,7 @@ public sealed class ActionButton : Control, IEntityControl
private Control? SupplyTooltip(Control sender)
{
- if (!_entities.TryGetComponent(ActionId, out MetaDataComponent? metadata))
+ if (!_entities.TryGetComponent(Action, out MetaDataComponent? metadata))
return null;
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
@@ -201,14 +201,14 @@ public sealed class ActionButton : Control, IEntityControl
FormattedMessage? chargesText = null;
// TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you.
- if (_entities.TryGetComponent(ActionId, out LimitedChargesComponent? actionCharges))
+ if (_entities.TryGetComponent(Action, out LimitedChargesComponent? actionCharges))
{
- var charges = _sharedChargesSys.GetCurrentCharges((ActionId.Value, actionCharges, null));
+ var charges = _sharedChargesSys.GetCurrentCharges((Action.Value, actionCharges, null));
chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}"));
- if (_entities.TryGetComponent(ActionId, out AutoRechargeComponent? autoRecharge))
+ if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge))
{
- var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((ActionId.Value, actionCharges, autoRecharge));
+ var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge));
chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}"));
}
}
@@ -223,7 +223,7 @@ public sealed class ActionButton : Control, IEntityControl
private void UpdateItemIcon()
{
- if (_action is not {EntityIcon: { } entity} ||
+ if (Action?.Comp is not {EntityIcon: { } entity} ||
!_entities.HasComponent(entity))
{
_bigItemSpriteView.Visible = false;
@@ -233,7 +233,7 @@ public sealed class ActionButton : Control, IEntityControl
}
else
{
- switch (_action.ItemIconStyle)
+ switch (Action?.Comp.ItemIconStyle)
{
case ItemActionIconStyle.BigItem:
_bigItemSpriteView.Visible = true;
@@ -259,17 +259,17 @@ public sealed class ActionButton : Control, IEntityControl
private void SetActionIcon(Texture? texture)
{
- if (_action == null || texture == null)
+ if (Action?.Comp is not {} action || texture == null)
{
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
- else if (_action.EntityIcon != null && _action.ItemIconStyle == ItemActionIconStyle.BigItem)
+ else if (action.EntityIcon != null && action.ItemIconStyle == ItemActionIconStyle.BigItem)
{
_smallActionIcon.Texture = texture;
- _smallActionIcon.Modulate = _action.IconColor;
+ _smallActionIcon.Modulate = action.IconColor;
_smallActionIcon.Visible = true;
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
@@ -277,7 +277,7 @@ public sealed class ActionButton : Control, IEntityControl
else
{
_bigActionIcon.Texture = texture;
- _bigActionIcon.Modulate = _action.IconColor;
+ _bigActionIcon.Modulate = action.IconColor;
_bigActionIcon.Visible = true;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
@@ -289,7 +289,7 @@ public sealed class ActionButton : Control, IEntityControl
UpdateItemIcon();
UpdateBackground();
- if (_action == null)
+ if (Action is not {} action)
{
SetActionIcon(null);
return;
@@ -297,29 +297,27 @@ public sealed class ActionButton : Control, IEntityControl
_controller ??= UserInterfaceManager.GetUIController();
_spriteSys ??= _entities.System();
- if ((_controller.SelectingTargetFor == ActionId || _action.Toggled))
+ var icon = action.Comp.Icon;
+ if (_controller.SelectingTargetFor == action || action.Comp.Toggled)
{
- if (_action.IconOn != null)
- SetActionIcon(_spriteSys.Frame0(_action.IconOn));
- else if (_action.Icon != null)
- SetActionIcon(_spriteSys.Frame0(_action.Icon));
- else
- SetActionIcon(null);
+ if (action.Comp.IconOn is {} iconOn)
+ icon = iconOn;
- if (_action.BackgroundOn != null)
- _buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);
+ if (action.Comp.BackgroundOn is {} background)
+ _buttonBackgroundTexture = _spriteSys.Frame0(background);
}
else
{
- SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null);
_buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
}
+
+ SetActionIcon(icon != null ? _spriteSys.Frame0(icon) : null);
}
public void UpdateBackground()
{
_controller ??= UserInterfaceManager.GetUIController();
- if (_action != null ||
+ if (Action != null ||
_controller.IsDragging && GetPositionInParent() == Parent?.ChildCount - 1)
{
Button.Texture = _buttonBackgroundTexture;
@@ -333,9 +331,7 @@ public sealed class ActionButton : Control, IEntityControl
public bool TryReplaceWith(EntityUid actionId, ActionsSystem system)
{
if (Locked)
- {
return false;
- }
UpdateData(actionId, system);
return true;
@@ -343,16 +339,15 @@ public sealed class ActionButton : Control, IEntityControl
public void UpdateData(EntityUid? actionId, ActionsSystem system)
{
- ActionId = actionId;
- system.TryGetActionData(actionId, out _action);
- Label.Visible = actionId != null;
+ Action = system.GetAction(actionId);
+
+ Label.Visible = Action != null;
UpdateIcons();
}
public void ClearData()
{
- ActionId = null;
- _action = null;
+ Action = null;
Cooldown.Visible = false;
Cooldown.Progress = 1;
Label.Visible = false;
@@ -365,19 +360,15 @@ public sealed class ActionButton : Control, IEntityControl
UpdateBackground();
- Cooldown.Visible = _action != null && _action.Cooldown != null;
- if (_action == null)
+ Cooldown.Visible = Action?.Comp.Cooldown != null;
+ if (Action?.Comp is not {} action)
return;
- if (_action.Cooldown != null)
- {
- Cooldown.FromTime(_action.Cooldown.Value.Start, _action.Cooldown.Value.End);
- }
+ if (action.Cooldown is {} cooldown)
+ Cooldown.FromTime(cooldown.Start, cooldown.End);
- if (ActionId != null && _toggled != _action.Toggled)
- {
- _toggled = _action.Toggled;
- }
+ if (_toggled != action.Toggled)
+ _toggled = action.Toggled;
}
protected override void MouseEntered()
@@ -404,7 +395,7 @@ public sealed class ActionButton : Control, IEntityControl
public void Depress(GUIBoundKeyEventArgs args, bool depress)
{
// action can still be toggled if it's allowed to stay selected
- if (_action is not {Enabled: true})
+ if (Action?.Comp is not {Enabled: true})
return;
_depressed = depress;
@@ -414,17 +405,17 @@ public sealed class ActionButton : Control, IEntityControl
public void DrawModeChanged()
{
_controller ??= UserInterfaceManager.GetUIController();
- HighlightRect.Visible = _beingHovered && (_action != null || _controller.IsDragging);
+ HighlightRect.Visible = _beingHovered && (Action != null || _controller.IsDragging);
// always show the normal empty button style if no action in this slot
- if (_action == null)
+ if (Action?.Comp is not {} action)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
return;
}
// show a hover only if the action is usable or another action is being dragged on top of this
- if (_beingHovered && (_controller.IsDragging || _action!.Enabled))
+ if (_beingHovered && (_controller.IsDragging || action.Enabled))
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
}
@@ -439,16 +430,16 @@ public sealed class ActionButton : Control, IEntityControl
}
// if it's toggled on, always show the toggled on style (currently same as depressed style)
- if (_action.Toggled || _controller.SelectingTargetFor == ActionId)
+ if (action.Toggled || _controller.SelectingTargetFor == Action?.Owner)
{
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
- SetOnlyStylePseudoClass(_action.IconOn != null
+ SetOnlyStylePseudoClass(action.IconOn != null
? ContainerButton.StylePseudoClassNormal
: ContainerButton.StylePseudoClassPressed);
return;
}
- if (!_action.Enabled)
+ if (!action.Enabled)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
return;
@@ -457,5 +448,5 @@ public sealed class ActionButton : Control, IEntityControl
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
}
- EntityUid? IEntityControl.UiEntity => ActionId;
+ EntityUid? IEntityControl.UiEntity => Action;
}
diff --git a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs
index c232e82313..0dbec0c83a 100644
--- a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs
+++ b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.CombatMode;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
@@ -46,24 +47,26 @@ public sealed class ActionsAddedTest
// This action should have a non-null event both on the server & client.
var evType = typeof(ToggleCombatActionEvent);
+ var sQuery = sEntMan.GetEntityQuery();
+ var cQuery = cEntMan.GetEntityQuery();
var sActions = sActionSystem.GetActions(serverEnt).Where(
- x => x.Comp is InstantActionComponent act && act.Event?.GetType() == evType).ToArray();
+ ent => sQuery.CompOrNull(ent)?.Event?.GetType() == evType).ToArray();
var cActions = cActionSystem.GetActions(clientEnt).Where(
- x => x.Comp is InstantActionComponent act && act.Event?.GetType() == evType).ToArray();
+ ent => cQuery.CompOrNull(ent)?.Event?.GetType() == evType).ToArray();
Assert.That(sActions.Length, Is.EqualTo(1));
Assert.That(cActions.Length, Is.EqualTo(1));
- var sAct = sActions[0].Comp;
- var cAct = cActions[0].Comp;
+ var sAct = sActions[0];
+ var cAct = cActions[0];
- Assert.That(sAct, Is.Not.Null);
- Assert.That(cAct, Is.Not.Null);
+ Assert.That(sAct.Comp, Is.Not.Null);
+ Assert.That(cAct.Comp, Is.Not.Null);
// Finally, these two actions are not the same object
// required, because integration tests do not respect the [NonSerialized] attribute and will simply events by reference.
- Assert.That(ReferenceEquals(sAct, cAct), Is.False);
- Assert.That(ReferenceEquals(sAct.BaseEvent, cAct.BaseEvent), Is.False);
+ Assert.That(ReferenceEquals(sAct.Comp, cAct.Comp), Is.False);
+ Assert.That(ReferenceEquals(sQuery.GetComponent(sAct).Event, cQuery.GetComponent(cAct).Event), Is.False);
await pair.CleanReturnAsync();
}
diff --git a/Content.Server/Actions/ActionOnInteractSystem.cs b/Content.Server/Actions/ActionOnInteractSystem.cs
index c658b3f90a..973e1afbcd 100644
--- a/Content.Server/Actions/ActionOnInteractSystem.cs
+++ b/Content.Server/Actions/ActionOnInteractSystem.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Interaction;
@@ -60,8 +61,10 @@ public sealed class ActionOnInteractSystem : EntitySystem
if (!TryUseCharge((uid, component)))
return;
- var (actId, act) = _random.Pick(options);
- _actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
+ // not predicted as this is in server due to random
+ // TODO: use predicted random and move to shared?
+ var (actId, action, comp) = _random.Pick(options);
+ _actions.PerformAction(args.User, (actId, action), predicted: false);
args.Handled = true;
}
@@ -79,13 +82,13 @@ public sealed class ActionOnInteractSystem : EntitySystem
}
// First, try entity target actions
- if (args.Target != null)
+ if (args.Target is {} target)
{
var entOptions = GetValidActions(actionEnts, args.CanReach);
for (var i = entOptions.Count - 1; i >= 0; i--)
{
var action = entOptions[i];
- if (!_actions.ValidateEntityTarget(args.User, args.Target.Value, action))
+ if (!_actions.ValidateEntityTarget(args.User, target, (action, action.Comp2)))
entOptions.RemoveAt(i);
}
@@ -94,50 +97,19 @@ public sealed class ActionOnInteractSystem : EntitySystem
if (!TryUseCharge((uid, component)))
return;
- var (entActId, entAct) = _random.Pick(entOptions);
- if (entAct.Event != null)
- {
- entAct.Event.Target = args.Target.Value;
- }
-
- _actions.PerformAction(args.User, null, entActId, entAct, entAct.Event, _timing.CurTime, false);
+ var (actionId, action, _) = _random.Pick(entOptions);
+ _actions.SetEventTarget(actionId, target);
+ _actions.PerformAction(args.User, (actionId, action), predicted: false);
args.Handled = true;
return;
}
}
-
- // Then EntityWorld target actions
- var entWorldOptions = GetValidActions(actionEnts, args.CanReach);
- for (var i = entWorldOptions.Count - 1; i >= 0; i--)
- {
- var action = entWorldOptions[i];
- if (!_actions.ValidateEntityWorldTarget(args.User, args.Target, args.ClickLocation, action))
- entWorldOptions.RemoveAt(i);
- }
-
- if (entWorldOptions.Count > 0)
- {
- if (!TryUseCharge((uid, component)))
- return;
-
- var (entActId, entAct) = _random.Pick(entWorldOptions);
- if (entAct.Event != null)
- {
- entAct.Event.Entity = args.Target;
- entAct.Event.Coords = args.ClickLocation;
- }
-
- _actions.PerformAction(args.User, null, entActId, entAct, entAct.Event, _timing.CurTime, false);
- args.Handled = true;
- return;
- }
-
// else: try world target actions
var options = GetValidActions(component.ActionEntities, args.CanReach);
for (var i = options.Count - 1; i >= 0; i--)
{
var action = options[i];
- if (!_actions.ValidateWorldTarget(args.User, args.ClickLocation, action))
+ if (!_actions.ValidateWorldTarget(args.User, args.ClickLocation, (action, action.Comp2)))
options.RemoveAt(i);
}
@@ -147,33 +119,34 @@ public sealed class ActionOnInteractSystem : EntitySystem
if (!TryUseCharge((uid, component)))
return;
- var (actId, act) = _random.Pick(options);
- if (act.Event != null)
+ var (actId, comp, world) = _random.Pick(options);
+ if (world.Event is {} worldEv)
{
- act.Event.Target = args.ClickLocation;
+ worldEv.Target = args.ClickLocation;
+ worldEv.Entity = HasComp(actId) ? args.Target : null;
}
- _actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
+ _actions.PerformAction(args.User, (actId, comp), world.Event, predicted: false);
args.Handled = true;
}
- private List<(EntityUid Id, T Comp)> GetValidActions(List? actions, bool canReach = true) where T : BaseActionComponent
+ private List> GetValidActions(List? actions, bool canReach = true) where T: Component
{
- var valid = new List<(EntityUid Id, T Comp)>();
+ var valid = new List>();
if (actions == null)
return valid;
foreach (var id in actions)
{
- if (!_actions.TryGetActionData(id, out var baseAction) ||
- baseAction as T is not { } action ||
+ if (_actions.GetAction(id) is not {} action ||
+ !TryComp(id, out var comp) ||
!_actions.ValidAction(action, canReach))
{
continue;
}
- valid.Add((id, action));
+ valid.Add((id, action, comp));
}
return valid;
diff --git a/Content.Server/Commands/ActionCommands.cs b/Content.Server/Commands/ActionCommands.cs
index fb50cfb3a3..93de66c856 100644
--- a/Content.Server/Commands/ActionCommands.cs
+++ b/Content.Server/Commands/ActionCommands.cs
@@ -1,5 +1,6 @@
-using Content.Server.Administration;
+using Content.Server.Administration;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Administration;
using Robust.Shared.Console;
diff --git a/Content.Server/NPC/Components/NPCUseActionOnTargetComponent.cs b/Content.Server/NPC/Components/NPCUseActionOnTargetComponent.cs
index f022a45ecc..649607f724 100644
--- a/Content.Server/NPC/Components/NPCUseActionOnTargetComponent.cs
+++ b/Content.Server/NPC/Components/NPCUseActionOnTargetComponent.cs
@@ -1,5 +1,5 @@
using Content.Server.NPC.Systems;
-using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.NPC.Components;
@@ -20,7 +20,7 @@ public sealed partial class NPCUseActionOnTargetComponent : Component
/// Action that's going to attempt to be used.
///
[DataField(required: true)]
- public EntProtoId ActionId;
+ public EntProtoId ActionId;
[DataField]
public EntityUid? ActionEnt;
diff --git a/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs b/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs
index 33bc8f9074..9822050f95 100644
--- a/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs
+++ b/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs
@@ -28,24 +28,16 @@ public sealed class NPCUseActionOnTargetSystem : EntitySystem
if (!Resolve(user, ref user.Comp, false))
return false;
- if (!TryComp(user.Comp.ActionEnt, out var action))
+ if (_actions.GetAction(user.Comp.ActionEnt) is not {} action)
return false;
if (!_actions.ValidAction(action))
return false;
- if (action.Event != null)
- {
- action.Event.Coords = Transform(target).Coordinates;
- }
+ _actions.SetEventTarget(action, target);
- _actions.PerformAction(user,
- null,
- user.Comp.ActionEnt.Value,
- action,
- action.BaseEvent,
- _timing.CurTime,
- false);
+ // NPC is serverside, no prediction :(
+ _actions.PerformAction(user.Owner, action, predicted: false);
return true;
}
diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs
index b87e3febee..5976594a57 100644
--- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs
+++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs
@@ -4,6 +4,7 @@ using Content.Server.Inventory;
using Content.Server.Mind.Commands;
using Content.Server.Polymorph.Components;
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Buckle;
using Content.Shared.Coordinates;
using Content.Shared.Damage;
@@ -111,8 +112,8 @@ public sealed partial class PolymorphSystem : EntitySystem
if (_actions.AddAction(uid, ref component.Action, out var action, RevertPolymorphId))
{
- action.EntityIcon = component.Parent;
- action.UseDelay = TimeSpan.FromSeconds(component.Configuration.Delay);
+ _actions.SetEntityIcon((component.Action.Value, action), component.Parent);
+ _actions.SetUseDelay(component.Action.Value, TimeSpan.FromSeconds(component.Configuration.Delay));
}
}
@@ -397,20 +398,19 @@ public sealed partial class PolymorphSystem : EntitySystem
_metaData.SetEntityName(actionId.Value, Loc.GetString("polymorph-self-action-name", ("target", entProto.Name)), metaDataCache);
_metaData.SetEntityDescription(actionId.Value, Loc.GetString("polymorph-self-action-description", ("target", entProto.Name)), metaDataCache);
- if (!_actions.TryGetActionData(actionId, out var baseAction))
+ if (_actions.GetAction(actionId) is not {} action)
return;
- baseAction.Icon = new SpriteSpecifier.EntityPrototype(polyProto.Configuration.Entity);
- if (baseAction is InstantActionComponent action)
- action.Event = new PolymorphActionEvent(id);
+ _actions.SetIcon((action, action.Comp), new SpriteSpecifier.EntityPrototype(polyProto.Configuration.Entity));
+ _actions.SetEvent(action, new PolymorphActionEvent(id));
}
public void RemovePolymorphAction(ProtoId id, Entity target)
{
- if (target.Comp.PolymorphActions == null)
+ if (target.Comp.PolymorphActions is not {} actions)
return;
- if (target.Comp.PolymorphActions.TryGetValue(id, out var val))
- _actions.RemoveAction(target, val);
+ if (actions.TryGetValue(id, out var action))
+ _actions.RemoveAction(target.Owner, action);
}
}
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
index f95a580736..784c2f8fbb 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
@@ -61,12 +61,10 @@ public sealed partial class BorgSystem
if (_actions.AddAction(chassis, ref component.ModuleSwapActionEntity, out var action, component.ModuleSwapActionId, uid))
{
- if(TryComp(uid, out var moduleIconComp))
- {
- action.Icon = moduleIconComp.Icon;
- };
- action.EntityIcon = uid;
- Dirty(component.ModuleSwapActionEntity.Value, action);
+ var actEnt = (component.ModuleSwapActionEntity.Value, action);
+ _actions.SetEntityIcon(actEnt, uid);
+ if (TryComp(uid, out var moduleIconComp))
+ _actions.SetIcon(actEnt, moduleIconComp.Icon);
}
if (!TryComp(chassis, out BorgChassisComponent? chassisComp))
diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs
index 587270bd64..f9772e48da 100644
--- a/Content.Server/Store/Systems/StoreSystem.Ui.cs
+++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs
@@ -350,10 +350,7 @@ public sealed partial class StoreSystem
component.BoughtEntities.RemoveAt(i);
- if (_actions.TryGetActionData(purchase, out var actionComponent, logError: false))
- {
- _actionContainer.RemoveAction(purchase, actionComponent);
- }
+ _actionContainer.RemoveAction(purchase, logMissing: false);
EntityManager.DeleteEntity(purchase);
}
diff --git a/Content.Shared/Abilities/Goliath/GoliathSummonTentacleAction.cs b/Content.Shared/Abilities/Goliath/GoliathSummonTentacleAction.cs
index abdb0d5e10..fa151b4f09 100644
--- a/Content.Shared/Abilities/Goliath/GoliathSummonTentacleAction.cs
+++ b/Content.Shared/Abilities/Goliath/GoliathSummonTentacleAction.cs
@@ -3,7 +3,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Abilities.Goliath;
-public sealed partial class GoliathSummonTentacleAction : EntityWorldTargetActionEvent
+public sealed partial class GoliathSummonTentacleAction : WorldTargetActionEvent
{
///
/// The ID of the entity that is spawned.
diff --git a/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs b/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs
index 98dbc5e18a..e0a0453285 100644
--- a/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs
+++ b/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs
@@ -28,7 +28,7 @@ public sealed class GoliathTentacleSystem : EntitySystem
private void OnSummonAction(GoliathSummonTentacleAction args)
{
- if (args.Handled || args.Coords is not { } coords)
+ if (args.Handled)
return;
// TODO: animation
@@ -36,6 +36,7 @@ public sealed class GoliathTentacleSystem : EntitySystem
_popup.PopupPredicted(Loc.GetString("tentacle-ability-use-popup", ("entity", args.Performer)), args.Performer, args.Performer, type: PopupType.SmallCaution);
_stun.TryStun(args.Performer, TimeSpan.FromSeconds(0.8f), false);
+ var coords = args.Target;
List spawnPos = new();
spawnPos.Add(coords);
diff --git a/Content.Shared/Actions/ActionContainerSystem.cs b/Content.Shared/Actions/ActionContainerSystem.cs
index 1a83cf38e5..534f0d3ee7 100644
--- a/Content.Shared/Actions/ActionContainerSystem.cs
+++ b/Content.Shared/Actions/ActionContainerSystem.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Content.Shared.Actions.Components;
using Content.Shared.Ghost;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
@@ -22,10 +23,14 @@ public sealed class ActionContainerSystem : EntitySystem
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
+ private EntityQuery _query;
+
public override void Initialize()
{
base.Initialize();
+ _query = GetEntityQuery();
+
SubscribeLocalEvent(OnInit);
SubscribeLocalEvent(OnShutdown);
SubscribeLocalEvent(OnEntityRemoved);
@@ -77,7 +82,7 @@ public sealed class ActionContainerSystem : EntitySystem
///
public bool EnsureAction(EntityUid uid,
[NotNullWhen(true)] ref EntityUid? actionId,
- [NotNullWhen(true)] out BaseActionComponent? action,
+ [NotNullWhen(true)] out ActionComponent? action,
string? actionPrototypeId,
ActionsContainerComponent? comp = null)
{
@@ -94,12 +99,14 @@ public sealed class ActionContainerSystem : EntitySystem
return false;
}
- if (!_actions.TryGetActionData(actionId, out action))
+ if (_actions.GetAction(actionId) is not {} ent)
return false;
- DebugTools.Assert(Transform(actionId.Value).ParentUid == uid);
- DebugTools.Assert(_container.IsEntityInContainer(actionId.Value));
- DebugTools.Assert(action.Container == uid);
+ actionId = ent;
+ action = ent.Comp;
+ DebugTools.Assert(Transform(ent).ParentUid == uid);
+ DebugTools.Assert(_container.IsEntityInContainer(ent));
+ DebugTools.Assert(ent.Comp.Container == uid);
return true;
}
@@ -112,7 +119,14 @@ public sealed class ActionContainerSystem : EntitySystem
return false;
actionId = Spawn(actionPrototypeId);
- if (AddAction(uid, actionId.Value, action, comp) && _actions.TryGetActionData(actionId, out action))
+ if (!_query.TryComp(actionId, out action))
+ {
+ Log.Error($"Tried to add invalid action {ToPrettyString(actionId)} to {ToPrettyString(uid)}!");
+ Del(actionId);
+ return false;
+ }
+
+ if (AddAction(uid, actionId.Value, action, comp))
return true;
Del(actionId.Value);
@@ -129,21 +143,21 @@ public sealed class ActionContainerSystem : EntitySystem
public void TransferAction(
EntityUid actionId,
EntityUid newContainer,
- BaseActionComponent? action = null,
+ ActionComponent? action = null,
ActionsContainerComponent? container = null)
{
- if (!_actions.ResolveActionData(actionId, ref action))
+ if (_actions.GetAction((actionId, action)) is not {} ent)
return;
- if (action.Container == newContainer)
+ if (ent.Comp.Container == newContainer)
return;
- var attached = action.AttachedEntity;
- if (!AddAction(newContainer, actionId, action, container))
+ var attached = ent.Comp.AttachedEntity;
+ if (!AddAction(newContainer, ent, ent.Comp, container))
return;
- DebugTools.AssertEqual(action.Container, newContainer);
- DebugTools.AssertEqual(action.AttachedEntity, attached);
+ DebugTools.AssertEqual(ent.Comp.Container, newContainer);
+ DebugTools.AssertEqual(ent.Comp.AttachedEntity, attached);
}
///
@@ -180,23 +194,23 @@ public sealed class ActionContainerSystem : EntitySystem
EntityUid actionId,
EntityUid newContainer,
EntityUid newAttached,
- BaseActionComponent? action = null,
+ ActionComponent? action = null,
ActionsContainerComponent? container = null)
{
- if (!_actions.ResolveActionData(actionId, ref action))
+ if (_actions.GetAction((actionId, action)) is not {} ent)
return;
- if (action.Container == newContainer)
+ if (ent.Comp.Container == newContainer)
return;
var attached = newAttached;
- if (!AddAction(newContainer, actionId, action, container))
+ if (!AddAction(newContainer, ent, ent.Comp, container))
return;
- DebugTools.AssertEqual(action.Container, newContainer);
- _actions.AddActionDirect(newAttached, actionId, action: action);
+ DebugTools.AssertEqual(ent.Comp.Container, newContainer);
+ _actions.AddActionDirect(newAttached, (ent, ent.Comp));
- DebugTools.AssertEqual(action.AttachedEntity, attached);
+ DebugTools.AssertEqual(ent.Comp.AttachedEntity, attached);
}
///
@@ -227,25 +241,25 @@ public sealed class ActionContainerSystem : EntitySystem
///
/// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it.
///
- public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? action = null, ActionsContainerComponent? comp = null)
+ public bool AddAction(EntityUid uid, EntityUid actionId, ActionComponent? action = null, ActionsContainerComponent? comp = null)
{
- if (!_actions.ResolveActionData(actionId, ref action))
+ if (_actions.GetAction((actionId, action)) is not {} ent)
return false;
- if (action.Container != null)
- RemoveAction(actionId, action);
+ if (ent.Comp.Container != null)
+ RemoveAction((ent, ent));
DebugTools.AssertOwner(uid, comp);
comp ??= EnsureComp(uid);
- if (!_container.Insert(actionId, comp.Container))
+ if (!_container.Insert(ent.Owner, comp.Container))
{
- Log.Error($"Failed to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}");
+ Log.Error($"Failed to insert action {ToPrettyString(ent)} into {ToPrettyString(uid)}");
return false;
}
// Container insert events should have updated the component's fields:
- DebugTools.Assert(comp.Container.Contains(actionId));
- DebugTools.Assert(action.Container == uid);
+ DebugTools.Assert(comp.Container.Contains(ent));
+ DebugTools.Assert(ent.Comp.Container == uid);
return true;
}
@@ -253,30 +267,31 @@ public sealed class ActionContainerSystem : EntitySystem
///
/// Removes an action from its container and any action-performer and moves the action to null-space
///
- public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null)
+ public void RemoveAction(Entity? action, bool logMissing = true)
{
- if (!_actions.ResolveActionData(actionId, ref action))
+ if (_actions.GetAction(action, logMissing) is not {} ent)
return;
- if (action.Container == null)
+ if (ent.Comp.Container == null)
return;
- _transform.DetachEntity(actionId, Transform(actionId));
+ _transform.DetachEntity(ent, Transform(ent));
// Container removal events should have removed the action from the action container.
// However, just in case the container was already deleted we will still manually clear the container field
- if (action.Container != null)
+ if (ent.Comp.Container is {} container)
{
- if (Exists(action.Container))
- Log.Error($"Failed to remove action {ToPrettyString(actionId)} from its container {ToPrettyString(action.Container)}?");
- action.Container = null;
+ if (Exists(container))
+ Log.Error($"Failed to remove action {ToPrettyString(ent)} from its container {ToPrettyString(container)}?");
+ ent.Comp.Container = null;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Container));
}
// If the action was granted to some entity, then the removal from the container should have automatically removed it.
// However, if the action was granted without ever being placed in an action container, it will not have been removed.
// Therefore, to ensure that the behaviour of the method is consistent we will also explicitly remove the action.
- if (action.AttachedEntity != null)
- _actions.RemoveAction(action.AttachedEntity.Value, actionId, action: action);
+ if (ent.Comp.AttachedEntity is {} actions)
+ _actions.RemoveAction(actions, (ent, ent));
}
private void OnInit(EntityUid uid, ActionsContainerComponent component, ComponentInit args)
@@ -297,16 +312,16 @@ public sealed class ActionContainerSystem : EntitySystem
if (args.Container.ID != ActionsContainerComponent.ContainerId)
return;
- if (!_actions.TryGetActionData(args.Entity, out var data))
+ if (_actions.GetAction(args.Entity) is not {} action)
return;
- if (data.Container != uid)
+ if (action.Comp.Container != uid)
{
- data.Container = uid;
- Dirty(args.Entity, data);
+ action.Comp.Container = uid;
+ DirtyField(action, action.Comp, nameof(ActionComponent.Container));
}
- var ev = new ActionAddedEvent(args.Entity, data);
+ var ev = new ActionAddedEvent(args.Entity, action);
RaiseLocalEvent(uid, ref ev);
}
@@ -315,17 +330,17 @@ public sealed class ActionContainerSystem : EntitySystem
if (args.Container.ID != ActionsContainerComponent.ContainerId)
return;
- if (!_actions.TryGetActionData(args.Entity, out var data, false))
+ if (_actions.GetAction(args.Entity, false) is not {} action)
return;
- var ev = new ActionRemovedEvent(args.Entity, data);
+ var ev = new ActionRemovedEvent(args.Entity, action);
RaiseLocalEvent(uid, ref ev);
- if (data.Container == null)
+ if (action.Comp.Container == null)
return;
- data.Container = null;
- Dirty(args.Entity, data);
+ action.Comp.Container = null;
+ DirtyField(action, action.Comp, nameof(ActionComponent.Container));
}
private void OnActionAdded(EntityUid uid, ActionsContainerComponent component, ActionAddedEvent args)
@@ -342,9 +357,9 @@ public sealed class ActionContainerSystem : EntitySystem
public readonly struct ActionAddedEvent
{
public readonly EntityUid Action;
- public readonly BaseActionComponent Component;
+ public readonly ActionComponent Component;
- public ActionAddedEvent(EntityUid action, BaseActionComponent component)
+ public ActionAddedEvent(EntityUid action, ActionComponent component)
{
Action = action;
Component = component;
@@ -358,9 +373,9 @@ public readonly struct ActionAddedEvent
public readonly struct ActionRemovedEvent
{
public readonly EntityUid Action;
- public readonly BaseActionComponent Component;
+ public readonly ActionComponent Component;
- public ActionRemovedEvent(EntityUid action, BaseActionComponent component)
+ public ActionRemovedEvent(EntityUid action, ActionComponent component)
{
Action = action;
Component = component;
diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs
index 6ff8660458..3e03911b0a 100644
--- a/Content.Shared/Actions/ActionEvents.cs
+++ b/Content.Shared/Actions/ActionEvents.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Actions.Components;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
@@ -102,7 +103,7 @@ public sealed class RequestPerformActionEvent : EntityEventArgs
EntityCoordinatesTarget = entityCoordinatesTarget;
}
- public RequestPerformActionEvent(NetEntity action, NetEntity entityTarget, NetCoordinates entityCoordinatesTarget)
+ public RequestPerformActionEvent(NetEntity action, NetEntity? entityTarget, NetCoordinates entityCoordinatesTarget)
{
Action = action;
EntityTarget = entityTarget;
@@ -149,27 +150,12 @@ public abstract partial class WorldTargetActionEvent : BaseActionEvent
/// The coordinates of the location that the user targeted.
///
public EntityCoordinates Target;
-}
-///
-/// This is the type of event that gets raised when an is performed.
-/// The , , and
-/// fields will automatically be filled out by the .
-///
-///
-/// To define a new action for some system, you need to create an event that inherits from this class.
-///
-public abstract partial class EntityWorldTargetActionEvent : BaseActionEvent
-{
///
- /// The entity that the user targeted.
+ /// When combined with (and Event is null), the entity the client was hovering when clicked.
+ /// This can be null as the primary purpose of this event is for getting coordinates.
///
public EntityUid? Entity;
-
- ///
- /// The coordinates of the location that the user targeted.
- ///
- public EntityCoordinates? Coords;
}
///
@@ -187,7 +173,7 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs
///
/// The action the event belongs to.
///
- public Entity Action;
+ public Entity Action;
///
/// Should we toggle the action entity?
diff --git a/Content.Shared/Actions/ActionUpgradeSystem.cs b/Content.Shared/Actions/ActionUpgradeSystem.cs
index b89b462814..72e412b50f 100644
--- a/Content.Shared/Actions/ActionUpgradeSystem.cs
+++ b/Content.Shared/Actions/ActionUpgradeSystem.cs
@@ -1,5 +1,6 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Content.Shared.Actions.Components;
using Content.Shared.Actions.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -22,13 +23,13 @@ public sealed class ActionUpgradeSystem : EntitySystem
private void OnActionUpgradeEvent(EntityUid uid, ActionUpgradeComponent component, ActionUpgradeEvent args)
{
if (!CanUpgrade(args.NewLevel, component.EffectedLevels, out var newActionProto)
- || !_actions.TryGetActionData(uid, out var actionComp))
+ || _actions.GetAction(uid) is not {} action)
return;
- var originalContainer = actionComp.Container;
- var originalAttachedEntity = actionComp.AttachedEntity;
+ var originalContainer = action.Comp.Container;
+ var originalAttachedEntity = action.Comp.AttachedEntity;
- _actionContainer.RemoveAction(uid, actionComp);
+ _actionContainer.RemoveAction((action, action));
EntityUid? upgradedActionId = null;
if (originalContainer != null
@@ -150,16 +151,16 @@ public sealed class ActionUpgradeSystem : EntitySystem
// RaiseActionUpgradeEvent(newLevel, actionId.Value);
if (!CanUpgrade(newLevel, actionUpgradeComponent.EffectedLevels, out var newActionPrototype)
- || !_actions.TryGetActionData(actionId, out var actionComp))
+ || _actions.GetAction(actionId) is not {} action)
return null;
newActionProto ??= newActionPrototype;
DebugTools.AssertNotNull(newActionProto);
- var originalContainer = actionComp.Container;
- var originalAttachedEntity = actionComp.AttachedEntity;
+ var originalContainer = action.Comp.Container;
+ var originalAttachedEntity = action.Comp.AttachedEntity;
- _actionContainer.RemoveAction(actionId.Value, actionComp);
+ _actionContainer.RemoveAction((action, action.Comp));
EntityUid? upgradedActionId = null;
if (originalContainer != null
diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/Components/ActionComponent.cs
similarity index 61%
rename from Content.Shared/Actions/BaseActionComponent.cs
rename to Content.Shared/Actions/Components/ActionComponent.cs
index 05abe30f24..2833aa1798 100644
--- a/Content.Shared/Actions/BaseActionComponent.cs
+++ b/Content.Shared/Actions/Components/ActionComponent.cs
@@ -1,35 +1,39 @@
-using Robust.Shared.Audio;
+using Content.Shared.Actions;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
-namespace Content.Shared.Actions;
+namespace Content.Shared.Actions.Components;
-// TODO ACTIONS make this a separate component and remove the inheritance stuff.
-// TODO ACTIONS convert to auto comp state?
-
-// TODO add access attribute. Need to figure out what to do with decal & mapping actions.
-// [Access(typeof(SharedActionsSystem))]
+///
+/// Component all actions are required to have.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
+[AutoGenerateComponentState(true, true)]
[EntityCategory("Actions")]
-public abstract partial class BaseActionComponent : Component
+public sealed partial class ActionComponent : Component
{
- public abstract BaseActionEvent? BaseEvent { get; }
-
///
/// Icon representing this action in the UI.
///
- [DataField("icon")] public SpriteSpecifier? Icon;
+ [DataField, AutoNetworkedField]
+ public SpriteSpecifier? Icon;
///
/// For toggle actions only, icon to show when toggled on. If omitted, the action will simply be highlighted
/// when turned on.
///
- [DataField("iconOn")] public SpriteSpecifier? IconOn;
+ [DataField, AutoNetworkedField]
+ public SpriteSpecifier? IconOn;
///
/// For toggle actions only, background to show when toggled on.
///
- [DataField] public SpriteSpecifier? BackgroundOn;
+ [DataField]
+ public SpriteSpecifier? BackgroundOn;
///
/// If not null, this color will modulate the action icon color.
@@ -38,12 +42,14 @@ public abstract partial class BaseActionComponent : Component
/// This currently only exists for decal-placement actions, so that the action icons correspond to the color of
/// the decal. But this is probably useful for other actions, including maybe changing color on toggle.
///
- [DataField("iconColor")] public Color IconColor = Color.White;
+ [DataField, AutoNetworkedField]
+ public Color IconColor = Color.White;
///
/// The original this action was.
///
- [DataField] public Color OriginalIconColor;
+ [DataField, AutoNetworkedField]
+ public Color OriginalIconColor;
///
/// The color the action should turn to when disabled
@@ -53,12 +59,14 @@ public abstract partial class BaseActionComponent : Component
///
/// Keywords that can be used to search for this action in the action menu.
///
- [DataField("keywords")] public HashSet Keywords = new();
+ [DataField, AutoNetworkedField]
+ public HashSet Keywords = new();
///
/// Whether this action is currently enabled. If not enabled, this action cannot be performed.
///
- [DataField("enabled")] public bool Enabled = true;
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
///
/// The toggle state of this action. Toggling switches the currently displayed icon, see and .
@@ -67,14 +75,14 @@ public abstract partial class BaseActionComponent : Component
/// The toggle can set directly via , but it will also be
/// automatically toggled for targeted-actions while selecting a target.
///
- [DataField]
+ [DataField, AutoNetworkedField]
public bool Toggled;
///
/// The current cooldown on the action.
///
- // TODO serialization
- public (TimeSpan Start, TimeSpan End)? Cooldown;
+ [DataField, AutoNetworkedField]
+ public ActionCooldown? Cooldown;
///
/// If true, the action will have an initial cooldown applied upon addition.
@@ -84,14 +92,15 @@ public abstract partial class BaseActionComponent : Component
///
/// Time interval between action uses.
///
- [DataField("useDelay")] public TimeSpan? UseDelay;
+ [DataField, AutoNetworkedField]
+ public TimeSpan? UseDelay;
///
/// The entity that contains this action. If the action is innate, this may be the user themselves.
/// This should almost always be non-null.
///
[Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
- [DataField]
+ [DataField, AutoNetworkedField]
public EntityUid? Container;
///
@@ -113,40 +122,45 @@ public abstract partial class BaseActionComponent : Component
set => EntIcon = value;
}
- [DataField]
+ [DataField, AutoNetworkedField]
public EntityUid? EntIcon;
///
/// Whether the action system should block this action if the user cannot currently interact. Some spells or
/// abilities may want to disable this and implement their own checks.
///
- [DataField("checkCanInteract")] public bool CheckCanInteract = true;
+ [DataField, AutoNetworkedField]
+ public bool CheckCanInteract = true;
///
/// Whether to check if the user is conscious or not. Can be used instead of
/// for a more permissive check.
///
- [DataField] public bool CheckConsciousness = true;
+ [DataField, AutoNetworkedField]
+ public bool CheckConsciousness = true;
///
/// If true, this will cause the action to only execute locally without ever notifying the server.
///
- [DataField("clientExclusive")] public bool ClientExclusive = false;
+ [DataField, AutoNetworkedField]
+ public bool ClientExclusive;
///
/// Determines the order in which actions are automatically added the action bar.
///
- [DataField("priority")] public int Priority = 0;
+ [DataField, AutoNetworkedField]
+ public int Priority = 0;
///
/// What entity, if any, currently has this action in the actions component?
///
- [DataField] public EntityUid? AttachedEntity;
+ [DataField, AutoNetworkedField]
+ public EntityUid? AttachedEntity;
///
/// If true, this will cause the the action event to always be raised directed at the action performer/user instead of the action's container/provider.
///
- [DataField]
+ [DataField, AutoNetworkedField]
public bool RaiseOnUser;
///
@@ -160,75 +174,34 @@ public abstract partial class BaseActionComponent : Component
///
/// Whether or not to automatically add this action to the action bar when it becomes available.
///
- [DataField("autoPopulate")] public bool AutoPopulate = true;
+ [DataField, AutoNetworkedField]
+ public bool AutoPopulate = true;
///
/// Temporary actions are deleted when they get removed a .
///
- [DataField("temporary")] public bool Temporary;
+ [DataField, AutoNetworkedField]
+ public bool Temporary;
///
/// Determines the appearance of the entity-icon for actions that are enabled via some entity.
///
- [DataField("itemIconStyle")] public ItemActionIconStyle ItemIconStyle;
+ [DataField, AutoNetworkedField]
+ public ItemActionIconStyle ItemIconStyle;
///
/// If not null, this sound will be played when performing this action.
///
- [DataField("sound")] public SoundSpecifier? Sound;
-}
-
-[Serializable, NetSerializable]
-public abstract class BaseActionComponentState : ComponentState
-{
- public SpriteSpecifier? Icon;
- public SpriteSpecifier? IconOn;
- public Color IconColor;
- public Color OriginalIconColor;
- public Color DisabledIconColor;
- public HashSet Keywords;
- public bool Enabled;
- public bool Toggled;
- public (TimeSpan Start, TimeSpan End)? Cooldown;
- public TimeSpan? UseDelay;
- public NetEntity? Container;
- public NetEntity? EntityIcon;
- public bool CheckCanInteract;
- public bool CheckConsciousness;
- public bool ClientExclusive;
- public int Priority;
- public NetEntity? AttachedEntity;
- public bool RaiseOnUser;
- public bool RaiseOnAction;
- public bool AutoPopulate;
- public bool Temporary;
- public ItemActionIconStyle ItemIconStyle;
+ [DataField, AutoNetworkedField]
public SoundSpecifier? Sound;
-
- protected BaseActionComponentState(BaseActionComponent component, IEntityManager entManager)
- {
- Container = entManager.GetNetEntity(component.Container);
- EntityIcon = entManager.GetNetEntity(component.EntIcon);
- AttachedEntity = entManager.GetNetEntity(component.AttachedEntity);
- RaiseOnUser = component.RaiseOnUser;
- RaiseOnAction = component.RaiseOnAction;
- Icon = component.Icon;
- IconOn = component.IconOn;
- IconColor = component.IconColor;
- OriginalIconColor = component.OriginalIconColor;
- DisabledIconColor = component.DisabledIconColor;
- Keywords = component.Keywords;
- Enabled = component.Enabled;
- Toggled = component.Toggled;
- Cooldown = component.Cooldown;
- UseDelay = component.UseDelay;
- CheckCanInteract = component.CheckCanInteract;
- CheckConsciousness = component.CheckConsciousness;
- ClientExclusive = component.ClientExclusive;
- Priority = component.Priority;
- AutoPopulate = component.AutoPopulate;
- Temporary = component.Temporary;
- ItemIconStyle = component.ItemIconStyle;
- Sound = component.Sound;
- }
+}
+
+[DataRecord, Serializable, NetSerializable]
+public record struct ActionCooldown
+{
+ [DataField(required: true, customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan Start;
+
+ [DataField(required: true, customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan End;
}
diff --git a/Content.Shared/Actions/ActionContainerComponent.cs b/Content.Shared/Actions/Components/ActionContainerComponent.cs
similarity index 66%
rename from Content.Shared/Actions/ActionContainerComponent.cs
rename to Content.Shared/Actions/Components/ActionContainerComponent.cs
index c18d1ead62..f7f8120316 100644
--- a/Content.Shared/Actions/ActionContainerComponent.cs
+++ b/Content.Shared/Actions/Components/ActionContainerComponent.cs
@@ -1,13 +1,13 @@
+using Content.Shared.Actions;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
-namespace Content.Shared.Actions;
+namespace Content.Shared.Actions.Components;
///
/// This component indicates that this entity contains actions inside of some container.
///
-[NetworkedComponent, RegisterComponent]
-[Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
+[NetworkedComponent, RegisterComponent, Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
public sealed partial class ActionsContainerComponent : Component
{
public const string ContainerId = "actions";
diff --git a/Content.Shared/Actions/ActionUpgradeComponent.cs b/Content.Shared/Actions/Components/ActionUpgradeComponent.cs
similarity index 68%
rename from Content.Shared/Actions/ActionUpgradeComponent.cs
rename to Content.Shared/Actions/Components/ActionUpgradeComponent.cs
index d12ce339c8..72a3447a83 100644
--- a/Content.Shared/Actions/ActionUpgradeComponent.cs
+++ b/Content.Shared/Actions/Components/ActionUpgradeComponent.cs
@@ -1,24 +1,29 @@
+using Content.Shared.Actions;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-namespace Content.Shared.Actions;
+namespace Content.Shared.Actions.Components;
-// For actions that can use basic upgrades
-// Not all actions should be upgradable
+///
+/// For actions that can use basic upgrades
+/// Not all actions should be upgradable
+/// Requires .
+///
[RegisterComponent, NetworkedComponent, Access(typeof(ActionUpgradeSystem))]
+[EntityCategory("Actions")]
public sealed partial class ActionUpgradeComponent : Component
{
///
/// Current Level of the action.
///
- [ViewVariables]
+ [DataField]
public int Level = 1;
///
/// What level(s) effect this action?
/// You can skip levels, so you can have this entity change at level 2 but then won't change again until level 5.
///
- [DataField("effectedLevels"), ViewVariables]
+ [DataField]
public Dictionary EffectedLevels = new();
// TODO: Branching level upgrades
diff --git a/Content.Shared/Actions/ActionsComponent.cs b/Content.Shared/Actions/Components/ActionsComponent.cs
similarity index 78%
rename from Content.Shared/Actions/ActionsComponent.cs
rename to Content.Shared/Actions/Components/ActionsComponent.cs
index a081a23867..f4c41261cd 100644
--- a/Content.Shared/Actions/ActionsComponent.cs
+++ b/Content.Shared/Actions/Components/ActionsComponent.cs
@@ -1,18 +1,21 @@
+using Content.Shared.Actions;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
-namespace Content.Shared.Actions;
+namespace Content.Shared.Actions.Components;
-[NetworkedComponent]
-[RegisterComponent]
-[Access(typeof(SharedActionsSystem))]
+///
+/// Lets the player controlling this entity use actions.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
public sealed partial class ActionsComponent : Component
{
///
/// List of actions currently granted to this entity.
/// On the client, this may contain a mixture of client-side and networked entities.
///
- [DataField] public HashSet Actions = new();
+ [DataField]
+ public HashSet Actions = new();
}
[Serializable, NetSerializable]
diff --git a/Content.Shared/Actions/ConfirmableActionComponent.cs b/Content.Shared/Actions/Components/ConfirmableActionComponent.cs
similarity index 90%
rename from Content.Shared/Actions/ConfirmableActionComponent.cs
rename to Content.Shared/Actions/Components/ConfirmableActionComponent.cs
index 6c208f47c6..a681062368 100644
--- a/Content.Shared/Actions/ConfirmableActionComponent.cs
+++ b/Content.Shared/Actions/Components/ConfirmableActionComponent.cs
@@ -1,15 +1,19 @@
+using Content.Shared.Actions;
using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.Actions;
+namespace Content.Shared.Actions.Components;
///
/// An action that must be confirmed before using it.
/// Using it for the first time primes it, after a delay you can then confirm it.
/// Used for dangerous actions that cannot be undone (unlike screaming).
+/// Requires .
///
[RegisterComponent, NetworkedComponent, Access(typeof(ConfirmableActionSystem))]
[AutoGenerateComponentState, AutoGenerateComponentPause]
+[EntityCategory("Actions")]
public sealed partial class ConfirmableActionComponent : Component
{
///
diff --git a/Content.Shared/Actions/Components/EntityTargetActionComponent.cs b/Content.Shared/Actions/Components/EntityTargetActionComponent.cs
new file mode 100644
index 0000000000..d32205d157
--- /dev/null
+++ b/Content.Shared/Actions/Components/EntityTargetActionComponent.cs
@@ -0,0 +1,48 @@
+using Content.Shared.Actions;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Actions.Components;
+
+///
+/// Used on action entities to define an action that triggers when targeting an entity.
+/// If used with , the event here can be set to null and Optional should be set.
+/// Then can have TargetEntity optionally set to the client's hovered entity, if it is valid.
+/// Using entity-world targeting like this will always give coords, but doesn't need to have an entity.
+///
+///
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
+[EntityCategory("Actions")]
+[AutoGenerateComponentState]
+public sealed partial class EntityTargetActionComponent : Component
+{
+ ///
+ /// The local-event to raise when this action is performed.
+ /// If this is null entity-world targeting is done as specified on the component doc.
+ ///
+ [DataField, NonSerialized]
+ public EntityTargetActionEvent? Event;
+
+ ///
+ /// Determines which entities are valid targets for this action.
+ ///
+ /// No whitelist check when null.
+ [DataField, AutoNetworkedField]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// Determines which entities cannot be valid targets for this action, even if matching the whitelist.
+ ///
+ /// No blacklist check when null.
+ [DataField, AutoNetworkedField]
+ public EntityWhitelist? Blacklist;
+
+ ///
+ /// Whether this action considers the user as a valid target entity when using this action.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CanTargetSelf = true;
+}
diff --git a/Content.Shared/Actions/Components/InstantActionComponent.cs b/Content.Shared/Actions/Components/InstantActionComponent.cs
new file mode 100644
index 0000000000..0634b3172c
--- /dev/null
+++ b/Content.Shared/Actions/Components/InstantActionComponent.cs
@@ -0,0 +1,20 @@
+using Content.Shared.Actions;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Actions.Components;
+
+///
+/// An action that raises an event as soon as it gets used.
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
+[EntityCategory("Actions")]
+public sealed partial class InstantActionComponent : Component
+{
+ ///
+ /// The local-event to raise when this action is performed.
+ ///
+ [DataField(required: true), NonSerialized]
+ public InstantActionEvent? Event;
+}
diff --git a/Content.Shared/Actions/BaseTargetActionComponent.cs b/Content.Shared/Actions/Components/TargetActionComponent.cs
similarity index 65%
rename from Content.Shared/Actions/BaseTargetActionComponent.cs
rename to Content.Shared/Actions/Components/TargetActionComponent.cs
index 7e40b10c32..0cb9de4946 100644
--- a/Content.Shared/Actions/BaseTargetActionComponent.cs
+++ b/Content.Shared/Actions/Components/TargetActionComponent.cs
@@ -1,19 +1,30 @@
+using Content.Shared.Actions;
using Content.Shared.Interaction;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
-namespace Content.Shared.Actions;
+namespace Content.Shared.Actions.Components;
-public abstract partial class BaseTargetActionComponent : BaseActionComponent
+///
+/// An action that targets an entity or map.
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
+[EntityCategory("Actions")]
+public sealed partial class TargetActionComponent : Component
{
///
/// For entity- or map-targeting actions, if this is true the action will remain selected after it is used, so
/// it can be continuously re-used. If this is false, the action will be deselected after one use.
///
- [DataField("repeat")] public bool Repeat;
+ [DataField]
+ public bool Repeat;
///
/// For entity- or map-targeting action, determines whether the action is deselected if the user doesn't click a valid target.
///
- [DataField("deselectOnMiss")] public bool DeselectOnMiss;
+ [DataField]
+ public bool DeselectOnMiss;
///
/// Whether the action system should block this action if the user cannot actually access the target
@@ -23,9 +34,11 @@ public abstract partial class BaseTargetActionComponent : BaseActionComponent
///
/// Even if this is false, the will still be checked.
///
- [DataField("checkCanAccess")] public bool CheckCanAccess = true;
+ [DataField]
+ public bool CheckCanAccess = true;
- [DataField("range")] public float Range = SharedInteractionSystem.InteractionRange;
+ [DataField]
+ public float Range = SharedInteractionSystem.InteractionRange;
///
/// If the target is invalid, this bool determines whether the left-click will default to performing a standard-interaction
@@ -33,11 +46,13 @@ public abstract partial class BaseTargetActionComponent : BaseActionComponent
///
/// Interactions will still be blocked if the target-validation generates a pop-up
///
- [DataField("interactOnMiss")] public bool InteractOnMiss = false;
+ [DataField]
+ public bool InteractOnMiss;
///
/// If true, and if is enabled, then this action's icon will be drawn by that
/// over lay in place of the currently held item "held item".
///
- [DataField("targetingIndicator")] public bool TargetingIndicator = true;
+ [DataField]
+ public bool TargetingIndicator = true;
}
diff --git a/Content.Shared/Actions/Components/WorldTargetActionComponent.cs b/Content.Shared/Actions/Components/WorldTargetActionComponent.cs
new file mode 100644
index 0000000000..6ec201dcb8
--- /dev/null
+++ b/Content.Shared/Actions/Components/WorldTargetActionComponent.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Actions;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Actions.Components;
+
+///
+/// Used on action entities to define an action that triggers when targeting an entity coordinate.
+/// Can be combined with , see its docs for more information.
+///
+///
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
+[EntityCategory("Actions")]
+public sealed partial class WorldTargetActionComponent : Component
+{
+ ///
+ /// The local-event to raise when this action is performed.
+ ///
+ [DataField(required: true), NonSerialized]
+ public WorldTargetActionEvent? Event;
+}
diff --git a/Content.Shared/Actions/ConfirmableActionSystem.cs b/Content.Shared/Actions/ConfirmableActionSystem.cs
index 26cc7111d2..eaffc47549 100644
--- a/Content.Shared/Actions/ConfirmableActionSystem.cs
+++ b/Content.Shared/Actions/ConfirmableActionSystem.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Actions.Components;
using Content.Shared.Actions.Events;
using Content.Shared.Popups;
using Robust.Shared.Timing;
diff --git a/Content.Shared/Actions/EntityTargetActionComponent.cs b/Content.Shared/Actions/EntityTargetActionComponent.cs
deleted file mode 100644
index aa8c0f4a99..0000000000
--- a/Content.Shared/Actions/EntityTargetActionComponent.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Content.Shared.Whitelist;
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Actions;
-
-///
-/// Used on action entities to define an action that triggers when targeting an entity.
-///
-[RegisterComponent, NetworkedComponent]
-public sealed partial class EntityTargetActionComponent : BaseTargetActionComponent
-{
- public override BaseActionEvent? BaseEvent => Event;
-
- ///
- /// The local-event to raise when this action is performed.
- ///
- [DataField("event")]
- [NonSerialized]
- public EntityTargetActionEvent? Event;
-
- ///
- /// Determines which entities are valid targets for this action.
- ///
- /// No whitelist check when null.
- [DataField("whitelist")] public EntityWhitelist? Whitelist;
-
- ///
- /// Determines which entities are NOT valid targets for this action.
- ///
- /// No blacklist check when null.
- [DataField] public EntityWhitelist? Blacklist;
-
- ///
- /// Whether this action considers the user as a valid target entity when using this action.
- ///
- [DataField("canTargetSelf")] public bool CanTargetSelf = true;
-}
-
-[Serializable, NetSerializable]
-public sealed class EntityTargetActionComponentState : BaseActionComponentState
-{
- public EntityWhitelist? Whitelist;
- public EntityWhitelist? Blacklist;
- public bool CanTargetSelf;
-
- public EntityTargetActionComponentState(EntityTargetActionComponent component, IEntityManager entManager) : base(component, entManager)
- {
- Whitelist = component.Whitelist;
- Blacklist = component.Blacklist;
- CanTargetSelf = component.CanTargetSelf;
- }
-}
diff --git a/Content.Shared/Actions/EntityWorldTargetActionComponent.cs b/Content.Shared/Actions/EntityWorldTargetActionComponent.cs
deleted file mode 100644
index 3cfa60d030..0000000000
--- a/Content.Shared/Actions/EntityWorldTargetActionComponent.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Content.Shared.Whitelist;
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Actions;
-
-///
-/// Used on action entities to define an action that triggers when targeting an entity or entity coordinates.
-///
-[RegisterComponent, NetworkedComponent]
-public sealed partial class EntityWorldTargetActionComponent : BaseTargetActionComponent
-{
- public override BaseActionEvent? BaseEvent => Event;
-
- ///
- /// The local-event to raise when this action is performed.
- ///
- [DataField]
- [NonSerialized]
- public EntityWorldTargetActionEvent? Event;
-
- ///
- /// Determines which entities are valid targets for this action.
- ///
- /// No whitelist check when null.
- [DataField] public EntityWhitelist? Whitelist;
-
- ///
- /// Whether this action considers the user as a valid target entity when using this action.
- ///
- [DataField] public bool CanTargetSelf = true;
-}
-
-[Serializable, NetSerializable]
-public sealed class EntityWorldTargetActionComponentState(
- EntityWorldTargetActionComponent component,
- IEntityManager entManager)
- : BaseActionComponentState(component, entManager)
-{
- public EntityWhitelist? Whitelist = component.Whitelist;
- public bool CanTargetSelf = component.CanTargetSelf;
-}
diff --git a/Content.Shared/Actions/Events/ActionAttemptEvent.cs b/Content.Shared/Actions/Events/ActionAttemptEvent.cs
index 26f23f9ec3..98db50fd39 100644
--- a/Content.Shared/Actions/Events/ActionAttemptEvent.cs
+++ b/Content.Shared/Actions/Events/ActionAttemptEvent.cs
@@ -2,7 +2,7 @@ namespace Content.Shared.Actions.Events;
///
/// Raised before an action is used and can be cancelled to prevent it.
-/// Allowed to have side effects like modifying the action component.
+/// Allowed to have side effects like modifying the action components.
///
[ByRefEvent]
public record struct ActionAttemptEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Actions/Events/ActionGetEventEvent.cs b/Content.Shared/Actions/Events/ActionGetEventEvent.cs
new file mode 100644
index 0000000000..df231d3ef5
--- /dev/null
+++ b/Content.Shared/Actions/Events/ActionGetEventEvent.cs
@@ -0,0 +1,7 @@
+namespace Content.Shared.Actions.Events;
+
+///
+/// Raised on an action entity to get its event.
+///
+[ByRefEvent]
+public record struct ActionGetEventEvent(BaseActionEvent? Event = null);
diff --git a/Content.Shared/Actions/Events/ActionSetEventEvent.cs b/Content.Shared/Actions/Events/ActionSetEventEvent.cs
new file mode 100644
index 0000000000..b33a548ec4
--- /dev/null
+++ b/Content.Shared/Actions/Events/ActionSetEventEvent.cs
@@ -0,0 +1,8 @@
+namespace Content.Shared.Actions.Events;
+
+///
+/// Raised on an action entity to have the event-holding component cast and set its event.
+/// If it was set successfully then Handled must be set to true.
+///
+[ByRefEvent]
+public record struct ActionSetEventEvent(BaseActionEvent Event, bool Handled = false);
diff --git a/Content.Shared/Actions/Events/ActionSetTargetEvent.cs b/Content.Shared/Actions/Events/ActionSetTargetEvent.cs
new file mode 100644
index 0000000000..9868c804fc
--- /dev/null
+++ b/Content.Shared/Actions/Events/ActionSetTargetEvent.cs
@@ -0,0 +1,8 @@
+namespace Content.Shared.Actions.Events;
+
+///
+/// Raised on an action entity to set its event's target to an entity, if it makes sense.
+/// Does nothing for an instant action as it has no target.
+///
+[ByRefEvent]
+public record struct ActionSetTargetEvent(EntityUid Target, bool Handled = false);
diff --git a/Content.Shared/Actions/Events/ActionValidateEvent.cs b/Content.Shared/Actions/Events/ActionValidateEvent.cs
new file mode 100644
index 0000000000..fa60a83c3f
--- /dev/null
+++ b/Content.Shared/Actions/Events/ActionValidateEvent.cs
@@ -0,0 +1,32 @@
+namespace Content.Shared.Actions.Events;
+
+///
+/// Raised on an action entity before being used to:
+/// 1. Make sure client is sending the correct kind of target (if any)
+/// 2. Do any validation on the target, if needed
+/// 3. Give the action system an event to raise on the performer, to actually do the action.
+///
+[ByRefEvent]
+public struct ActionValidateEvent
+{
+ ///
+ /// Request event the client sent.
+ ///
+ public RequestPerformActionEvent Input;
+
+ ///
+ /// User trying to use the action.
+ ///
+ public EntityUid User;
+
+ ///
+ /// Entity providing this action to the user, used for logging.
+ ///
+ public EntityUid Provider;
+
+ ///
+ /// If set to true, the client sent invalid event data and this should be logged as an error.
+ /// For functioning input that happens to not be allowed this should not be set, for example a range check.
+ ///
+ public bool Invalid;
+}
diff --git a/Content.Shared/Actions/Events/GetActionDataEvent.cs b/Content.Shared/Actions/Events/GetActionDataEvent.cs
deleted file mode 100644
index be77cfd426..0000000000
--- a/Content.Shared/Actions/Events/GetActionDataEvent.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Content.Shared.Actions.Events;
-
-[ByRefEvent]
-public record struct GetActionDataEvent(BaseActionComponent? Action);
diff --git a/Content.Shared/Actions/Events/ValidateActionEntityTargetEvent.cs b/Content.Shared/Actions/Events/ValidateActionEntityTargetEvent.cs
deleted file mode 100644
index 9f22e7973a..0000000000
--- a/Content.Shared/Actions/Events/ValidateActionEntityTargetEvent.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Content.Shared.Actions.Events;
-
-[ByRefEvent]
-public record struct ValidateActionEntityTargetEvent(EntityUid User, EntityUid Target, bool Cancelled = false);
diff --git a/Content.Shared/Actions/Events/ValidateActionEntityWorldTargetEvent.cs b/Content.Shared/Actions/Events/ValidateActionEntityWorldTargetEvent.cs
deleted file mode 100644
index 57c47026be..0000000000
--- a/Content.Shared/Actions/Events/ValidateActionEntityWorldTargetEvent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Robust.Shared.Map;
-
-namespace Content.Shared.Actions.Events;
-
-[ByRefEvent]
-public record struct ValidateActionEntityWorldTargetEvent(
- EntityUid User,
- EntityUid? Target,
- EntityCoordinates? Coords,
- bool Cancelled = false);
diff --git a/Content.Shared/Actions/Events/ValidateActionWorldTargetEvent.cs b/Content.Shared/Actions/Events/ValidateActionWorldTargetEvent.cs
deleted file mode 100644
index 43e398aad4..0000000000
--- a/Content.Shared/Actions/Events/ValidateActionWorldTargetEvent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Robust.Shared.Map;
-
-namespace Content.Shared.Actions.Events;
-
-[ByRefEvent]
-public record struct ValidateActionWorldTargetEvent(EntityUid User, EntityCoordinates Target, bool Cancelled = false);
diff --git a/Content.Shared/Actions/InstantActionComponent.cs b/Content.Shared/Actions/InstantActionComponent.cs
deleted file mode 100644
index 04c9b94556..0000000000
--- a/Content.Shared/Actions/InstantActionComponent.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Actions;
-
-[RegisterComponent, NetworkedComponent]
-public sealed partial class InstantActionComponent : BaseActionComponent
-{
- public override BaseActionEvent? BaseEvent => Event;
-
- ///
- /// The local-event to raise when this action is performed.
- ///
- [DataField("event")]
- [NonSerialized]
- public InstantActionEvent? Event;
-}
-
-[Serializable, NetSerializable]
-public sealed class InstantActionComponentState : BaseActionComponentState
-{
- public InstantActionComponentState(InstantActionComponent component, IEntityManager entManager) : base(component, entManager)
- {
- }
-}
diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs
index 174f2c7881..2a7bc39b90 100644
--- a/Content.Shared/Actions/SharedActionsSystem.cs
+++ b/Content.Shared/Actions/SharedActionsSystem.cs
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.ActionBlocker;
+using Content.Shared.Actions.Components;
using Content.Shared.Actions.Events;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
@@ -22,27 +23,29 @@ public abstract class SharedActionsSystem : EntitySystem
{
[Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
- [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly RotateToFaceSystem _rotateToFace = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
- [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+ [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ private EntityQuery _actionQuery;
+ private EntityQuery _actionsQuery;
+ private EntityQuery _mindQuery;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnActionMapInit);
- SubscribeLocalEvent(OnActionMapInit);
- SubscribeLocalEvent(OnActionMapInit);
- SubscribeLocalEvent(OnActionMapInit);
+ _actionQuery = GetEntityQuery();
+ _actionsQuery = GetEntityQuery();
+ _mindQuery = GetEntityQuery();
- SubscribeLocalEvent(OnActionShutdown);
- SubscribeLocalEvent(OnActionShutdown);
- SubscribeLocalEvent(OnActionShutdown);
- SubscribeLocalEvent(OnActionShutdown);
+ SubscribeLocalEvent(OnActionMapInit);
+
+ SubscribeLocalEvent(OnActionShutdown);
SubscribeLocalEvent(OnActionCompChange);
SubscribeLocalEvent(OnRelayActionCompChange);
@@ -53,230 +56,198 @@ public abstract class SharedActionsSystem : EntitySystem
SubscribeLocalEvent(OnRejuventate);
SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnGetState);
- SubscribeLocalEvent(OnActionsGetState);
+ SubscribeLocalEvent(OnInstantValidate);
+ SubscribeLocalEvent(OnEntityValidate);
+ SubscribeLocalEvent(OnWorldValidate);
- SubscribeLocalEvent(OnInstantGetState);
- SubscribeLocalEvent(OnEntityTargetGetState);
- SubscribeLocalEvent(OnWorldTargetGetState);
- SubscribeLocalEvent(OnEntityWorldTargetGetState);
+ SubscribeLocalEvent(OnInstantGetEvent);
+ SubscribeLocalEvent(OnEntityGetEvent);
+ SubscribeLocalEvent(OnWorldGetEvent);
- SubscribeLocalEvent(OnGetActionData);
- SubscribeLocalEvent(OnGetActionData);
- SubscribeLocalEvent(OnGetActionData);
- SubscribeLocalEvent(OnGetActionData);
+ SubscribeLocalEvent(OnInstantSetEvent);
+ SubscribeLocalEvent(OnEntitySetEvent);
+ SubscribeLocalEvent(OnWorldSetEvent);
+
+ SubscribeLocalEvent(OnEntitySetTarget);
+ SubscribeLocalEvent(OnWorldSetTarget);
SubscribeAllEvent(OnActionRequest);
}
- private void OnActionMapInit(EntityUid uid, BaseActionComponent component, MapInitEvent args)
+ private void OnActionMapInit(Entity ent, ref MapInitEvent args)
{
- component.OriginalIconColor = component.IconColor;
+ var comp = ent.Comp;
+ comp.OriginalIconColor = comp.IconColor;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.OriginalIconColor));
}
- private void OnActionShutdown(EntityUid uid, BaseActionComponent component, ComponentShutdown args)
+ private void OnActionShutdown(Entity ent, ref ComponentShutdown args)
{
- if (component.AttachedEntity != null && !TerminatingOrDeleted(component.AttachedEntity.Value))
- RemoveAction(component.AttachedEntity.Value, uid, action: component);
+ if (ent.Comp.AttachedEntity is {} user && !TerminatingOrDeleted(user))
+ RemoveAction(user, (ent, ent));
}
- private void OnShutdown(EntityUid uid, ActionsComponent component, ComponentShutdown args)
+ private void OnShutdown(Entity ent, ref ComponentShutdown args)
{
- foreach (var act in component.Actions)
+ foreach (var actionId in ent.Comp.Actions)
{
- RemoveAction(uid, act, component);
+ RemoveAction((ent, ent), actionId);
}
}
- private void OnInstantGetState(EntityUid uid, InstantActionComponent component, ref ComponentGetState args)
+ private void OnGetState(Entity ent, ref ComponentGetState args)
{
- args.State = new InstantActionComponentState(component, EntityManager);
+ args.State = new ActionsComponentState(GetNetEntitySet(ent.Comp.Actions));
}
- private void OnEntityTargetGetState(EntityUid uid, EntityTargetActionComponent component, ref ComponentGetState args)
+ ///
+ /// Resolving an action's , only returning a value if it exists and has it.
+ ///
+ public Entity? GetAction(Entity? action, bool logError = true)
{
- args.State = new EntityTargetActionComponentState(component, EntityManager);
+ if (action is not {} ent || TerminatingOrDeleted(ent))
+ return null;
+
+ if (!_actionQuery.Resolve(ent, ref ent.Comp, logError))
+ return null;
+
+ return (ent, ent.Comp);
}
- private void OnWorldTargetGetState(EntityUid uid, WorldTargetActionComponent component, ref ComponentGetState args)
+ public void SetCooldown(Entity? action, TimeSpan start, TimeSpan end)
{
- args.State = new WorldTargetActionComponentState(component, EntityManager);
- }
-
- private void OnEntityWorldTargetGetState(EntityUid uid, EntityWorldTargetActionComponent component, ref ComponentGetState args)
- {
- args.State = new EntityWorldTargetActionComponentState(component, EntityManager);
- }
-
- private void OnGetActionData(EntityUid uid, T component, ref GetActionDataEvent args) where T : BaseActionComponent
- {
- args.Action = component;
- }
-
- public bool TryGetActionData(
- [NotNullWhen(true)] EntityUid? uid,
- [NotNullWhen(true)] out BaseActionComponent? result,
- bool logError = true)
- {
- result = null;
- if (uid == null || TerminatingOrDeleted(uid.Value))
- return false;
-
- var ev = new GetActionDataEvent();
- RaiseLocalEvent(uid.Value, ref ev);
- result = ev.Action;
-
- if (result != null)
- return true;
-
- if (logError)
- Log.Error($"Failed to get action from action entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}");
-
- return false;
- }
-
- public bool ResolveActionData(
- [NotNullWhen(true)] EntityUid? uid,
- [NotNullWhen(true)] ref BaseActionComponent? result,
- bool logError = true)
- {
- if (result != null)
- {
- DebugTools.AssertOwner(uid, result);
- return true;
- }
-
- return TryGetActionData(uid, out result, logError);
- }
-
- public void SetCooldown(EntityUid? actionId, TimeSpan start, TimeSpan end)
- {
- if (!TryGetActionData(actionId, out var action))
+ if (GetAction(action) is not {} ent)
return;
- action.Cooldown = (start, end);
- Dirty(actionId.Value, action);
+ ent.Comp.Cooldown = new ActionCooldown
+ {
+ Start = start,
+ End = end
+ };
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Cooldown));
}
- public void SetCooldown(EntityUid? actionId, TimeSpan cooldown)
+ public void RemoveCooldown(Entity? action)
+ {
+ if (GetAction(action) is not {} ent)
+ return;
+
+ ent.Comp.Cooldown = null;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Cooldown));
+ }
+
+ ///
+ /// Starts a cooldown starting now, lasting for cooldown seconds.
+ ///
+ public void SetCooldown(Entity? action, TimeSpan cooldown)
{
var start = GameTiming.CurTime;
- SetCooldown(actionId, start, start + cooldown);
+ SetCooldown(action, start, start + cooldown);
}
- public void ClearCooldown(EntityUid? actionId)
+ public void ClearCooldown(Entity? action)
{
- if (!TryGetActionData(actionId, out var action))
+ if (GetAction(action) is not {} ent)
return;
- if (action.Cooldown is not { } cooldown)
+ if (ent.Comp.Cooldown is not {} cooldown)
return;
- action.Cooldown = (cooldown.Start, GameTiming.CurTime);
- Dirty(actionId.Value, action);
+ ent.Comp.Cooldown = new ActionCooldown
+ {
+ Start = cooldown.Start,
+ End = GameTiming.CurTime
+ };
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Cooldown));
}
///
/// Sets the cooldown for this action only if it is bigger than the one it already has.
///
- public void SetIfBiggerCooldown(EntityUid? actionId, TimeSpan? cooldown)
+ public void SetIfBiggerCooldown(Entity? action, TimeSpan cooldown)
{
- if (cooldown == null ||
- cooldown.Value <= TimeSpan.Zero ||
- !TryGetActionData(actionId, out var action))
- {
+ if (GetAction(action) is not {} ent || cooldown < TimeSpan.Zero)
return;
- }
var start = GameTiming.CurTime;
var end = start + cooldown;
- if (action.Cooldown?.End > end)
+ if (ent.Comp.Cooldown?.End > end)
return;
- action.Cooldown = (start, end.Value);
- Dirty(actionId.Value, action);
+ SetCooldown((ent, ent), start, end);
}
- public void StartUseDelay(EntityUid? actionId)
+ ///
+ /// Set an action's cooldown to its use delay, if it has one.
+ /// If there is no set use delay this does nothing.
+ ///
+ public void StartUseDelay(Entity? action)
{
- if (actionId == null)
+ if (GetAction(action) is not {} ent || ent.Comp.UseDelay is not {} delay)
return;
- if (!TryGetActionData(actionId, out var action) || action.UseDelay == null)
- return;
-
- action.Cooldown = (GameTiming.CurTime, GameTiming.CurTime + action.UseDelay.Value);
- Dirty(actionId.Value, action);
+ SetCooldown((ent, ent), delay);
}
- public void SetUseDelay(EntityUid? actionId, TimeSpan? delay)
+ public void SetUseDelay(Entity? action, TimeSpan? delay)
{
- if (!TryGetActionData(actionId, out var action) || action.UseDelay == delay)
+ if (GetAction(action) is not {} ent || ent.Comp.UseDelay == delay)
return;
- action.UseDelay = delay;
- UpdateAction(actionId, action);
- Dirty(actionId.Value, action);
+ ent.Comp.UseDelay = delay;
+ UpdateAction(ent);
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.UseDelay));
}
- public void ReduceUseDelay(EntityUid? actionId, TimeSpan? lowerDelay)
+ public void ReduceUseDelay(Entity? action, TimeSpan? lowerDelay)
{
- if (!TryGetActionData(actionId, out var action))
+ if (GetAction(action) is not {} ent)
return;
- if (action.UseDelay != null && lowerDelay != null)
- action.UseDelay = action.UseDelay - lowerDelay;
+ if (ent.Comp.UseDelay != null && lowerDelay != null)
+ ent.Comp.UseDelay -= lowerDelay;
- if (action.UseDelay < TimeSpan.Zero)
- action.UseDelay = null;
+ if (ent.Comp.UseDelay < TimeSpan.Zero)
+ ent.Comp.UseDelay = null;
- UpdateAction(actionId, action);
- Dirty(actionId.Value, action);
+ UpdateAction(ent);
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.UseDelay));
}
- private void OnRejuventate(EntityUid uid, ActionsComponent component, RejuvenateEvent args)
+ private void OnRejuventate(Entity ent, ref RejuvenateEvent args)
{
- foreach (var act in component.Actions)
+ foreach (var act in ent.Comp.Actions)
{
ClearCooldown(act);
}
}
#region ComponentStateManagement
- public virtual void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
+ public virtual void UpdateAction(Entity ent)
{
// See client-side code.
}
- public void SetToggled(EntityUid? actionId, bool toggled)
+ public void SetToggled(Entity? action, bool toggled)
{
- if (!TryGetActionData(actionId, out var action) ||
- action.Toggled == toggled)
- {
+ if (GetAction(action) is not {} ent || ent.Comp.Toggled == toggled)
return;
- }
- action.Toggled = toggled;
- UpdateAction(actionId, action);
- Dirty(actionId.Value, action);
+ ent.Comp.Toggled = toggled;
+ UpdateAction(ent);
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Toggled));
}
- public void SetEnabled(EntityUid? actionId, bool enabled)
+ public void SetEnabled(Entity? action, bool enabled)
{
- if (!TryGetActionData(actionId, out var action) ||
- action.Enabled == enabled)
- {
+ if (GetAction(action) is not {} ent || ent.Comp.Enabled == enabled)
return;
- }
- action.Enabled = enabled;
- UpdateAction(actionId, action);
- Dirty(actionId.Value, action);
- }
-
- private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
- {
- args.State = new ActionsComponentState(GetNetEntitySet(component.Actions));
+ ent.Comp.Enabled = enabled;
+ UpdateAction(ent);
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Enabled));
}
#endregion
@@ -291,7 +262,7 @@ public abstract class SharedActionsSystem : EntitySystem
if (args.SenderSession.AttachedEntity is not { } user)
return;
- if (!TryComp(user, out ActionsComponent? component))
+ if (!_actionsQuery.TryComp(user, out var component))
return;
var actionEnt = GetEntity(ev.Action);
@@ -309,11 +280,11 @@ public abstract class SharedActionsSystem : EntitySystem
return;
}
- if (!TryGetActionData(actionEnt, out var action))
+ if (GetAction(actionEnt) is not {} action)
return;
- DebugTools.Assert(action.AttachedEntity == user);
- if (!action.Enabled)
+ DebugTools.Assert(action.Comp.AttachedEntity == user);
+ if (!action.Comp.Enabled)
return;
var curTime = GameTiming.CurTime;
@@ -323,298 +294,292 @@ public abstract class SharedActionsSystem : EntitySystem
// check for action use prevention
// TODO: make code below use this event with a dedicated component
var attemptEv = new ActionAttemptEvent(user);
- RaiseLocalEvent(actionEnt, ref attemptEv);
+ RaiseLocalEvent(action, ref attemptEv);
if (attemptEv.Cancelled)
return;
- BaseActionEvent? performEvent = null;
-
- if (action.CheckConsciousness && !_actionBlockerSystem.CanConsciouslyPerformAction(user))
+ // Validate request by checking action blockers and the like
+ var provider = action.Comp.Container ?? user;
+ var validateEv = new ActionValidateEvent()
+ {
+ Input = ev,
+ User = user,
+ Provider = provider
+ };
+ RaiseLocalEvent(action, ref validateEv);
+ if (validateEv.Invalid)
return;
- // Validate request by checking action blockers and the like:
- switch (action)
+ // All checks passed. Perform the action!
+ PerformAction((user, component), action);
+ }
+
+ private void OnValidate(Entity ent, ref ActionValidateEvent args)
+ {
+ if (ent.Comp.CheckConsciousness && !_actionBlocker.CanConsciouslyPerformAction(args.User))
{
- case EntityTargetActionComponent entityAction:
- if (ev.EntityTarget is not { Valid: true } netTarget)
- {
- Log.Error($"Attempted to perform an entity-targeted action without a target! Action: {name}");
- return;
- }
-
- var entityTarget = GetEntity(netTarget);
-
- var targetWorldPos = _transformSystem.GetWorldPosition(entityTarget);
- _rotateToFaceSystem.TryFaceCoordinates(user, targetWorldPos);
-
- if (!ValidateEntityTarget(user, entityTarget, (actionEnt, entityAction)))
- return;
-
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {ToPrettyString(entityTarget):target}.");
-
- if (entityAction.Event != null)
- {
- entityAction.Event.Target = entityTarget;
- Dirty(actionEnt, entityAction);
- performEvent = entityAction.Event;
- }
-
- break;
- case WorldTargetActionComponent worldAction:
- if (ev.EntityCoordinatesTarget is not { } netCoordinatesTarget)
- {
- Log.Error($"Attempted to perform a world-targeted action without a target! Action: {name}");
- return;
- }
-
- var entityCoordinatesTarget = GetCoordinates(netCoordinatesTarget);
- _rotateToFaceSystem.TryFaceCoordinates(user, _transformSystem.ToMapCoordinates(entityCoordinatesTarget).Position);
-
- if (!ValidateWorldTarget(user, entityCoordinatesTarget, (actionEnt, worldAction)))
- return;
-
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {entityCoordinatesTarget:target}.");
-
- if (worldAction.Event != null)
- {
- worldAction.Event.Target = entityCoordinatesTarget;
- Dirty(actionEnt, worldAction);
- performEvent = worldAction.Event;
- }
-
- break;
- case EntityWorldTargetActionComponent entityWorldAction:
- {
- var actionEntity = GetEntity(ev.EntityTarget);
- var actionCoords = GetCoordinates(ev.EntityCoordinatesTarget);
-
- if (actionEntity is null && actionCoords is null)
- {
- Log.Error($"Attempted to perform an entity-world-targeted action without an entity or world coordinates! Action: {name}");
- return;
- }
-
- var entWorldAction = new Entity(actionEnt, entityWorldAction);
-
- if (!ValidateEntityWorldTarget(user, actionEntity, actionCoords, entWorldAction))
- return;
-
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {ToPrettyString(actionEntity):target} {actionCoords:target}.");
-
- if (entityWorldAction.Event != null)
- {
- entityWorldAction.Event.Entity = actionEntity;
- entityWorldAction.Event.Coords = actionCoords;
- Dirty(actionEnt, entityWorldAction);
- performEvent = entityWorldAction.Event;
- }
- break;
- }
- case InstantActionComponent instantAction:
- if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
- return;
-
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Container ?? user):provider}.");
-
- performEvent = instantAction.Event;
- break;
+ args.Invalid = true;
+ return;
}
- // All checks passed. Perform the action!
- PerformAction(user, component, actionEnt, action, performEvent, curTime);
+ if (ent.Comp.CheckCanInteract && !_actionBlocker.CanInteract(args.User, null))
+ {
+ args.Invalid = true;
+ return;
+ }
+
+ // Event is not set here, only below
}
- public bool ValidateEntityTarget(EntityUid user, EntityUid target, Entity actionEnt)
+ private void OnInstantValidate(Entity ent, ref ActionValidateEvent args)
{
- var comp = actionEnt.Comp;
- if (!ValidateEntityTargetBase(user,
- target,
- comp.Whitelist,
- comp.Blacklist,
- comp.CheckCanInteract,
- comp.CanTargetSelf,
- comp.CheckCanAccess,
- comp.Range))
- return false;
-
- var ev = new ValidateActionEntityTargetEvent(user, target);
- RaiseLocalEvent(actionEnt, ref ev);
- return !ev.Cancelled;
+ _adminLogger.Add(LogType.Action,
+ $"{ToPrettyString(args.User):user} is performing the {Name(ent):action} action provided by {ToPrettyString(args.Provider):provider}.");
}
- private bool ValidateEntityTargetBase(EntityUid user,
- EntityUid? targetEntity,
- EntityWhitelist? whitelist,
- EntityWhitelist? blacklist,
- bool checkCanInteract,
- bool canTargetSelf,
- bool checkCanAccess,
- float range)
+ private void OnEntityValidate(Entity ent, ref ActionValidateEvent args)
{
- if (targetEntity is not { } target || !target.IsValid() || Deleted(target))
+ // let WorldTargetAction handle it
+ if (ent.Comp.Event is not {} ev)
+ {
+ DebugTools.Assert(HasComp(ent), $"Entity-world targeting action {ToPrettyString(ent)} requires WorldTargetActionComponent");
+ return;
+ }
+
+ if (args.Input.EntityTarget is not {} netTarget)
+ {
+ args.Invalid = true;
+ return;
+ }
+
+ var user = args.User;
+
+ var target = GetEntity(netTarget);
+
+ var targetWorldPos = _transform.GetWorldPosition(target);
+ _rotateToFace.TryFaceCoordinates(user, targetWorldPos);
+
+ if (!ValidateEntityTarget(user, target, ent))
+ return;
+
+ _adminLogger.Add(LogType.Action,
+ $"{ToPrettyString(user):user} is performing the {Name(ent):action} action (provided by {ToPrettyString(args.Provider):provider}) targeted at {ToPrettyString(target):target}.");
+
+ ev.Target = target;
+ }
+
+ private void OnWorldValidate(Entity ent, ref ActionValidateEvent args)
+ {
+ if (args.Input.EntityCoordinatesTarget is not { } netTarget)
+ {
+ args.Invalid = true;
+ return;
+ }
+
+ var user = args.User;
+ var target = GetCoordinates(netTarget);
+ _rotateToFace.TryFaceCoordinates(user, target.ToMapPos(EntityManager, _transform));
+
+ if (!ValidateWorldTarget(user, target, ent))
+ return;
+
+ // if the client specified an entity it needs to be valid
+ var targetEntity = GetEntity(args.Input.EntityTarget);
+ if (targetEntity != null && (
+ !TryComp(ent, out var entTarget) ||
+ !ValidateEntityTarget(user, targetEntity.Value, (ent, entTarget))))
+ {
+ args.Invalid = true;
+ return;
+ }
+
+ _adminLogger.Add(LogType.Action,
+ $"{ToPrettyString(user):user} is performing the {Name(ent):action} action (provided by {args.Provider}) targeting {targetEntity} at {target:target}.");
+
+ if (ent.Comp.Event is {} ev)
+ {
+ ev.Target = target;
+ ev.Entity = targetEntity;
+ }
+ }
+
+ public bool ValidateEntityTarget(EntityUid user, EntityUid target, Entity ent)
+ {
+ var (uid, comp) = ent;
+ if (!target.IsValid() || Deleted(target))
return false;
- if (_whitelistSystem.IsWhitelistFail(whitelist, target))
+ if (_whitelist.IsWhitelistFail(comp.Whitelist, target))
return false;
- if (_whitelistSystem.IsBlacklistPass(blacklist, target))
+ if (_whitelist.IsBlacklistPass(comp.Blacklist, target))
return false;
- if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
+ if (_actionQuery.Comp(uid).CheckCanInteract && !_actionBlocker.CanInteract(user, target))
return false;
if (user == target)
- return canTargetSelf;
+ return comp.CanTargetSelf;
- if (!checkCanAccess)
+ var targetAction = Comp(uid);
+ var coords = Transform(target).Coordinates;
+ if (!ValidateBaseTarget(user, coords, (uid, targetAction)))
{
- // even if we don't check for obstructions, we may still need to check the range.
- var xform = Transform(user);
- var targetXform = Transform(target);
-
- if (xform.MapID != targetXform.MapID)
- return false;
-
- if (range <= 0)
- return true;
-
- var distance = (_transformSystem.GetWorldPosition(xform) - _transformSystem.GetWorldPosition(targetXform)).Length();
- return distance <= range;
+ // if not just checking pure range, let stored entities be targeted by actions
+ // if it's out of range it probably isn't stored anyway...
+ return targetAction.CheckCanAccess && _interaction.CanAccessViaStorage(user, target);
}
- return _interactionSystem.InRangeAndAccessible(user, target, range: range);
+ return _interaction.InRangeAndAccessible(user, target, range: targetAction.Range);
}
- public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity action)
+ public bool ValidateWorldTarget(EntityUid user, EntityCoordinates target, Entity ent)
{
- var comp = action.Comp;
- if (!ValidateWorldTargetBase(user, coords, comp.CheckCanInteract, comp.CheckCanAccess, comp.Range))
- return false;
-
- var ev = new ValidateActionWorldTargetEvent(user, coords);
- RaiseLocalEvent(action, ref ev);
- return !ev.Cancelled;
+ var targetAction = Comp(ent);
+ return ValidateBaseTarget(user, target, (ent, targetAction));
}
- private bool ValidateWorldTargetBase(EntityUid user,
- EntityCoordinates? entityCoordinates,
- bool checkCanInteract,
- bool checkCanAccess,
- float range)
+ private bool ValidateBaseTarget(EntityUid user, EntityCoordinates coords, Entity ent)
{
- if (entityCoordinates is not { } coords)
+ var (uid, comp) = ent;
+ if (comp.CheckCanAccess)
+ return _interaction.InRangeUnobstructed(user, coords, range: comp.Range);
+
+ // even if we don't check for obstructions, we may still need to check the range.
+ var xform = Transform(user);
+ if (xform.MapID != _transform.GetMapId(coords))
return false;
- if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, null))
- return false;
+ if (comp.Range <= 0)
+ return true;
- if (!checkCanAccess)
+ return _transform.InRange(coords, xform.Coordinates, comp.Range);
+ }
+
+ private void OnInstantGetEvent(Entity ent, ref ActionGetEventEvent args)
+ {
+ if (ent.Comp.Event is {} ev)
+ args.Event = ev;
+ }
+
+ private void OnEntityGetEvent(Entity ent, ref ActionGetEventEvent args)
+ {
+ if (ent.Comp.Event is {} ev)
+ args.Event = ev;
+ }
+
+ private void OnWorldGetEvent(Entity ent, ref ActionGetEventEvent args)
+ {
+ if (ent.Comp.Event is {} ev)
+ args.Event = ev;
+ }
+
+ private void OnInstantSetEvent(Entity ent, ref ActionSetEventEvent args)
+ {
+ if (args.Event is InstantActionEvent ev)
{
- // even if we don't check for obstructions, we may still need to check the range.
- var xform = Transform(user);
-
- if (xform.MapID != _transformSystem.GetMapId(coords))
- return false;
-
- if (range <= 0)
- return true;
- return _transformSystem.InRange(coords, xform.Coordinates, range);
+ ent.Comp.Event = ev;
+ args.Handled = true;
}
-
- return _interactionSystem.InRangeUnobstructed(user, coords, range: range);
}
- public bool ValidateEntityWorldTarget(EntityUid user,
- EntityUid? entity,
- EntityCoordinates? coords,
- Entity action)
+ private void OnEntitySetEvent(Entity ent, ref ActionSetEventEvent args)
{
- var comp = action.Comp;
- var entityValidated = ValidateEntityTargetBase(user,
- entity,
- comp.Whitelist,
- null,
- comp.CheckCanInteract,
- comp.CanTargetSelf,
- comp.CheckCanAccess,
- comp.Range);
-
- var worldValidated
- = ValidateWorldTargetBase(user, coords, comp.CheckCanInteract, comp.CheckCanAccess, comp.Range);
-
- if (!entityValidated && !worldValidated)
- return false;
-
- var ev = new ValidateActionEntityWorldTargetEvent(user,
- entityValidated ? entity : null,
- worldValidated ? coords : null);
- RaiseLocalEvent(action, ref ev);
- return !ev.Cancelled;
+ if (args.Event is EntityTargetActionEvent ev)
+ {
+ ent.Comp.Event = ev;
+ args.Handled = true;
+ }
}
- public void PerformAction(EntityUid performer, ActionsComponent? component, EntityUid actionId, BaseActionComponent action, BaseActionEvent? actionEvent, TimeSpan curTime, bool predicted = true)
+ private void OnWorldSetEvent(Entity ent, ref ActionSetEventEvent args)
+ {
+ if (args.Event is WorldTargetActionEvent ev)
+ {
+ ent.Comp.Event = ev;
+ args.Handled = true;
+ }
+ }
+
+ private void OnEntitySetTarget(Entity ent, ref ActionSetTargetEvent args)
+ {
+ if (ent.Comp.Event is {} ev)
+ {
+ ev.Target = args.Target;
+ args.Handled = true;
+ }
+ }
+
+ private void OnWorldSetTarget(Entity ent, ref ActionSetTargetEvent args)
+ {
+ if (ent.Comp.Event is {} ev)
+ {
+ ev.Target = Transform(args.Target).Coordinates;
+ // only set Entity if the action also has EntityTargetAction
+ ev.Entity = HasComp(ent) ? args.Target : null;
+ args.Handled = true;
+ }
+ }
+
+ ///
+ /// Perform an action, bypassing validation checks.
+ ///
+ /// The entity performing the action
+ /// The action being performed
+ /// An event override to perform. If null, uses
+ /// If false, prevents playing the action's sound on the client
+ public void PerformAction(Entity performer, Entity action, BaseActionEvent? actionEvent = null, bool predicted = true)
{
var handled = false;
- var toggledBefore = action.Toggled;
+ var toggledBefore = action.Comp.Toggled;
// Note that attached entity and attached container are allowed to be null here.
- if (action.AttachedEntity != null && action.AttachedEntity != performer)
+ if (action.Comp.AttachedEntity != null && action.Comp.AttachedEntity != performer)
{
- Log.Error($"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(actionId)} that is attached to another entity {ToPrettyString(action.AttachedEntity.Value)}");
+ Log.Error($"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(action)} that is attached to another entity {ToPrettyString(action.Comp.AttachedEntity)}");
return;
}
- if (actionEvent != null)
- {
- // This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
- actionEvent.Handled = false;
- var target = performer;
- actionEvent.Performer = performer;
- actionEvent.Action = (actionId, action);
+ actionEvent ??= GetEvent(action);
- if (!action.RaiseOnUser && action.Container != null && !HasComp(action.Container))
- target = action.Container.Value;
+ if (actionEvent is not {} ev)
+ return;
- if (action.RaiseOnAction)
- target = actionId;
+ ev.Performer = performer;
- RaiseLocalEvent(target, (object) actionEvent, broadcast: true);
- handled = actionEvent.Handled;
- }
+ // This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
+ ev.Handled = false;
+ var target = performer.Owner;
+ ev.Performer = performer;
+ ev.Action = action;
+
+ if (!action.Comp.RaiseOnUser && action.Comp.Container is {} container && !_mindQuery.HasComp(container))
+ target = container;
+
+ if (action.Comp.RaiseOnAction)
+ target = action;
+
+ RaiseLocalEvent(target, (object) ev, broadcast: true);
+ handled = ev.Handled;
if (!handled)
return; // no interaction occurred.
- // play sound, reduce charges, start cooldown, and mark as dirty (if required).
- if (actionEvent?.Toggle == true)
- {
- action.Toggled = !action.Toggled;
- }
+ // play sound, reduce charges, start cooldown
+ if (ev?.Toggle == true)
+ SetToggled((action, action), !action.Comp.Toggled);
- _audio.PlayPredicted(action.Sound, performer, predicted ? performer : null);
+ _audio.PlayPredicted(action.Comp.Sound, performer, predicted ? performer : null);
- var dirty = toggledBefore != action.Toggled;
+ // TODO: move to ActionCooldown ActionPerformedEvent?
+ RemoveCooldown((action, action));
+ StartUseDelay((action, action));
- action.Cooldown = null;
- if (action is { UseDelay: not null})
- {
- dirty = true;
- action.Cooldown = (curTime, curTime + action.UseDelay.Value);
- }
+ UpdateAction(action);
- if (dirty)
- {
- Dirty(actionId, action);
- UpdateAction(actionId, action);
- }
-
- var ev = new ActionPerformedEvent(performer);
- RaiseLocalEvent(actionId, ref ev);
+ var performed = new ActionPerformedEvent(performer);
+ RaiseLocalEvent(action, ref performed);
}
#endregion
@@ -651,7 +616,7 @@ public abstract class SharedActionsSystem : EntitySystem
///
public bool AddAction(EntityUid performer,
[NotNullWhen(true)] ref EntityUid? actionId,
- [NotNullWhen(true)] out BaseActionComponent? action,
+ [NotNullWhen(true)] out ActionComponent? action,
string? actionPrototypeId,
EntityUid container = default,
ActionsComponent? component = null)
@@ -662,70 +627,65 @@ public abstract class SharedActionsSystem : EntitySystem
if (!_actionContainer.EnsureAction(container, ref actionId, out action, actionPrototypeId))
return false;
- return AddActionDirect(performer, actionId.Value, component, action);
+ return AddActionDirect((performer, component), (actionId.Value, action));
}
///
/// Adds a pre-existing action.
///
- public bool AddAction(EntityUid performer,
- EntityUid actionId,
- EntityUid container,
- ActionsComponent? comp = null,
- BaseActionComponent? action = null,
- ActionsContainerComponent? containerComp = null
- )
+ public bool AddAction(Entity performer,
+ Entity action,
+ Entity container)
{
- if (!ResolveActionData(actionId, ref action))
+ if (GetAction(action) is not {} ent)
return false;
- if (action.Container != container
- || !Resolve(container, ref containerComp)
- || !containerComp.Container.Contains(actionId))
+ if (ent.Comp.Container != container.Owner
+ || !Resolve(container, ref container.Comp)
+ || !container.Comp.Container.Contains(ent))
{
- Log.Error($"Attempted to add an action with an invalid container: {ToPrettyString(actionId)}");
+ Log.Error($"Attempted to add an action with an invalid container: {ToPrettyString(ent)}");
return false;
}
- return AddActionDirect(performer, actionId, comp, action);
+ return AddActionDirect(performer, (ent, ent));
}
///
/// Adds a pre-existing action. This also bypasses the requirement that the given action must be stored in a
/// valid action container.
///
- public bool AddActionDirect(EntityUid performer,
- EntityUid actionId,
- ActionsComponent? comp = null,
- BaseActionComponent? action = null)
+ public bool AddActionDirect(Entity performer,
+ Entity? action)
{
- if (!ResolveActionData(actionId, ref action))
+ if (GetAction(action) is not {} ent)
return false;
- DebugTools.Assert(action.Container == null ||
- (TryComp(action.Container, out ActionsContainerComponent? containerComp)
- && containerComp.Container.Contains(actionId)));
+ DebugTools.Assert(ent.Comp.Container == null ||
+ (TryComp(ent.Comp.Container, out ActionsContainerComponent? containerComp)
+ && containerComp.Container.Contains(ent)));
- if (action.AttachedEntity != null)
- RemoveAction(action.AttachedEntity.Value, actionId, action: action);
+ if (ent.Comp.AttachedEntity is {} user)
+ RemoveAction(user, (ent, ent));
- if (action.StartDelay && action.UseDelay != null)
- SetCooldown(actionId, action.UseDelay.Value);
+ // TODO: make this an event bruh
+ if (ent.Comp.StartDelay && ent.Comp.UseDelay != null)
+ SetCooldown((ent, ent), ent.Comp.UseDelay.Value);
- DebugTools.AssertOwner(performer, comp);
- comp ??= EnsureComp(performer);
- action.AttachedEntity = performer;
- comp.Actions.Add(actionId);
- Dirty(actionId, action);
- Dirty(performer, comp);
- ActionAdded(performer, actionId, comp, action);
+ DebugTools.AssertOwner(performer, performer.Comp);
+ performer.Comp ??= EnsureComp(performer);
+ ent.Comp.AttachedEntity = performer;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.AttachedEntity));
+ performer.Comp.Actions.Add(ent);
+ Dirty(performer, performer.Comp);
+ ActionAdded((performer, performer.Comp), (ent, ent.Comp));
return true;
}
///
/// This method gets called after a new action got added.
///
- protected virtual void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
+ protected virtual void ActionAdded(Entity performer, Entity action)
{
// See client-side system for UI code.
}
@@ -736,17 +696,19 @@ public abstract class SharedActionsSystem : EntitySystem
/// Entity to receive the actions
/// The actions to add
/// The entity that enables these actions (e.g., flashlight). May be null (innate actions).
- public void GrantActions(EntityUid performer, IEnumerable actions, EntityUid container, ActionsComponent? comp = null, ActionsContainerComponent? containerComp = null)
+ public void GrantActions(Entity performer,
+ IEnumerable actions,
+ Entity container)
{
- if (!Resolve(container, ref containerComp))
+ if (!Resolve(container, ref container.Comp))
return;
- DebugTools.AssertOwner(performer, comp);
- comp ??= EnsureComp(performer);
+ DebugTools.AssertOwner(performer, performer.Comp);
+ performer.Comp ??= EnsureComp(performer);
foreach (var actionId in actions)
{
- AddAction(performer, actionId, container, comp, containerComp: containerComp);
+ AddAction(performer, actionId, container);
}
}
@@ -765,8 +727,8 @@ public abstract class SharedActionsSystem : EntitySystem
foreach (var actionId in container.Comp.Container.ContainedEntities)
{
- if (TryGetActionData(actionId, out var action))
- AddActionDirect(performer, actionId, performer.Comp, action);
+ if (GetAction(actionId) is {} action)
+ AddActionDirect(performer, (action, action));
}
}
@@ -784,21 +746,20 @@ public abstract class SharedActionsSystem : EntitySystem
performer.Comp ??= EnsureComp(performer);
- if (TryGetActionData(actionId, out var action))
- AddActionDirect(performer, actionId, performer.Comp, action);
+ AddActionDirect(performer, actionId);
}
- public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetActions(EntityUid holderId, ActionsComponent? actions = null)
+ public IEnumerable> GetActions(EntityUid holderId, ActionsComponent? actions = null)
{
if (!Resolve(holderId, ref actions, false))
yield break;
foreach (var actionId in actions.Actions)
{
- if (!TryGetActionData(actionId, out var action))
+ if (GetAction(actionId) is not {} ent)
continue;
- yield return (actionId, action);
+ yield return ent;
}
}
@@ -812,11 +773,11 @@ public abstract class SharedActionsSystem : EntitySystem
foreach (var actionId in comp.Actions.ToArray())
{
- if (!TryGetActionData(actionId, out var action))
+ if (GetAction(actionId) is not {} ent)
return;
- if (action.Container == container)
- RemoveAction(performer, actionId, comp);
+ if (ent.Comp.Container == container)
+ RemoveAction((performer, comp), (ent, ent));
}
}
@@ -825,88 +786,81 @@ public abstract class SharedActionsSystem : EntitySystem
///
public void RemoveProvidedAction(EntityUid performer, EntityUid container, EntityUid actionId, ActionsComponent? comp = null)
{
- if (!Resolve(performer, ref comp, false) || !TryGetActionData(actionId, out var action))
+ if (!_actionsQuery.Resolve(performer, ref comp, false) || GetAction(actionId) is not {} ent)
return;
- if (action.Container == container)
- RemoveAction(performer, actionId, comp);
+ if (ent.Comp.Container == container)
+ RemoveAction((performer, comp), (ent, ent));
}
- public void RemoveAction(EntityUid? actionId)
+ ///
+ /// Removes an action from its container, if it still exists.
+ ///
+ public void RemoveAction(Entity? action)
{
- if (actionId == null)
+ if (GetAction(action) is not {} ent || ent.Comp.AttachedEntity is not {} actions)
return;
- if (!TryGetActionData(actionId, out var action))
+ if (!_actionsQuery.TryComp(actions, out var comp))
return;
- if (!TryComp(action.AttachedEntity, out ActionsComponent? comp))
- return;
-
- RemoveAction(action.AttachedEntity.Value, actionId, comp, action);
+ RemoveAction((actions, comp), (ent, ent));
}
- public void RemoveAction(EntityUid performer, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null)
+ public void RemoveAction(Entity performer, Entity? action)
{
- if (actionId == null)
+ if (GetAction(action) is not {} ent)
return;
- if (!ResolveActionData(actionId, ref action))
- return;
-
- if (action.AttachedEntity != performer)
+ if (ent.Comp.AttachedEntity != performer.Owner)
{
- DebugTools.Assert(!Resolve(performer, ref comp, false)
- || comp.LifeStage >= ComponentLifeStage.Stopping
- || !comp.Actions.Contains(actionId.Value));
+ DebugTools.Assert(!Resolve(performer, ref performer.Comp, false)
+ || performer.Comp.LifeStage >= ComponentLifeStage.Stopping
+ || !performer.Comp.Actions.Contains(ent.Owner));
if (!GameTiming.ApplyingState)
- Log.Error($"Attempted to remove an action {ToPrettyString(actionId)} from an entity that it was never attached to: {ToPrettyString(performer)}. Trace: {Environment.StackTrace}");
+ Log.Error($"Attempted to remove an action {ToPrettyString(ent)} from an entity that it was never attached to: {ToPrettyString(performer)}. Trace: {Environment.StackTrace}");
return;
}
- if (!Resolve(performer, ref comp, false))
+ if (!_actionsQuery.Resolve(performer, ref performer.Comp, false))
{
- DebugTools.Assert(action.AttachedEntity == null || TerminatingOrDeleted(action.AttachedEntity.Value));
- action.AttachedEntity = null;
+ DebugTools.Assert(performer == null || TerminatingOrDeleted(performer));
+ ent.Comp.AttachedEntity = null;
+ // TODO: should this delete the action since it's now orphaned?
return;
}
- if (action.AttachedEntity == null)
- {
- // action was already removed?
- DebugTools.Assert(!comp.Actions.Contains(actionId.Value) || GameTiming.ApplyingState);
- return;
- }
+ performer.Comp.Actions.Remove(ent.Owner);
+ Dirty(performer, performer.Comp);
+ ent.Comp.AttachedEntity = null;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.AttachedEntity));
+ ActionRemoved((performer, performer.Comp), ent);
- comp.Actions.Remove(actionId.Value);
- action.AttachedEntity = null;
- Dirty(actionId.Value, action);
- Dirty(performer, comp);
- ActionRemoved(performer, actionId.Value, comp, action);
- if (action.Temporary)
- QueueDel(actionId.Value);
+ if (ent.Comp.Temporary)
+ QueueDel(ent);
}
///
/// This method gets called after an action got removed.
///
- protected virtual void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
+ protected virtual void ActionRemoved(Entity performer, Entity action)
{
// See client-side system for UI code.
}
- public bool ValidAction(BaseActionComponent action, bool canReach = true)
+ public bool ValidAction(Entity ent, bool canReach = true)
{
- if (!action.Enabled)
+ var (uid, comp) = ent;
+ if (!comp.Enabled)
return false;
-
var curTime = GameTiming.CurTime;
- if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
+ if (comp.Cooldown.HasValue && comp.Cooldown.Value.End > curTime)
return false;
- return canReach || action is BaseTargetActionComponent { CheckCanAccess: false };
+ // TODO: use event for this
+ return canReach || Comp(ent)?.CheckCanAccess == false;
}
#endregion
@@ -953,7 +907,7 @@ public abstract class SharedActionsSystem : EntitySystem
}
#region EquipHandlers
- private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
+ private void OnDidEquip(Entity ent, ref DidEquipEvent args)
{
if (GameTiming.ApplyingState)
return;
@@ -964,10 +918,10 @@ public abstract class SharedActionsSystem : EntitySystem
if (ev.Actions.Count == 0)
return;
- GrantActions(args.Equipee, ev.Actions, args.Equipment, component);
+ GrantActions((ent, ent), ev.Actions, args.Equipment);
}
- private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
+ private void OnHandEquipped(Entity ent, ref DidEquipHandEvent args)
{
if (GameTiming.ApplyingState)
return;
@@ -978,7 +932,7 @@ public abstract class SharedActionsSystem : EntitySystem
if (ev.Actions.Count == 0)
return;
- GrantActions(args.User, ev.Actions, args.Equipped, component);
+ GrantActions((ent, ent), ev.Actions, args.Equipped);
}
private void OnDidUnequip(EntityUid uid, ActionsComponent component, DidUnequipEvent args)
@@ -998,19 +952,76 @@ public abstract class SharedActionsSystem : EntitySystem
}
#endregion
- public void SetEntityIcon(EntityUid uid, EntityUid? icon, BaseActionComponent? action = null)
+ public void SetEntityIcon(Entity ent, EntityUid? icon)
{
- if (!Resolve(uid, ref action))
+ if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.EntityIcon == icon)
return;
- action.EntityIcon = icon;
- Dirty(uid, action);
+ ent.Comp.EntityIcon = icon;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.EntIcon));
+ }
+
+ public void SetIcon(Entity ent, SpriteSpecifier? icon)
+ {
+ if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.Icon == icon)
+ return;
+
+ ent.Comp.Icon = icon;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.Icon));
+ }
+
+ public void SetIconOn(Entity ent, SpriteSpecifier? iconOn)
+ {
+ if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.IconOn == iconOn)
+ return;
+
+ ent.Comp.IconOn = iconOn;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.IconOn));
+ }
+
+ public void SetIconColor(Entity ent, Color color)
+ {
+ if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.IconColor == color)
+ return;
+
+ ent.Comp.IconColor = color;
+ DirtyField(ent, ent.Comp, nameof(ActionComponent.IconColor));
+ }
+
+ ///
+ /// Set the event of an action.
+ /// Since the event isn't required to be serializable this is not networked.
+ /// Only use this if it's predicted or for a clientside action.
+ ///
+ public void SetEvent(EntityUid uid, BaseActionEvent ev)
+ {
+ // now this is meta
+ var setEv = new ActionSetEventEvent(ev);
+ RaiseLocalEvent(uid, ref setEv);
+ if (!setEv.Handled)
+ Log.Error($"Tried to set event of {ToPrettyString(uid):action} but nothing handled it!");
+ }
+
+ public BaseActionEvent? GetEvent(EntityUid uid)
+ {
+ DebugTools.Assert(_actionQuery.HasComp(uid), $"Entity {ToPrettyString(uid)} is missing ActionComponent");
+ var ev = new ActionGetEventEvent();
+ RaiseLocalEvent(uid, ref ev);
+ return ev.Event;
+ }
+
+ public bool SetEventTarget(EntityUid uid, EntityUid target)
+ {
+ DebugTools.Assert(_actionQuery.HasComp(uid), $"Entity {ToPrettyString(uid)} is missing ActionComponent");
+ var ev = new ActionSetTargetEvent(target);
+ RaiseLocalEvent(uid, ref ev);
+ return ev.Handled;
}
///
/// Checks if the action has a cooldown and if it's still active
///
- public bool IsCooldownActive(BaseActionComponent action, TimeSpan? curTime = null)
+ public bool IsCooldownActive(ActionComponent action, TimeSpan? curTime = null)
{
// TODO: Check for charge recovery timer
return action.Cooldown.HasValue && action.Cooldown.Value.End > curTime;
diff --git a/Content.Shared/Actions/WorldTargetActionComponent.cs b/Content.Shared/Actions/WorldTargetActionComponent.cs
deleted file mode 100644
index 8066a64034..0000000000
--- a/Content.Shared/Actions/WorldTargetActionComponent.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Actions;
-
-///
-/// Used on action entities to define an action that triggers when targeting an entity coordinate.
-///
-[RegisterComponent, NetworkedComponent]
-public sealed partial class WorldTargetActionComponent : BaseTargetActionComponent
-{
- public override BaseActionEvent? BaseEvent => Event;
-
- ///
- /// The local-event to raise when this action is performed.
- ///
- [DataField("event")]
- [NonSerialized]
- public WorldTargetActionEvent? Event;
-}
-
-[Serializable, NetSerializable]
-public sealed class WorldTargetActionComponentState : BaseActionComponentState
-{
- public WorldTargetActionComponentState(WorldTargetActionComponent component, IEntityManager entManager) : base(component, entManager)
- {
- }
-}
diff --git a/Content.Shared/Bed/SharedBedSystem.cs b/Content.Shared/Bed/SharedBedSystem.cs
index 903bfefc20..cdf3ffcbd4 100644
--- a/Content.Shared/Bed/SharedBedSystem.cs
+++ b/Content.Shared/Bed/SharedBedSystem.cs
@@ -42,7 +42,7 @@ public abstract class SharedBedSystem : EntitySystem
private void OnUnstrapped(Entity bed, ref UnstrappedEvent args)
{
- _actionsSystem.RemoveAction(args.Buckle, bed.Comp.SleepAction);
+ _actionsSystem.RemoveAction(args.Buckle.Owner, bed.Comp.SleepAction);
_sleepingSystem.TryWaking(args.Buckle.Owner);
RemComp(bed);
}
diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs
index 916bab09e2..d1ca138431 100644
--- a/Content.Shared/Bed/Sleep/SleepingSystem.cs
+++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Buckle.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Events;
@@ -255,7 +256,7 @@ public sealed partial class SleepingSystem : EntitySystem
private void Wake(Entity ent)
{
RemComp(ent);
- _actionsSystem.RemoveAction(ent, ent.Comp.WakeAction);
+ _actionsSystem.RemoveAction(ent.Owner, ent.Comp.WakeAction);
var ev = new SleepStateChangedEvent(false);
RaiseLocalEvent(ent, ref ev);
diff --git a/Content.Shared/Clothing/Components/ToggleClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
index f827cbfea8..c92ab10b47 100644
--- a/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
+++ b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Toggleable;
@@ -23,7 +23,7 @@ public sealed partial class ToggleClothingComponent : Component
/// This must raise to then get handled.
///
[DataField(required: true)]
- public EntProtoId Action = string.Empty;
+ public EntProtoId Action;
[DataField, AutoNetworkedField]
public EntityUid? ActionEntity;
diff --git a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
index 4c8de4def1..334f88af6d 100644
--- a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
+++ b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
@@ -296,7 +296,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
}
if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action))
- _actionsSystem.SetEntityIcon(component.ActionEntity.Value, component.ClothingUid, action);
+ _actionsSystem.SetEntityIcon((component.ActionEntity.Value, action), component.ClothingUid);
}
}
diff --git a/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs b/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs
index 9a4afaec3a..1b62c45af4 100644
--- a/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs
+++ b/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs
@@ -36,7 +36,7 @@ public sealed class EyeClosingSystem : EntitySystem
private void OnShutdown(Entity eyelids, ref ComponentShutdown args)
{
- _actionsSystem.RemoveAction(eyelids, eyelids.Comp.EyeToggleActionEntity);
+ _actionsSystem.RemoveAction(eyelids.Owner, eyelids.Comp.EyeToggleActionEntity);
SetEyelids((eyelids.Owner, eyelids.Comp), false);
}
diff --git a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs
index aaedfb6c61..8d0e371b89 100644
--- a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs
+++ b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
@@ -73,23 +74,21 @@ public abstract partial class SharedItemRecallSystem : EntitySystem
if (!Resolve(ent.Owner, ref ent.Comp, false))
return;
- if (!TryComp(ent.Comp.MarkedByAction, out var instantAction))
+ if (_actions.GetAction(ent.Comp.MarkedByAction) is not {} action)
return;
- var actionOwner = instantAction.AttachedEntity;
-
- if (actionOwner == null)
+ if (action.Comp.AttachedEntity is not {} user)
return;
if (TryComp(ent, out var projectile))
- _proj.EmbedDetach(ent, projectile, actionOwner.Value);
+ _proj.EmbedDetach(ent, projectile, user);
_popups.PopupPredicted(Loc.GetString("item-recall-item-summon-self", ("item", ent)),
- Loc.GetString("item-recall-item-summon-others", ("item", ent), ("name", Identity.Entity(actionOwner.Value, EntityManager))),
- actionOwner.Value, actionOwner.Value);
- _popups.PopupPredictedCoordinates(Loc.GetString("item-recall-item-disappear", ("item", ent)), Transform(ent).Coordinates, actionOwner.Value);
+ Loc.GetString("item-recall-item-summon-others", ("item", ent), ("name", Identity.Entity(user, EntityManager))),
+ user, user);
+ _popups.PopupPredictedCoordinates(Loc.GetString("item-recall-item-disappear", ("item", ent)), Transform(ent).Coordinates, user);
- _hands.TryForcePickupAnyHand(actionOwner.Value, ent);
+ _hands.TryForcePickupAnyHand(user, ent);
}
private void OnRecallMarkerShutdown(Entity ent, ref ComponentShutdown args)
@@ -99,24 +98,22 @@ public abstract partial class SharedItemRecallSystem : EntitySystem
private void TryMarkItem(Entity ent, EntityUid item)
{
- if (!TryComp(ent, out var instantAction))
+ if (_actions.GetAction(ent.Owner) is not {} action)
return;
- var actionOwner = instantAction.AttachedEntity;
-
- if (actionOwner == null)
+ if (action.Comp.AttachedEntity is not {} user)
return;
- AddToPvsOverride(item, actionOwner.Value);
+ AddToPvsOverride(item, user);
- var marker = AddComp(item);
ent.Comp.MarkedEntity = item;
Dirty(ent);
- marker.MarkedByAction = ent.Owner;
-
- UpdateActionAppearance(ent);
+ var marker = AddComp(item);
+ marker.MarkedByAction = ent;
Dirty(item, marker);
+
+ UpdateActionAppearance((action, action, ent));
}
private void TryUnmarkItem(EntityUid item)
@@ -124,52 +121,47 @@ public abstract partial class SharedItemRecallSystem : EntitySystem
if (!TryComp(item, out var marker))
return;
- if (!TryComp(marker.MarkedByAction, out var instantAction))
+ if (_actions.GetAction(marker.MarkedByAction) is not {} action)
return;
- if (TryComp(marker.MarkedByAction, out var action))
+ if (TryComp(action, out var itemRecall))
{
// For some reason client thinks the station grid owns the action on client and this doesn't work. It doesn't work in PopupEntity(mispredicts) and PopupPredicted either(doesnt show).
// I don't have the heart to move this code to server because of this small thing.
// This line will only do something once that is fixed.
- if (instantAction.AttachedEntity != null)
+ if (action.Comp.AttachedEntity is {} user)
{
- _popups.PopupClient(Loc.GetString("item-recall-item-unmark", ("item", item)), instantAction.AttachedEntity.Value, instantAction.AttachedEntity.Value, PopupType.MediumCaution);
- RemoveFromPvsOverride(item, instantAction.AttachedEntity.Value);
+ _popups.PopupClient(Loc.GetString("item-recall-item-unmark", ("item", item)), user, user, PopupType.MediumCaution);
+ RemoveFromPvsOverride(item, user);
}
- action.MarkedEntity = null;
- UpdateActionAppearance((marker.MarkedByAction.Value, action));
- Dirty(marker.MarkedByAction.Value, action);
+ itemRecall.MarkedEntity = null;
+ UpdateActionAppearance((action, action, itemRecall));
+ Dirty(action, itemRecall);
}
RemCompDeferred(item);
}
- private void UpdateActionAppearance(Entity action)
+ private void UpdateActionAppearance(Entity action)
{
- if (!TryComp(action, out var instantAction))
- return;
-
- if (action.Comp.MarkedEntity == null)
+ if (action.Comp2.MarkedEntity is {} marked)
{
- if (action.Comp.InitialName != null)
- _metaData.SetEntityName(action, action.Comp.InitialName);
- if (action.Comp.InitialDescription != null)
- _metaData.SetEntityDescription(action, action.Comp.InitialDescription);
- _actions.SetEntityIcon(action, null, instantAction);
+ if (action.Comp2.WhileMarkedName is {} name)
+ _metaData.SetEntityName(action, Loc.GetString(name, ("item", marked)));
+
+ if (action.Comp2.WhileMarkedDescription is {} desc)
+ _metaData.SetEntityDescription(action, Loc.GetString(desc, ("item", marked)));
+
+ _actions.SetEntityIcon((action, action), marked);
}
else
{
- if (action.Comp.WhileMarkedName != null)
- _metaData.SetEntityName(action, Loc.GetString(action.Comp.WhileMarkedName,
- ("item", action.Comp.MarkedEntity.Value)));
-
- if (action.Comp.WhileMarkedDescription != null)
- _metaData.SetEntityDescription(action, Loc.GetString(action.Comp.WhileMarkedDescription,
- ("item", action.Comp.MarkedEntity.Value)));
-
- _actions.SetEntityIcon(action, action.Comp.MarkedEntity, instantAction);
+ if (action.Comp2.InitialName is {} name)
+ _metaData.SetEntityName(action, name);
+ if (action.Comp2.InitialDescription is {} desc)
+ _metaData.SetEntityDescription(action, desc);
+ _actions.SetEntityIcon((action, action), null);
}
}
diff --git a/Content.Shared/Magic/SpellbookSystem.cs b/Content.Shared/Magic/SpellbookSystem.cs
index 39fa16f622..6ef0a10b0c 100644
--- a/Content.Shared/Magic/SpellbookSystem.cs
+++ b/Content.Shared/Magic/SpellbookSystem.cs
@@ -1,4 +1,5 @@
-using Content.Shared.Actions;
+using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Interaction.Events;
@@ -56,7 +57,7 @@ public sealed class SpellbookSystem : EntitySystem
if (!ent.Comp.LearnPermanently)
{
- _actions.GrantActions(args.Args.User, ent.Comp.Spells, ent);
+ _actions.GrantActions(args.Args.User, ent.Comp.Spells, ent.Owner);
return;
}
diff --git a/Content.Shared/Mapping/StartPlacementActionEvent.cs b/Content.Shared/Mapping/StartPlacementActionEvent.cs
index 9e5e267898..2091068b63 100644
--- a/Content.Shared/Mapping/StartPlacementActionEvent.cs
+++ b/Content.Shared/Mapping/StartPlacementActionEvent.cs
@@ -1,18 +1,20 @@
-using Content.Shared.Actions;
+using Content.Shared.Actions;
+using Content.Shared.Maps;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Mapping;
public sealed partial class StartPlacementActionEvent : InstantActionEvent
{
- [DataField("entityType")]
- public string? EntityType;
+ [DataField]
+ public EntProtoId? EntityType;
- [DataField("tileId")]
- public string? TileId;
+ [DataField]
+ public ProtoId? TileId;
- [DataField("placementOption")]
+ [DataField]
public string? PlacementOption;
- [DataField("eraser")]
+ [DataField]
public bool Eraser;
}
diff --git a/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs b/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs
index cb48ef6997..a597e03406 100644
--- a/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs
+++ b/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Actions;
+using Content.Shared.Actions;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Content.Shared.Mindshield.Components;
@@ -28,7 +28,8 @@ public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
if (!TryComp(ent, out var comp))
return;
- _actionsSystem.SetToggled(ev.Action, !comp.IsEnabled); // Set it to what the Mindshield component WILL be after this
+ // TODO: is there a reason this cant set ev.Toggle = true;
+ _actionsSystem.SetToggled((ev.Action, ev.Action), !comp.IsEnabled); // Set it to what the Mindshield component WILL be after this
RaiseLocalEvent(ent, ev); //this reraises the action event to support an eventual future Changeling Antag which will also be using this component for it's "mindshield" ability
}
private void ImplantCheck(EntityUid uid, FakeMindShieldImplantComponent component ,ref ImplantImplantedEvent ev)
diff --git a/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs b/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs
index 9419daf348..8a442cd4b9 100644
--- a/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs
+++ b/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Mobs.Components;
namespace Content.Shared.Mobs.Systems;
diff --git a/Content.Shared/Ninja/Components/DashAbilityComponent.cs b/Content.Shared/Ninja/Components/DashAbilityComponent.cs
index 464f48f187..de63506f37 100644
--- a/Content.Shared/Ninja/Components/DashAbilityComponent.cs
+++ b/Content.Shared/Ninja/Components/DashAbilityComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Ninja.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
diff --git a/Content.Shared/Ninja/Components/ItemCreatorComponent.cs b/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
index d9f66d21a3..c000b3e0c1 100644
--- a/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
+++ b/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Ninja.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -22,7 +23,7 @@ public sealed partial class ItemCreatorComponent : Component
/// The action id for creating an item.
///
[DataField(required: true)]
- public EntProtoId Action = string.Empty;
+ public EntProtoId Action;
[DataField, AutoNetworkedField]
public EntityUid? ActionEntity;
diff --git a/Content.Shared/PAI/SharedPAISystem.cs b/Content.Shared/PAI/SharedPAISystem.cs
index c100e38a76..00d3e23cef 100644
--- a/Content.Shared/PAI/SharedPAISystem.cs
+++ b/Content.Shared/PAI/SharedPAISystem.cs
@@ -30,7 +30,7 @@ public abstract class SharedPAISystem : EntitySystem
private void OnShutdown(Entity ent, ref ComponentShutdown args)
{
- _actions.RemoveAction(ent, ent.Comp.ShopAction);
+ _actions.RemoveAction(ent.Owner, ent.Comp.ShopAction);
}
}
public sealed partial class PAIShopActionEvent : InstantActionEvent
diff --git a/Content.Shared/RatKing/SharedRatKingSystem.cs b/Content.Shared/RatKing/SharedRatKingSystem.cs
index ea489e332d..edb2ab90db 100644
--- a/Content.Shared/RatKing/SharedRatKingSystem.cs
+++ b/Content.Shared/RatKing/SharedRatKingSystem.cs
@@ -1,4 +1,5 @@
-using Content.Shared.Actions;
+using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.DoAfter;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
@@ -60,12 +61,13 @@ public abstract class SharedRatKingSystem : EntitySystem
if (!TryComp(uid, out ActionsComponent? comp))
return;
- _action.RemoveAction(uid, component.ActionRaiseArmyEntity, comp);
- _action.RemoveAction(uid, component.ActionDomainEntity, comp);
- _action.RemoveAction(uid, component.ActionOrderStayEntity, comp);
- _action.RemoveAction(uid, component.ActionOrderFollowEntity, comp);
- _action.RemoveAction(uid, component.ActionOrderCheeseEmEntity, comp);
- _action.RemoveAction(uid, component.ActionOrderLooseEntity, comp);
+ var actions = new Entity(uid, comp);
+ _action.RemoveAction(actions, component.ActionRaiseArmyEntity);
+ _action.RemoveAction(actions, component.ActionDomainEntity);
+ _action.RemoveAction(actions, component.ActionOrderStayEntity);
+ _action.RemoveAction(actions, component.ActionOrderFollowEntity);
+ _action.RemoveAction(actions, component.ActionOrderCheeseEmEntity);
+ _action.RemoveAction(actions, component.ActionOrderLooseEntity);
}
private void OnOrderAction(EntityUid uid, RatKingComponent component, RatKingOrderActionEvent args)
diff --git a/Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs b/Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs
index d9abeb2d32..55287e4d23 100644
--- a/Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs
+++ b/Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Actions;
+using Content.Shared.Actions;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Movement.Components;
@@ -56,7 +56,7 @@ public abstract class SharedBorgSwitchableTypeSystem : EntitySystem
private void OnShutdown(Entity ent, ref ComponentShutdown args)
{
- _actionsSystem.RemoveAction(ent, ent.Comp.SelectTypeAction);
+ _actionsSystem.RemoveAction(ent.Owner, ent.Comp.SelectTypeAction);
}
private void OnSelectBorgTypeAction(Entity ent, ref BorgToggleSelectTypeEvent args)
@@ -90,7 +90,7 @@ public abstract class SharedBorgSwitchableTypeSystem : EntitySystem
{
ent.Comp.SelectedBorgType = borgType;
- _actionsSystem.RemoveAction(ent, ent.Comp.SelectTypeAction);
+ _actionsSystem.RemoveAction(ent.Owner, ent.Comp.SelectTypeAction);
ent.Comp.SelectTypeAction = null;
Dirty(ent);
diff --git a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
index b01d3d34f6..ce037c7c2e 100644
--- a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
+++ b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
using Content.Shared.Destructible.Thresholds;
using Content.Shared.EntityTable.EntitySelectors;
using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
diff --git a/Resources/Prototypes/Actions/anomaly.yml b/Resources/Prototypes/Actions/anomaly.yml
index 65c6ae164e..f89ccce92c 100644
--- a/Resources/Prototypes/Actions/anomaly.yml
+++ b/Resources/Prototypes/Actions/anomaly.yml
@@ -1,9 +1,11 @@
- type: entity
+ parent: BaseAction
id: ActionAnomalyPulse
name: Anomaly pulse
description: Release a pulse of energy of your abnormal nature
components:
- - type: InstantAction
+ - type: Action
+ useDelay: 30
icon: Structures/Specific/anomaly.rsi/anom1.png
+ - type: InstantAction
event: !type:ActionAnomalyPulseEvent
- useDelay: 30
\ No newline at end of file
diff --git a/Resources/Prototypes/Actions/borgs.yml b/Resources/Prototypes/Actions/borgs.yml
index 680bd730f5..59c008352c 100644
--- a/Resources/Prototypes/Actions/borgs.yml
+++ b/Resources/Prototypes/Actions/borgs.yml
@@ -1,26 +1,28 @@
-- type: entity
+- type: entity
+ parent: BaseMentalAction # allow reading laws when crit
id: ActionViewLaws
name: View Laws
description: View the laws that you must follow.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
itemIconStyle: NoItem
icon:
sprite: Interface/Actions/actions_borg.rsi
state: state-laws
- event: !type:ToggleLawsScreenEvent
useDelay: 0.5
+ - type: InstantAction
+ event: !type:ToggleLawsScreenEvent
- type: entity
+ parent: BaseAction
id: ActionSelectBorgType
name: Select Cyborg Type
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: NoItem
icon:
sprite: Interface/Actions/actions_borg.rsi
state: select-type
- event: !type:BorgToggleSelectTypeEvent
useDelay: 0.5
+ - type: InstantAction
+ event: !type:BorgToggleSelectTypeEvent
diff --git a/Resources/Prototypes/Actions/crit.yml b/Resources/Prototypes/Actions/crit.yml
index aaad27d2b7..8dd3e1ece3 100644
--- a/Resources/Prototypes/Actions/crit.yml
+++ b/Resources/Prototypes/Actions/crit.yml
@@ -1,47 +1,50 @@
# Actions added to mobs in crit.
- type: entity
+ abstract: true
+ parent: BaseMentalAction
+ id: BaseCritAction
+ components:
+ - type: Action
+ itemIconStyle: NoItem
+
+- type: entity
+ parent: BaseCritAction
id: ActionCritSuccumb
name: Succumb
description: Accept your fate.
components:
- - type: InstantAction
- itemIconStyle: NoItem
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
+ startDelay: true
+ useDelay: 10
icon:
sprite: Mobs/Ghosts/ghost_human.rsi
state: icon
+ - type: InstantAction
event: !type:CritSuccumbEvent
- startDelay: true
- useDelay: 10
- type: entity
+ parent: BaseCritAction
id: ActionCritFakeDeath
name: Fake Death
description: Pretend to take your final breath while staying alive.
components:
- - type: InstantAction
- itemIconStyle: NoItem
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
+ useDelay: 30
icon:
sprite: Interface/Actions/actions_crit.rsi
state: fakedeath
+ - type: InstantAction
event: !type:CritFakeDeathEvent
- useDelay: 30
- type: entity
+ parent: ActionCritSuccumb # for use delay
id: ActionCritLastWords
name: Say Last Words
description: Whisper your last words to anyone nearby, and then succumb to your fate. You only have 30 characters to work with.
components:
- - type: InstantAction
- itemIconStyle: NoItem
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon:
sprite: Interface/Actions/actions_crit.rsi
state: lastwords
+ - type: InstantAction
event: !type:CritLastWordsEvent
- startDelay: true
- useDelay: 10
diff --git a/Resources/Prototypes/Actions/diona.yml b/Resources/Prototypes/Actions/diona.yml
index 9f80f18178..be127b61d1 100644
--- a/Resources/Prototypes/Actions/diona.yml
+++ b/Resources/Prototypes/Actions/diona.yml
@@ -1,24 +1,26 @@
- type: entity
+ parent: BaseSuicideAction
id: DionaGibAction
name: Gib Yourself!
description: Split apart into 3 nymphs.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Mobs/Species/Diona/organs.rsi
state: brain
+ - type: InstantAction
event: !type:GibActionEvent {}
- checkCanInteract: false
- checkConsciousness: false
- type: entity
+ parent: BaseAction
id: DionaReformAction
name: Reform
description: Reform back into a whole Diona.
components:
- - type: InstantAction
+ - type: Action
+ useDelay: 600 # Once every 10 minutes. Keep them dead for a fair bit before reforming
icon:
sprite: Mobs/Species/Diona/parts.rsi
state: full
+ - type: InstantAction
event: !type:ReformEvent {}
- useDelay: 600 # Once every 10 minutes. Keep them dead for a fair bit before reforming
diff --git a/Resources/Prototypes/Actions/internals.yml b/Resources/Prototypes/Actions/internals.yml
index 5982c3daa2..d3c1e95fc0 100644
--- a/Resources/Prototypes/Actions/internals.yml
+++ b/Resources/Prototypes/Actions/internals.yml
@@ -1,14 +1,16 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionToggleInternals
name: Toggle Internals
description: Breathe from the equipped gas tank. Also requires equipped breath mask.
components:
- - type: InstantAction
+ - type: Action
+ useDelay: 1
icon:
sprite: Interface/Alerts/internals.rsi
state: internal2
iconOn:
sprite: Interface/Alerts/internals.rsi
state: internal1
+ - type: InstantAction
event: !type:ToggleActionEvent
- useDelay: 1
diff --git a/Resources/Prototypes/Actions/mapping.yml b/Resources/Prototypes/Actions/mapping.yml
new file mode 100644
index 0000000000..937fc9263c
--- /dev/null
+++ b/Resources/Prototypes/Actions/mapping.yml
@@ -0,0 +1,48 @@
+- type: entity
+ abstract: true
+ parent: BaseMentalAction
+ id: BaseMappingAction
+ components:
+ - type: Action
+ clientExclusive: true
+
+- type: entity
+ parent: BaseMappingAction
+ id: BaseMappingDecalAction # not abstract but the event has to be set in code
+ components:
+ - type: TargetAction
+ repeat: true
+ range: -1
+ - type: WorldTargetAction
+ event: null # has to be set with SetEvent in DecalPlacementSystem
+
+- type: entity
+ parent: BaseMappingAction
+ id: BaseMappingSpawnAction # not abstract but the event has to be set in code
+ components:
+ - type: Action
+ icon: Tiles/cropped_parallax.png
+ - type: InstantAction
+ event: null # has to be set with SetEvent in MappingSystem
+
+- type: entity
+ parent: BaseMappingAction
+ id: ActionMappingEraser
+ name: action-name-mapping-erase
+ components:
+ - type: Action
+ icon: Interface/VerbIcons/delete.svg.192dpi.png
+ - type: InstantAction
+ event: !type:StartPlacementActionEvent
+ eraser: true
+
+# these are used for mapping actions yml files
+- type: entity
+ parent: BaseMappingSpawnAction
+ id: BaseMappingEntityAction # not abstract but the event has to be set in code
+ components:
+ - type: Action
+ autoPopulate: False
+ temporary: True
+ - type: InstantAction
+ event: null # has to be set with SetEvent in ActionsSystem
diff --git a/Resources/Prototypes/Actions/mech.yml b/Resources/Prototypes/Actions/mech.yml
index 48092f9c5a..1db758fc97 100644
--- a/Resources/Prototypes/Actions/mech.yml
+++ b/Resources/Prototypes/Actions/mech.yml
@@ -1,37 +1,48 @@
- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseMechAction
+ components:
+ - type: Action
+ itemIconStyle: NoItem
+
+- type: entity
+ parent: BaseMechAction
id: ActionMechCycleEquipment
name: Cycle
description: Cycles currently selected equipment
components:
- - type: InstantAction
- itemIconStyle: NoItem
+ - type: Action
+ useDelay: 0.5
icon:
sprite: Interface/Actions/actions_mecha.rsi
state: mech_cycle_equip_on
+ - type: InstantAction
event: !type:MechToggleEquipmentEvent
- useDelay: 0.5
- type: entity
+ parent: BaseMechAction
id: ActionMechOpenUI
name: Control Panel
description: Opens the control panel for the mech
components:
- - type: InstantAction
- itemIconStyle: NoItem
+ - type: Action
+ useDelay: 1
icon:
sprite: Interface/Actions/actions_mecha.rsi
state: mech_view_stats
+ - type: InstantAction
event: !type:MechOpenUiEvent
- useDelay: 1
- type: entity
+ parent: BaseMechAction
id: ActionMechEject
name: Eject
description: Ejects the pilot from the mech
components:
- - type: InstantAction
- itemIconStyle: NoItem
+ - type: Action
icon:
sprite: Interface/Actions/actions_mecha.rsi
state: mech_eject
+ - type: InstantAction
event: !type:MechEjectPilotEvent
diff --git a/Resources/Prototypes/Actions/ninja.yml b/Resources/Prototypes/Actions/ninja.yml
index 47cb8a83f4..c1fcf8dd7a 100644
--- a/Resources/Prototypes/Actions/ninja.yml
+++ b/Resources/Prototypes/Actions/ninja.yml
@@ -1,73 +1,82 @@
# gloves
- type: entity
+ parent: BaseToggleAction
id: ActionToggleNinjaGloves
name: Toggle ninja gloves
description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies and hacking certain computers.
components:
- - type: InstantAction
+ - type: Action
priority: -13
- event: !type:ToggleActionEvent {}
# suit
- type: entity
+ parent: BaseAction
id: ActionCreateThrowingStar
name: Create throwing star
description: Channels suit power into creating a throwing star that deals extra stamina damage.
components:
- - type: InstantAction
+ - type: Action
useDelay: 0.5
icon:
sprite: Objects/Weapons/Throwable/throwing_star.rsi
state: icon
itemIconStyle: NoItem
priority: -10
+ - type: InstantAction
event: !type:CreateItemEvent {}
- type: entity
+ parent: BaseAction
id: ActionRecallKatana
name: Recall katana
description: Teleports the Energy Katana linked to this suit to its wearer, cost based on distance.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
icon:
sprite: Objects/Weapons/Melee/energykatana.rsi
state: icon
itemIconStyle: NoItem
priority: -11
+ - type: InstantAction
event: !type:RecallKatanaEvent {}
- type: entity
+ parent: BaseAction
id: ActionNinjaEmp
name: EM Burst
description: Disable any nearby technology with an electro-magnetic pulse.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Objects/Weapons/Grenades/empgrenade.rsi
state: icon
itemIconStyle: BigAction
priority: -13
+ - type: InstantAction
event: !type:NinjaEmpEvent {}
- type: entity
+ parent: BaseAction
id: ActionTogglePhaseCloak
name: Phase cloak
description: Toggles your suit's phase cloak. Beware that if you are hit, all abilities are disabled for 5 seconds, including your cloak!
components:
- - type: InstantAction
+ - type: Action
# have to plan (un)cloaking ahead of time
useDelay: 5
priority: -9
+ - type: InstantAction
event: !type:ToggleActionEvent
# katana
- type: entity
+ parent: BaseAction
id: ActionEnergyKatanaDash
name: Katana dash
description: Teleport to anywhere you can see, if your Energy Katana is in your hand.
components:
- - type: WorldTargetAction
+ - type: Action
icon:
sprite: Objects/Magic/magicactions.rsi
state: blink
@@ -77,6 +86,8 @@
params:
volume: 5
priority: -12
- event: !type:DashEvent
+ - type: TargetAction
checkCanAccess: false
range: 0
+ - type: WorldTargetAction
+ event: !type:DashEvent
diff --git a/Resources/Prototypes/Actions/polymorph.yml b/Resources/Prototypes/Actions/polymorph.yml
index 291400ab38..2f931e29e2 100644
--- a/Resources/Prototypes/Actions/polymorph.yml
+++ b/Resources/Prototypes/Actions/polymorph.yml
@@ -1,4 +1,5 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionRevertPolymorph
name: Revert
description: Revert back into your original form.
@@ -7,54 +8,59 @@
event: !type:RevertPolymorphActionEvent
- type: entity
- id: ActionPolymorph
+ abstract: true
+ parent: BaseAction
+ id: BaseActionPolymorph
components:
+ - type: Action
+ itemIconStyle: NoItem
+ useDelay: 60
- type: InstantAction
event: !type:PolymorphActionEvent
- itemIconStyle: NoItem
- type: entity
+ parent: BaseActionPolymorph
id: ActionPolymorphWizardSpider
name: Spider Polymorph
description: Polymorphs you into a Spider.
components:
- - type: InstantAction
- useDelay: 60
- event: !type:PolymorphActionEvent
- protoId: WizardSpider
- itemIconStyle: NoItem
+ - type: Action
icon:
sprite: Mobs/Animals/spider.rsi
state: tarantula
+ - type: InstantAction
+ event: !type:PolymorphActionEvent
+ protoId: WizardSpider
- type: entity
+ parent: BaseActionPolymorph
id: ActionPolymorphWizardRod
name: Rod Form
description: CLANG!
components:
- - type: InstantAction
- useDelay: 60
- event: !type:PolymorphActionEvent
- protoId: WizardRod
- itemIconStyle: NoItem
+ - type: Action
icon:
sprite: Objects/Fun/immovable_rod.rsi
state: icon
+ - type: InstantAction
+ event: !type:PolymorphActionEvent
+ protoId: WizardRod
- type: entity
+ parent: BaseActionPolymorph
id: ActionPolymorphJaunt
name: Ethereal Jaunt
description: Melt into the Ethereal Plane for a quick getaway!
components:
- type: Magic
- - type: InstantAction
+ - type: Action
useDelay: 30
- event: !type:PolymorphActionEvent
- protoId: Jaunt
- itemIconStyle: NoItem
icon:
sprite: Objects/Magic/magicactions.rsi
state: jaunt
+ - type: InstantAction
+ event: !type:PolymorphActionEvent
+ protoId: Jaunt
# TODO: Effect ECS (from cardboard box)
- type: ActionUpgrade
effectedLevels:
@@ -62,31 +68,19 @@
3: ActionPolymorphJauntIII
- type: entity
- id: ActionPolymorphJauntII
parent: ActionPolymorphJaunt
+ id: ActionPolymorphJauntII
name: Ethereal Jaunt II
description: Melt into the Ethereal Plane for an even quicker getaway!
components:
- - type: InstantAction
+ - type: Action
useDelay: 25
- event: !type:PolymorphActionEvent
- protoId: Jaunt
- itemIconStyle: NoItem
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: jaunt
- type: entity
- id: ActionPolymorphJauntIII
parent: ActionPolymorphJaunt
+ id: ActionPolymorphJauntIII
name: Ethereal Jaunt III
description: Are you even tangible anymore?
components:
- - type: InstantAction
+ - type: Action
useDelay: 20
- event: !type:PolymorphActionEvent
- protoId: Jaunt
- itemIconStyle: NoItem
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: jaunt
diff --git a/Resources/Prototypes/Actions/revenant.yml b/Resources/Prototypes/Actions/revenant.yml
index dca491a99b..5904908a75 100644
--- a/Resources/Prototypes/Actions/revenant.yml
+++ b/Resources/Prototypes/Actions/revenant.yml
@@ -1,48 +1,58 @@
- type: entity
+ parent: BaseAction
id: ActionRevenantShop
name: Shop
description: Opens the ability shop.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/shop.png
+ - type: InstantAction
event: !type:RevenantShopActionEvent
- type: entity
+ parent: BaseAction
id: ActionRevenantDefile
name: Defile
description: Costs 30 Essence.
components:
- - type: InstantAction
- icon: Interface/Actions/defile.png
- event: !type:RevenantDefileActionEvent
+ - type: Action
useDelay: 15
+ icon: Interface/Actions/defile.png
+ - type: InstantAction
+ event: !type:RevenantDefileActionEvent
- type: entity
+ parent: BaseAction
id: ActionRevenantOverloadLights
name: Overload Lights
description: Costs 40 Essence.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/overloadlight.png
- event: !type:RevenantOverloadLightsActionEvent
useDelay: 20
+ - type: InstantAction
+ event: !type:RevenantOverloadLightsActionEvent
#- type: entity
+# parent: BaseAction
# id: ActionRevenantBlight
# name: Blight
# description: Costs 50 Essence.
# components:
-# - type: InstantAction
+# - type: Action
# icon: Interface/Actions/blight.png
-# event: !type:RevenantBlightActionEvent
# useDelay: 20
+# - type: InstantAction
+# event: !type:RevenantBlightActionEvent
- type: entity
+ parent: BaseAction
id: ActionRevenantMalfunction
name: Malfunction
description: Costs 60 Essence.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/malfunction.png
- event: !type:RevenantMalfunctionActionEvent
useDelay: 20
+ - type: InstantAction
+ event: !type:RevenantMalfunctionActionEvent
diff --git a/Resources/Prototypes/Actions/security.yml b/Resources/Prototypes/Actions/security.yml
index ace9d914e4..0c479a2424 100644
--- a/Resources/Prototypes/Actions/security.yml
+++ b/Resources/Prototypes/Actions/security.yml
@@ -1,5 +1,6 @@
# gloves
- type: entity
+ parent: BaseAction
id: ActionToggleKnuckleDustersStun
name: Toggle stun knuckle dusters
description: Toggles the duster's built in stun baton.
diff --git a/Resources/Prototypes/Actions/speech.yml b/Resources/Prototypes/Actions/speech.yml
index c71ec74880..24cb7646a8 100644
--- a/Resources/Prototypes/Actions/speech.yml
+++ b/Resources/Prototypes/Actions/speech.yml
@@ -1,10 +1,12 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionConfigureMeleeSpeech
name: Set Battlecry
description: Set a custom battlecry for when you attack!
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigItem
priority: -20
useDelay: 1
+ - type: InstantAction
event: !type:MeleeSpeechConfigureActionEvent
diff --git a/Resources/Prototypes/Actions/spider.yml b/Resources/Prototypes/Actions/spider.yml
index fe37085eca..da3785ed84 100644
--- a/Resources/Prototypes/Actions/spider.yml
+++ b/Resources/Prototypes/Actions/spider.yml
@@ -1,19 +1,23 @@
- type: entity
+ parent: BaseAction
id: ActionSpiderWeb
name: Spider Web
description: Spawns a web that slows your prey down.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/web.png
- event: !type:SpiderWebActionEvent
useDelay: 25
+ - type: InstantAction
+ event: !type:SpiderWebActionEvent
- type: entity
+ parent: BaseAction
id: ActionSericulture
name: Weave silk
description: Weave a bit of silk for use in arts and crafts.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/web.png
- event: !type:SericultureActionEvent
useDelay: 1
+ - type: InstantAction
+ event: !type:SericultureActionEvent
diff --git a/Resources/Prototypes/Actions/station_ai.yml b/Resources/Prototypes/Actions/station_ai.yml
index 67d5626219..4dbaf07aab 100644
--- a/Resources/Prototypes/Actions/station_ai.yml
+++ b/Resources/Prototypes/Actions/station_ai.yml
@@ -1,28 +1,32 @@
-# Actions
+# Actions
- type: entity
+ parent: BaseAction
id: ActionJumpToCore
name: Jump to core
description: Sends your eye back to the core.
components:
- - type: InstantAction
+ - type: Action
priority: -9
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/actions_ai.rsi
state: ai_core
+ - type: InstantAction
event: !type:JumpToCoreEvent
- type: entity
+ parent: BaseAction
id: ActionSurvCameraLights
name: Toggle camera lights
description: Enable surveillance camera lights near wherever you're viewing.
components:
- - type: InstantAction
+ - type: Action
priority: -5
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/actions_ai.rsi
state: camera_light
+ - type: InstantAction
event: !type:RelayedActionComponentChangeEvent
components:
- type: LightOnCollideCollider
@@ -39,17 +43,17 @@
- type: entity
+ parent: BaseMentalAction
id: ActionAIViewLaws
name: View Laws
description: View the laws that you must follow.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
priority: -3
itemIconStyle: NoItem
icon:
sprite: Interface/Actions/actions_ai.rsi
state: state_laws
- event: !type:ToggleLawsScreenEvent
useDelay: 0.5
+ - type: InstantAction
+ event: !type:ToggleLawsScreenEvent
diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml
index e1da9bcf1f..945070dea0 100644
--- a/Resources/Prototypes/Actions/types.yml
+++ b/Resources/Prototypes/Actions/types.yml
@@ -1,130 +1,167 @@
# base actions
+# base prototype for all action entities
- type: entity
- id: BaseSuicideAction
abstract: true
+ id: BaseAction
+ components:
+ - type: Action
+
+# an action that is done all in le head and cant be prevented by any means
+- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseMentalAction
+ components:
+ - type: Action
+ checkCanInteract: false
+ checkConsciousness: false
+
+- type: entity
+ abstract: true
+ parent: BaseMentalAction
+ id: BaseSuicideAction
components:
- type: ConfirmableAction
popup: suicide-action-popup
+- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseImplantAction
+ components:
+ - type: InstantAction
+ event: !type:ActivateImplantEvent
+
+- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseToggleAction
+ components:
+ - type: InstantAction
+ event: !type:ToggleActionEvent
+
# actions
- type: entity
+ parent: BaseAction
id: ActionScream
name: Scream
description: AAAAAAAAAAAAAAAAAAAAAAAAA
components:
- - type: InstantAction
+ - type: Action
useDelay: 10
icon: Interface/Actions/scream.png
- event: !type:ScreamActionEvent
checkCanInteract: false
+ - type: InstantAction
+ event: !type:ScreamActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionTurnUndead
name: Turn Undead
description: Succumb to your infection and become a zombie.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon: Interface/Actions/zombie-turn.png
+ - type: InstantAction
event: !type:ZombifySelfActionEvent
- type: entity
+ parent: BaseToggleAction
id: ActionToggleLight
name: Toggle Light
description: Turn the light on and off.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight }
iconOn: { sprite: Objects/Tools/flashlight.rsi, state: flashlight-on }
- event: !type:ToggleActionEvent
- type: entity
+ parent: BaseAction
id: ActionOpenStorageImplant
name: Toggle Storage Implant
description: Opens or closes the storage implant embedded under your skin
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigAction
priority: -20
icon:
sprite: Clothing/Back/Backpacks/backpack.rsi
state: icon
- event: !type:OpenStorageImplantEvent
useDelay: 1
+ - type: InstantAction
+ event: !type:OpenStorageImplantEvent
- type: entity
- parent: BaseSuicideAction
+ parent: [BaseSuicideAction, BaseImplantAction]
id: ActionActivateMicroBomb
name: Activate Microbomb
description: Activates your internal microbomb, completely destroying you and your equipment
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
itemIconStyle: BigAction
priority: -20
icon:
sprite: Actions/Implants/implants.rsi
state: explosive
- event: !type:ActivateImplantEvent
- type: entity
- parent: BaseSuicideAction
+ parent: [BaseSuicideAction, BaseImplantAction]
id: ActionActivateDeathAcidifier
name: Activate Death-Acidifier
description: Activates your death-acidifier, completely melting you and your equipment
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
itemIconStyle: BigAction
priority: -20
icon:
sprite: Objects/Magic/magicactions.rsi
state: gib
- event: !type:ActivateImplantEvent
- type: entity
+ parent: BaseAction
id: ActionActivateFreedomImplant
name: Break Free
description: Activating your freedom implant will free you from any hand restraints
components:
- type: LimitedCharges
maxCharges: 3
- - type: InstantAction
+ - type: Action
checkCanInteract: false
itemIconStyle: BigAction
priority: -20
icon:
sprite: Actions/Implants/implants.rsi
state: freedom
+ - type: InstantAction
event: !type:UseFreedomImplantEvent
- type: entity
+ parent: BaseAction
id: ActionOpenUplinkImplant
name: Open Uplink
description: Opens the syndicate uplink embedded under your skin
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigAction
priority: -20
icon:
sprite: Objects/Devices/communication.rsi
state: old-radio
+ - type: InstantAction
event: !type:OpenUplinkImplantEvent
- type: entity
+ parent: BaseImplantAction
id: ActionActivateEmpImplant
name: Activate EMP
description: Triggers a small EMP pulse around you
components:
- type: LimitedCharges
maxCharges: 3
- - type: InstantAction
+ - type: Action
checkCanInteract: false
useDelay: 5
itemIconStyle: BigAction
@@ -132,16 +169,16 @@
icon:
sprite: Objects/Weapons/Grenades/empgrenade.rsi
state: icon
- event: !type:ActivateImplantEvent
- type: entity
+ parent: BaseAction
id: ActionActivateScramImplant
name: SCRAM!
description: Randomly teleports you within a large distance.
components:
- type: LimitedCharges
maxCharges: 2
- - type: InstantAction
+ - type: Action
checkCanInteract: false
useDelay: 5
itemIconStyle: BigAction
@@ -149,9 +186,11 @@
icon:
sprite: Structures/Specific/anomaly.rsi
state: anom4
+ - type: InstantAction
event: !type:UseScramImplantEvent
- type: entity
+ parent: BaseAction
id: ActionActivateDnaScramblerImplant
name: Scramble DNA
description: Randomly changes your name and appearance.
@@ -160,190 +199,199 @@
popup: dna-scrambler-action-popup
- type: LimitedCharges
maxCharges: 1
- - type: InstantAction
+ - type: Action
itemIconStyle: BigAction
priority: -20
icon:
sprite: Clothing/OuterClothing/Hardsuits/lingspacesuit.rsi
state: icon
+ - type: InstantAction
event: !type:UseDnaScramblerImplantEvent
- type: entity
+ parent: BaseAction
id: ActionToggleSuitPiece
name: Toggle Suit Piece
description: Remember to equip the important pieces of your suit before going into action.
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigItem
useDelay: 1 # equip noise spam.
+ - type: InstantAction
event: !type:ToggleClothingEvent
- type: entity
+ parent: BaseAction
id: ActionCombatModeToggle
name: "[color=red]Combat Mode[/color]"
description: Enter combat mode
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon: Interface/Actions/harmOff.png
iconOn: Interface/Actions/harm.png
- event: !type:ToggleCombatActionEvent
priority: -100
+ - type: InstantAction
+ event: !type:ToggleCombatActionEvent
- type: entity
- id: ActionCombatModeToggleOff
parent: ActionCombatModeToggle
- name: "[color=red]Combat Mode[/color]"
- description: Enter combat mode
+ id: ActionCombatModeToggleOff
components:
- - type: InstantAction
+ - type: Action
enabled: false
autoPopulate: false
- priority: -100
- type: entity
+ parent: BaseAction
id: ActionChangeVoiceMask
name: Set name
description: Change the name others hear to something else.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Interface/Actions/voice-mask.rsi, state: icon }
+ - type: InstantAction
event: !type:VoiceMaskSetNameEvent
- type: entity
+ parent: BaseAction
id: ActionVendingThrow
name: Dispense Item
description: Randomly dispense an item from your stock.
components:
- - type: InstantAction
+ - type: Action
useDelay: 30
+ - type: InstantAction
event: !type:VendingMachineSelfDispenseEvent
- type: entity
+ parent: BaseToggleAction
id: ActionToggleBlock
name: Block
description: Raise or lower your shield.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Objects/Weapons/Melee/shields.rsi, state: teleriot-icon }
- iconOn: Objects/Weapons/Melee/shields.rsi/teleriot-on.png
- event: !type:ToggleActionEvent
+ iconOn: { sprite: Objects/Weapons/Melee/shields.rsi, state: teleriot-on }
- type: entity
+ parent: BaseMentalAction
id: ActionClearNetworkLinkOverlays
name: Clear network link overlays
description: Clear network link overlays.
components:
- - type: InstantAction
+ - type: Action
clientExclusive: true
- checkCanInteract: false
- checkConsciousness: false
temporary: true
icon: { sprite: Objects/Tools/multitool.rsi, state: icon }
+ - type: InstantAction
event: !type:ClearAllOverlaysEvent
- type: entity
+ parent: BaseAction
id: ActionAnimalLayEgg
name: Lay egg
description: Uses hunger to lay an egg.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Objects/Consumable/Food/egg.rsi, state: icon }
useDelay: 60
+ - type: InstantAction
event: !type:EggLayInstantActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionSleep
name: Sleep
description: Go to sleep.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon: { sprite: Clothing/Head/Hats/pyjamasyndicatered.rsi, state: icon }
+ - type: InstantAction
event: !type:SleepActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionWake
name: Wake up
description: Stop sleeping.
components:
- - type: InstantAction
- icon: { sprite: Clothing/Head/Hats/pyjamasyndicatered.rsi, state: icon }
- checkCanInteract: false
- checkConsciousness: false
- event: !type:WakeActionEvent
+ - type: Action
startDelay: true
useDelay: 2
+ icon: { sprite: Clothing/Head/Hats/pyjamasyndicatered.rsi, state: icon }
+ - type: InstantAction
+ event: !type:WakeActionEvent
- type: entity
+ parent: BaseImplantAction
id: ActionActivateHonkImplant
name: Honk
description: Activates your honking implant, which will produce the signature sound of the clown.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon }
- event: !type:ActivateImplantEvent
- useDelay: 1
- type: entity
+ parent: BaseAction
id: ActionFireStarter
name: Ignite
description: Ignites enemies in a radius around you.
components:
- - type: InstantAction
+ - type: Action
priority: -1
useDelay: 30
icon: Interface/Actions/firestarter.png
+ - type: InstantAction
event: !type:FireStarterActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionToggleEyes
name: Open/Close eyes
description: Close your eyes to protect your peepers, or open your eyes to enjoy the pretty lights.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/eyeopen.png
iconOn: Interface/Actions/eyeclose.png
- event: !type:ToggleEyesActionEvent
useDelay: 1 # so u cant give yourself and observers eyestrain by rapidly spamming the action
- checkCanInteract: false
- checkConsciousness: false
+ - type: InstantAction
+ event: !type:ToggleEyesActionEvent
- type: entity
+ parent: BaseToggleAction
id: ActionToggleWagging
name: Wagging Tail
description: Start or stop wagging your tail.
components:
- - type: InstantAction
- icon: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind }
- iconOn: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind }
- itemIconStyle: NoItem
- useDelay: 1 # emote spam
- event: !type:ToggleActionEvent
+ - type: Action
+ icon: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind }
+ iconOn: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind }
+ itemIconStyle: NoItem
+ useDelay: 1 # emote spam
- type: entity
+ parent: BaseAction
id: FakeMindShieldToggleAction
name: '[color=green]Toggle Fake Mindshield[/color]'
description: Turn the Fake Mindshield implant's transmission on/off
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Interface/Actions/actions_fakemindshield.rsi, state: icon }
iconOn: { sprite: Interface/Actions/actions_fakemindshield.rsi, state: icon-on }
itemIconStyle: NoItem
useDelay: 1
+ - type: InstantAction
event: !type:FakeMindShieldToggleEvent
- type: entity
+ parent: BaseToggleAction
id: ActionToggleParamedicSiren
name: Toggle Paramedic Siren
description: Toggles the paramedic siren on and off.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Clothing/OuterClothing/Hardsuits/paramed.rsi
state: icon-siren
useDelay: 1
itemIconStyle: BigAction
- event: !type:ToggleActionEvent
diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
index a587294d76..4c38dde0d7 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
@@ -556,11 +556,13 @@
Hair: HEAD
- type: entity
+ parent: BaseAction
id: ActionToggleJusticeHelm
name: Toggle Justice Helm
description: Toggles the justice helm on and off.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
itemIconStyle: BigItem
+ - type: InstantAction
event: !type:ToggleActionEvent
diff --git a/Resources/Prototypes/Entities/Clothing/Head/misc.yml b/Resources/Prototypes/Entities/Clothing/Head/misc.yml
index a7c9e701ba..711a9b0513 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml
@@ -242,13 +242,10 @@
accent: OwOAccent
- type: entity
- categories: [ Actions, HideSpawnMenu ]
+ parent: BaseToggleAction
id: ActionBecomeValid
name: Become Valid
description: "*notices your killsign* owo whats this"
- components:
- - type: InstantAction
- event: !type:ToggleActionEvent
- type: entity
parent: ClothingHeadBase
diff --git a/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml b/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml
index 31e91df4a7..a112e4f0de 100644
--- a/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml
+++ b/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml
@@ -20,13 +20,15 @@
- type: Mask
- type: entity
+ parent: BaseAction
id: ActionToggleMask
name: Toggle Mask
description: Handy, but prevents insertion of pie into your pie hole.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Clothing/Mask/gas.rsi, state: icon }
iconOn: Interface/Default/blocked.png
+ - type: InstantAction
event: !type:ToggleMaskEvent
- type: entity
diff --git a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml
index 26071b5146..b25f78e6a4 100644
--- a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml
@@ -50,17 +50,20 @@
- type: Stethoscope
- type: entity
+ parent: BaseAction
id: ActionStethoscope
name: Listen with stethoscope
components:
- - type: EntityTargetAction
+ - type: Action
icon:
sprite: Clothing/Neck/Misc/stethoscope.rsi
state: icon
- event: !type:StethoscopeActionEvent
- checkCanInteract: false
priority: -1
itemIconStyle: BigAction
+ checkCanInteract: false
+ - type: TargetAction
+ - type: EntityTargetAction
+ event: !type:StethoscopeActionEvent
- type: entity
parent: ClothingNeckBase
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
index 6f88dde595..ad88dd1ae2 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
@@ -115,10 +115,12 @@
size: Normal
- type: entity
+ parent: BaseAction
id: ActionToggleMagboots
name: Toggle Magboots
description: Toggles the magboots on and off.
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigItem
+ - type: InstantAction
event: !type:ToggleActionEvent
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
index a04a6453f9..fafb9a0846 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
@@ -155,12 +155,14 @@
tags: []
- type: entity
+ parent: BaseAction
id: ActionToggleSpeedBoots
name: Toggle Speed Boots
description: Toggles the speed boots on and off.
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigItem
+ - type: InstantAction
event: !type:ToggleActionEvent
- type: entity
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml
index c4c131a7ab..672d29a637 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml
@@ -99,11 +99,12 @@
amount: 3
- type: entity
+ parent: BaseAction
id: ActionGoliathTentacle
name: "[color=red]Tentacle Slam[/color]"
description: Use your tentacles to grab and stun a target player!
components:
- - type: EntityWorldTargetAction
+ - type: Action
raiseOnUser: true
icon:
sprite: Mobs/Aliens/Asteroid/goliath.rsi
@@ -113,9 +114,11 @@
state: goliath_tentacle_wiggle
sound:
path: "/Audio/Weapons/slash.ogg"
- event: !type:GoliathSummonTentacleAction
useDelay: 8
+ - type: TargetAction
range: 10
+ - type: WorldTargetAction
+ event: !type:GoliathSummonTentacleAction
- type: entity
id: GoliathTentacle
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
index 4ba7ac7c55..5177d38fa4 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
@@ -315,35 +315,40 @@
IngotGold1: 5 #loot
- type: entity
+ parent: BaseAction
id: ActionRatKingRaiseArmy
name: Raise Army
description: Spend some hunger to summon an allied rat to help defend you.
components:
- - type: InstantAction
+ - type: Action
useDelay: 4
icon:
sprite: Interface/Actions/actions_rat_king.rsi
state: ratKingArmy
+ - type: InstantAction
event: !type:RatKingRaiseArmyActionEvent
- type: entity
+ parent: BaseAction
id: ActionRatKingDomain
name: Rat King's Domain
description: Spend some hunger to release a cloud of ammonia into the air.
components:
- - type: InstantAction
+ - type: Action
useDelay: 6
icon:
sprite: Interface/Actions/actions_rat_king.rsi
state: ratKingDomain
+ - type: InstantAction
event: !type:RatKingDomainActionEvent
- type: entity
+ parent: BaseAction
id: ActionRatKingOrderStay
name: Stay
description: Command your army to stand in place.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
icon:
sprite: Interface/Actions/actions_rat_king.rsi
@@ -351,17 +356,18 @@
iconOn:
sprite: Interface/Actions/actions_rat_king.rsi
state: stay
- event:
- !type:RatKingOrderActionEvent
- type: Stay
priority: 5
+ - type: InstantAction
+ event: !type:RatKingOrderActionEvent
+ type: Stay
- type: entity
+ parent: BaseAction
id: ActionRatKingOrderFollow
name: Follow
description: Command your army to follow you around.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
icon:
sprite: Interface/Actions/actions_rat_king.rsi
@@ -369,17 +375,18 @@
iconOn:
sprite: Interface/Actions/actions_rat_king.rsi
state: follow
- event:
- !type:RatKingOrderActionEvent
- type: Follow
priority: 6
+ - type: InstantAction
+ event: !type:RatKingOrderActionEvent
+ type: Follow
- type: entity
+ parent: BaseAction
id: ActionRatKingOrderCheeseEm
name: Cheese 'Em
description: Command your army to attack whoever you point at.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
icon:
sprite: Interface/Actions/actions_rat_king.rsi
@@ -387,17 +394,18 @@
iconOn:
sprite: Interface/Actions/actions_rat_king.rsi
state: attack
- event:
- !type:RatKingOrderActionEvent
- type: CheeseEm
priority: 7
+ - type: InstantAction
+ event: !type:RatKingOrderActionEvent
+ type: CheeseEm
- type: entity
+ parent: BaseAction
id: ActionRatKingOrderLoose
name: Loose
description: Command your army to act at their own will.
components:
- - type: InstantAction
+ - type: Action
useDelay: 1
icon:
sprite: Interface/Actions/actions_rat_king.rsi
@@ -405,7 +413,8 @@
iconOn:
sprite: Interface/Actions/actions_rat_king.rsi
state: loose
+ priority: 8
+ - type: InstantAction
event:
!type:RatKingOrderActionEvent
type: Loose
- priority: 8
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index 2b18468ace..2304c0d386 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -103,73 +103,86 @@
- type: BypassInteractionChecks
- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseAGhostAction
+ components:
+ - type: Action
+ icon: { sprite: Structures/Machines/parts.rsi, state: box_0 }
+ iconOn: Structures/Machines/parts.rsi/box_2.png
+ keywords: [ "AI", "console", "interface" ]
+ priority: -10
+
+- type: entity
+ parent: BaseAGhostAction
id: ActionAGhostShowSolar
name: Solar Control Interface
description: View a Solar Control Interface.
components:
- type: InstantAction
- icon: { sprite: Structures/Machines/parts.rsi, state: box_0 }
- iconOn: Structures/Machines/parts.rsi/box_2.png
- keywords: [ "AI", "console", "interface" ]
- priority: -10
event: !type:ToggleIntrinsicUIEvent { key: enum.SolarControlConsoleUiKey.Key }
- type: entity
+ parent: BaseAGhostAction
id: ActionAGhostShowCommunications
name: Communications Interface
description: View a Communications Interface.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Interface/Actions/actions_ai.rsi, state: comms_console }
iconOn: Interface/Actions/actions_ai.rsi/comms_console.png
keywords: [ "AI", "console", "interface" ]
priority: -4
+ - type: InstantAction
event: !type:ToggleIntrinsicUIEvent { key: enum.CommunicationsConsoleUiKey.Key }
- type: entity
+ parent: BaseAGhostAction
id: ActionAGhostShowRadar
name: Mass Scanner Interface
description: View a Mass Scanner Interface.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Interface/Actions/actions_ai.rsi, state: mass_scanner }
iconOn: Interface/Actions/actions_ai.rsi/mass_scanner.png
keywords: [ "AI", "console", "interface" ]
priority: -6
+ - type: InstantAction
event: !type:ToggleIntrinsicUIEvent { key: enum.RadarConsoleUiKey.Key }
- type: entity
+ parent: BaseAGhostAction
id: ActionAGhostShowCargo
name: Cargo Ordering Interface
description: View a Cargo Ordering Interface.
components:
- type: InstantAction
- icon: { sprite: Structures/Machines/parts.rsi, state: box_0 }
- iconOn: Structures/Machines/parts.rsi/box_2.png
- keywords: [ "AI", "console", "interface" ]
- priority: -10
event: !type:ToggleIntrinsicUIEvent { key: enum.CargoConsoleUiKey.Orders }
- type: entity
+ parent: BaseAGhostAction
id: ActionAGhostShowCrewMonitoring
name: Crew Monitoring Interface
description: View a Crew Monitoring Interface.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Interface/Actions/actions_ai.rsi, state: crew_monitor }
iconOn: Interface/Actions/actions_ai.rsi/crew_monitor.png
keywords: [ "AI", "console", "interface" ]
priority: -8
+ - type: InstantAction
event: !type:ToggleIntrinsicUIEvent { key: enum.CrewMonitoringUIKey.Key }
- type: entity
+ parent: BaseAGhostAction
id: ActionAGhostShowStationRecords
name: Station Records Interface
description: View a Station Records Interface.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Interface/Actions/actions_ai.rsi, state: station_records }
iconOn: Interface/Actions/actions_ai.rsi/station_records.png
keywords: [ "AI", "console", "interface" ]
priority: -7
+ - type: InstantAction
event: !type:ToggleIntrinsicUIEvent { key: enum.GeneralStationRecordConsoleKey.Key }
diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml
index 0f5b2ee3be..f3332ef0c5 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml
@@ -230,39 +230,47 @@
Brute: 12
- type: entity
+ parent: BaseAction
id: ActionSpawnRift
name: Summon Carp Rift
description: Summons a carp rift that will periodically spawns carps.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Interface/Actions/carp_rift.rsi
state: icon
- event: !type:DragonSpawnRiftActionEvent
useDelay: 1
priority: 3
+ - type: InstantAction
+ event: !type:DragonSpawnRiftActionEvent
- type: entity
+ parent: BaseAction
id: ActionDevour
name: "[color=red]Devour[/color]"
description: Attempt to break a structure with your jaws or swallow a creature.
components:
- - type: EntityTargetAction
+ - type: Action
icon: { sprite : Interface/Actions/devour.rsi, state: icon }
iconOn: { sprite : Interface/Actions/devour.rsi, state: icon-on }
- event: !type:DevourActionEvent
priority: 1
+ - type: TargetAction
+ - type: EntityTargetAction
+ event: !type:DevourActionEvent
- type: entity
+ parent: BaseAction
id: ActionDragonsBreath
name: "[color=orange]Dragon's Breath[/color]"
description: Spew out flames at anyone foolish enough to attack you!
components:
- - type: WorldTargetAction
- # TODO: actual sprite
+ - type: Action
+ # TODO: actual sprite and iconOn
icon: { sprite : Objects/Weapons/Guns/Projectiles/magic.rsi, state: fireball }
- event: !type:ActionGunShootEvent
+ itemIconStyle: BigAction
priority: 2
+ - type: TargetAction
checkCanAccess: false
range: 0
- itemIconStyle: BigAction
+ - type: WorldTargetAction
+ event: !type:ActionGunShootEvent
diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml
index 44212f640e..0aee45e28e 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml
@@ -261,13 +261,13 @@
task: SimpleHumanoidHostileCompound
- type: entity
+ parent: BaseMentalAction
id: ActionToggleGuardian
name: Toggle Guardian
description: Either manifests the guardian or recalls it back into your body
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/manifest.png
- event: !type:GuardianToggleActionEvent
useDelay: 2
- checkCanInteract: false
- checkConsciousness: false
+ - type: InstantAction
+ event: !type:GuardianToggleActionEvent
diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml
index 9224d52725..cb0cfdb693 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml
@@ -80,59 +80,65 @@
- AllowGhostShownByEvent
- type: entity
+ parent: BaseMentalAction
id: ActionGhostBoo
name: Boo!
description: Scare your crew members because of boredom!
components:
- - type: InstantAction
+ - type: Action
icon: Interface/Actions/scream.png
checkCanInteract: false
- event: !type:BooActionEvent
startDelay: true
useDelay: 120
+ - type: InstantAction
+ event: !type:BooActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionToggleLighting
name: Toggle Lighting
description: Toggle light rendering to better observe dark areas.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/VerbIcons/light.svg.192dpi.png
clientExclusive: true
- checkCanInteract: false
+ - type: InstantAction
event: !type:ToggleLightingActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionToggleFov
name: Toggle FoV
description: Toggles field-of-view in order to see what players see.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/VerbIcons/vv.svg.192dpi.png
clientExclusive: true
- checkCanInteract: false
+ - type: InstantAction
event: !type:ToggleFoVActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionToggleGhosts
name: Toggle Ghosts
description: Toggle the visibility of other ghosts.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Mobs/Ghosts/ghost_human.rsi, state: icon }
clientExclusive: true
- checkCanInteract: false
+ - type: InstantAction
event: !type:ToggleGhostsActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionToggleGhostHearing
name: Toggle Ghost Hearing
description: Toggle between hearing all messages and hearing only radio & nearby messages.
components:
- - type: InstantAction
- checkCanInteract: false
+ - type: Action
icon:
sprite: Clothing/Ears/Headsets/base.rsi
state: icon
iconOn: Interface/Actions/ghostHearingToggled.png
+ - type: InstantAction
event: !type:ToggleGhostHearingActionEvent
diff --git a/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml b/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml
index b6819a18b9..925bb3e86c 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml
@@ -12,7 +12,7 @@
components:
- Anchorable
- Item
- tags:
+ tags:
- Bot # for funny bot moments
blacklist:
components:
@@ -39,23 +39,27 @@
# actions
- type: entity
+ parent: BaseAction
id: ActionDisguiseNoRot
name: Toggle Rotation
description: Use this to prevent your disguise from rotating, making it easier to hide in some scenarios.
components:
- - type: InstantAction
+ - type: Action
icon: Interface/VerbIcons/refresh.svg.192dpi.png
itemIconStyle: BigAction
+ - type: InstantAction
event: !type:DisguiseToggleNoRotEvent
- type: entity
+ parent: BaseAction
id: ActionDisguiseAnchor
name: Toggle Anchored
description: For many objects you will want to be anchored to not be completely obvious.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Objects/Tools/wrench.rsi
state: icon
itemIconStyle: BigAction
+ - type: InstantAction
event: !type:DisguiseToggleAnchoredEvent
diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml
index 1f5136935a..699d1eec2d 100644
--- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml
+++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml
@@ -160,51 +160,51 @@
node: potatoai
- type: entity
+ parent: BaseMentalAction
id: ActionPAIOpenShop
name: Software Catalog
description: Install new software to assist your owner.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon: Interface/Actions/shop.png
+ - type: InstantAction
event: !type:PAIShopActionEvent
- type: entity
+ parent: BaseMentalAction
id: ActionPAIMassScanner
name: Mass Scanner
description: View a mass scanner interface.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon: { sprite: Interface/Actions/actions_ai.rsi, state: mass_scanner }
itemIconStyle: NoItem
+ - type: InstantAction
event: !type:OpenUiActionEvent
- key: enum.RadarConsoleUiKey.Key
+ key: enum.RadarConsoleUiKey.Key
- type: entity
+ parent: BaseMentalAction
id: ActionPAIPlayMidi
name: Play MIDI
description: Open your portable MIDI interface to soothe your owner.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
+ - type: Action
icon: Interface/Actions/pai-midi.png
itemIconStyle: NoItem
+ - type: InstantAction
event: !type:OpenUiActionEvent
key: enum.InstrumentUiKey.Key
- type: entity
+ parent: BaseMentalAction
id: ActionPAIOpenMap
name: Open Map
description: Open your map interface and guide your owner.
components:
- - type: InstantAction
- checkCanInteract: false
- checkConsciousness: false
- icon: { sprite: Interface/Actions/pai-map.rsi, state: icon }
- itemIconStyle: NoItem
- event: !type:OpenUiActionEvent
- key: enum.StationMapUiKey.Key
+ - type: Action
+ icon: { sprite: Interface/Actions/pai-map.rsi, state: icon }
+ itemIconStyle: NoItem
+ - type: InstantAction
+ event: !type:OpenUiActionEvent
+ key: enum.StationMapUiKey.Key
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml
index 6539b4f8aa..8f4488ba51 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml
@@ -84,11 +84,13 @@
sprite: Objects/Specific/Chapel/necronomicon.rsi
- type: entity
+ parent: BaseAction
id: ActionBibleSummon
name: Summon familiar
description: Summon a familiar that will aid you and gain humanlike intelligence once inhabited by a soul.
components:
- - type: InstantAction
+ - type: Action
icon: { sprite: Clothing/Head/Hats/witch.rsi, state: icon }
- event: !type:SummonActionEvent
useDelay: 1
+ - type: InstantAction
+ event: !type:SummonActionEvent
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
index e73c44ff43..f80ba1a0e3 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
@@ -49,13 +49,15 @@
provided_container: !type:Container { }
- type: entity
+ parent: BaseAction
id: ActionBorgSwapModule
name: Swap Module
description: Select this module, enabling you to use the tools it provides.
components:
- - type: InstantAction
+ - type: Action
itemIconStyle: BigAction
useDelay: 0.5
+ - type: InstantAction
event: !type:BorgModuleActionSelectedEvent
- type: entity
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml
index 6fa30b7265..c634e86912 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml
@@ -51,13 +51,15 @@
Acidic: [Touch]
- type: entity
+ parent: BaseAction
id: ActionArtifactActivate
name: Activate Artifact
description: Activate yourself, causing chaos to those near you.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
state: ano29
useDelay: 300
+ - type: InstantAction
event: !type:ArtifactSelfActivateEvent
diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml
index e304d21b87..682ee4ff0b 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml
@@ -56,11 +56,12 @@
price: 100
- type: entity
+ parent: BaseAction
id: ActionToggleJetpack
name: Toggle jetpack
description: Toggles the jetpack, giving you movement outside the station.
components:
- - type: InstantAction
+ - type: Action
icon:
sprite: Objects/Tanks/Jetpacks/blue.rsi
state: icon
@@ -68,6 +69,7 @@
sprite: Objects/Tanks/Jetpacks/blue.rsi
state: icon-on
useDelay: 1.0
+ - type: InstantAction
event: !type:ToggleJetpackEvent
#Empty blue
diff --git a/Resources/Prototypes/Magic/animate_spell.yml b/Resources/Prototypes/Magic/animate_spell.yml
index 38c17e0517..9b4481d168 100644
--- a/Resources/Prototypes/Magic/animate_spell.yml
+++ b/Resources/Prototypes/Magic/animate_spell.yml
@@ -1,11 +1,17 @@
- type: entity
+ parent: BaseEntitySpellAction
id: ActionAnimateSpell
name: Animate
description: Bring an inanimate object to life!
components:
- - type: EntityTargetAction
+ - type: Action
useDelay: 0
- itemIconStyle: BigAction
+ sound: !type:SoundPathSpecifier
+ path: /Audio/Magic/staff_animation.ogg
+ icon:
+ sprite: Objects/Magic/magicactions.rsi
+ state: spell_default
+ - type: EntityTargetAction
whitelist:
components:
- Animateable # Currently on: SeatBase, TableBase, ClosetBase, BaseMachine, ConstructibleMachine, BaseComputer, BaseItem, CrateGeneric, StorageTank, GasCanister
@@ -18,13 +24,6 @@
- TegGenerator
- TegCirculator
- XenoArtifact
- canTargetSelf: false
- interactOnMiss: false
- sound: !type:SoundPathSpecifier
- path: /Audio/Magic/staff_animation.ogg
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: spell_default
event: !type:ChangeComponentsSpellEvent
toAdd:
- type: Animate
diff --git a/Resources/Prototypes/Magic/event_spells.yml b/Resources/Prototypes/Magic/event_spells.yml
index 91db28bca2..e32bbac3d5 100644
--- a/Resources/Prototypes/Magic/event_spells.yml
+++ b/Resources/Prototypes/Magic/event_spells.yml
@@ -1,30 +1,34 @@
- type: entity
+ parent: BaseAction
id: ActionSummonGhosts
name: Summon Ghosts
description: Makes all current ghosts permanently visible
components:
- - type: InstantAction
+ - type: Action
useDelay: 120
itemIconStyle: BigAction
icon:
sprite: Mobs/Ghosts/ghost_human.rsi
state: icon
+ - type: InstantAction
event: !type:ToggleGhostVisibilityToAllEvent
# TODO: Add Whitelist/Blacklist and Component support to EntitySpawnLists (to avoid making huge hardcoded lists like below).
- type: entity
+ parent: BaseAction
id: ActionSummonGuns
name: Summon Guns
description: AK47s for everyone! Places a random gun in front of everybody.
components:
- type: Magic
- - type: InstantAction
+ - type: Action
useDelay: 300
itemIconStyle: BigAction
icon:
sprite: Objects/Weapons/Guns/Rifles/ak.rsi
state: base
+ - type: InstantAction
event: !type:RandomGlobalSpawnSpellEvent
makeSurvivorAntagonist: true
spawns:
@@ -162,17 +166,19 @@
sentence: action-speech-spell-summon-guns
- type: entity
+ parent: BaseAction
id: ActionSummonMagic
name: Summon Magic
description: Places a random magical item in front of everybody. Nothing could go wrong!
components:
- type: Magic
- - type: InstantAction
+ - type: Action
useDelay: 300
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: magicmissile
+ - type: InstantAction
event: !type:RandomGlobalSpawnSpellEvent
makeSurvivorAntagonist: true
spawns:
diff --git a/Resources/Prototypes/Magic/forcewall_spells.yml b/Resources/Prototypes/Magic/forcewall_spells.yml
index ae71d81526..c0c5a2eec6 100644
--- a/Resources/Prototypes/Magic/forcewall_spells.yml
+++ b/Resources/Prototypes/Magic/forcewall_spells.yml
@@ -1,9 +1,10 @@
- type: entity
+ parent: BaseAction
id: ActionForceWall
name: forcewall
description: Creates a magical barrier.
components:
- - type: InstantAction
+ - type: Action
useDelay: 15
itemIconStyle: BigAction
sound: !type:SoundPathSpecifier
@@ -11,6 +12,7 @@
icon:
sprite: Objects/Magic/magicactions.rsi
state: shield
+ - type: InstantAction
event: !type:InstantSpawnSpellEvent
prototype: WallForce
posData: !type:TargetInFront
diff --git a/Resources/Prototypes/Magic/knock_spell.yml b/Resources/Prototypes/Magic/knock_spell.yml
index e01c5a57af..ca84465d2b 100644
--- a/Resources/Prototypes/Magic/knock_spell.yml
+++ b/Resources/Prototypes/Magic/knock_spell.yml
@@ -1,9 +1,10 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionKnock
name: Knock
description: This spell opens nearby doors.
components:
- - type: InstantAction
+ - type: Action
useDelay: 10
itemIconStyle: BigAction
sound: !type:SoundPathSpecifier
@@ -11,6 +12,7 @@
icon:
sprite: Objects/Magic/magicactions.rsi
state: knock
+ - type: InstantAction
event: !type:KnockSpellEvent
- type: SpeakOnAction
sentence: action-speech-spell-knock
diff --git a/Resources/Prototypes/Magic/mindswap_spell.yml b/Resources/Prototypes/Magic/mindswap_spell.yml
index ae0bfa87bc..8039f12074 100644
--- a/Resources/Prototypes/Magic/mindswap_spell.yml
+++ b/Resources/Prototypes/Magic/mindswap_spell.yml
@@ -1,22 +1,22 @@
- type: entity
+ parent: BaseEntitySpellAction
id: ActionMindSwap
name: Mind Swap
description: Exchange bodies with another person!
components:
- - type: EntityTargetAction
+ - type: Action
useDelay: 300
- itemIconStyle: BigAction
- whitelist:
- components:
- - Body
- - MindContainer
- canTargetSelf: false
- interactOnMiss: false
sound: !type:SoundPathSpecifier
path: /Audio/Magic/staff_animation.ogg
icon:
sprite: Mobs/Species/Human/organs.rsi
state: brain
+ - type: EntityTargetAction
+ whitelist:
+ components:
+ - Body # this also allows borgs because that supercode uses Body for no reason
+ - PAI # intended to mindswap pAIs and AIs
+ - StationAiCore
event: !type:MindSwapSpellEvent
- type: SpeakOnAction
sentence: action-speech-spell-mind-swap
diff --git a/Resources/Prototypes/Magic/projectile_spells.yml b/Resources/Prototypes/Magic/projectile_spells.yml
index 1568b3ab65..87ed8772f0 100644
--- a/Resources/Prototypes/Magic/projectile_spells.yml
+++ b/Resources/Prototypes/Magic/projectile_spells.yml
@@ -1,20 +1,23 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionFireball
name: Fireball
description: Fires an explosive fireball towards the clicked location.
components:
- type: Magic
- - type: WorldTargetAction
+ - type: Action
useDelay: 15
itemIconStyle: BigAction
- checkCanAccess: false
raiseOnUser: true
- range: 60
sound: !type:SoundPathSpecifier
path: /Audio/Magic/fireball.ogg
icon:
sprite: Objects/Magic/magicactions.rsi
state: fireball
+ - type: TargetAction
+ range: 60
+ checkCanAccess: false
+ - type: WorldTargetAction
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
- type: SpeakOnAction
@@ -25,43 +28,19 @@
3: ActionFireballIII
- type: entity
- id: ActionFireballII
parent: ActionFireball
+ id: ActionFireballII
name: Fireball II
description: Fires a fireball, but faster!
components:
- - type: WorldTargetAction
- itemIconStyle: BigAction
- checkCanAccess: false
- raiseOnUser: true
- range: 60
- sound: !type:SoundPathSpecifier
- path: /Audio/Magic/fireball.ogg
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: fireball
- event: !type:ProjectileSpellEvent
- prototype: ProjectileFireball
- - type: SpeakOnAction
- sentence: action-speech-spell-fireball
+ - type: Action
+ useDelay: 10
- type: entity
- id: ActionFireballIII
parent: ActionFireball
+ id: ActionFireballIII
name: Fireball III
description: The fastest fireball in the west!
components:
- - type: WorldTargetAction
- itemIconStyle: BigAction
- checkCanAccess: false
- raiseOnUser: true
- range: 60
- sound: !type:SoundPathSpecifier
- path: /Audio/Magic/fireball.ogg
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: fireball
- event: !type:ProjectileSpellEvent
- prototype: ProjectileFireball
- - type: SpeakOnAction
- sentence: action-speech-spell-fireball
+ - type: Action
+ useDelay: 8
diff --git a/Resources/Prototypes/Magic/recall_spell.yml b/Resources/Prototypes/Magic/recall_spell.yml
index c5bb96870d..72082c3834 100644
--- a/Resources/Prototypes/Magic/recall_spell.yml
+++ b/Resources/Prototypes/Magic/recall_spell.yml
@@ -1,9 +1,10 @@
- type: entity
+ parent: BaseAction
id: ActionItemRecall
name: Mark Item
description: Mark a held item to later summon into your hand.
components:
- - type: InstantAction
+ - type: Action
useDelay: 10
raiseOnAction: true
itemIconStyle: BigAction
@@ -17,5 +18,6 @@
icon:
sprite: Objects/Magic/magicactions.rsi
state: item_recall
+ - type: InstantAction
event: !type:OnItemRecallActionEvent
- type: ItemRecall
diff --git a/Resources/Prototypes/Magic/repulse_spell.yml b/Resources/Prototypes/Magic/repulse_spell.yml
index 77f919e6ff..96a90ce2ec 100644
--- a/Resources/Prototypes/Magic/repulse_spell.yml
+++ b/Resources/Prototypes/Magic/repulse_spell.yml
@@ -1,4 +1,5 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionRepulse
name: Repulse
description: Pushes entities away from the user.
@@ -10,11 +11,12 @@
components:
- MobMover
- Item
- - type: InstantAction
+ - type: Action
useDelay: 40
raiseOnAction: true
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: repulse
+ - type: InstantAction
event: !type:RepulseAttractActionEvent
diff --git a/Resources/Prototypes/Magic/rune_spells.yml b/Resources/Prototypes/Magic/rune_spells.yml
index 7ba357e7c1..551a5bee00 100644
--- a/Resources/Prototypes/Magic/rune_spells.yml
+++ b/Resources/Prototypes/Magic/rune_spells.yml
@@ -1,55 +1,58 @@
-- type: entity
+- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseRuneAction
+ components:
+ - type: Action
+ itemIconStyle: BigAction
+ icon:
+ sprite: Objects/Magic/magicactions.rsi
+ state: spell_default
+
+- type: entity
+ parent: BaseRuneAction
id: ActionFlashRune
name: Flash Rune
description: Summons a rune that flashes if used.
components:
- - type: InstantAction
+ - type: Action
useDelay: 10
- itemIconStyle: BigAction
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: spell_default
+ - type: InstantAction
event: !type:InstantSpawnSpellEvent
prototype: FlashRune
- type: entity
+ parent: BaseRuneAction
id: ActionExplosionRune
name: Explosion Rune
description: Summons a rune that explodes if used.
components:
- - type: InstantAction
+ - type: Action
useDelay: 20
- itemIconStyle: BigAction
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: spell_default
+ - type: InstantAction
event: !type:InstantSpawnSpellEvent
prototype: ExplosionRune
- type: entity
+ parent: BaseRuneAction
id: ActionIgniteRune
name: Ignite Rune
description: Summons a rune that ignites if used.
components:
- - type: InstantAction
+ - type: Action
useDelay: 15
- itemIconStyle: BigAction
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: spell_default
+ - type: InstantAction
event: !type:InstantSpawnSpellEvent
prototype: IgniteRune
- type: entity
+ parent: BaseRuneAction
id: ActionStunRune
name: Stun Rune
description: Summons a rune that stuns if used.
components:
- - type: InstantAction
+ - type: Action
useDelay: 10
- itemIconStyle: BigAction
- icon:
- sprite: Objects/Magic/magicactions.rsi
- state: spell_default
+ - type: InstantAction
event: !type:InstantSpawnSpellEvent
prototype: StunRune
diff --git a/Resources/Prototypes/Magic/smoke_spell.yml b/Resources/Prototypes/Magic/smoke_spell.yml
index 5c28606136..a63f6e1ebf 100644
--- a/Resources/Prototypes/Magic/smoke_spell.yml
+++ b/Resources/Prototypes/Magic/smoke_spell.yml
@@ -1,14 +1,16 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionSmoke
name: Smoke
description: Summons smoke around the user.
components:
- - type: InstantAction
+ - type: Action
useDelay: 10
itemIconStyle: BigAction
icon:
sprite: Actions/smokeaction.rsi
state: smokeaction
+ - type: InstantAction
event: !type:InstantSpawnSpellEvent
prototype: WizardSmoke
posData: !type:TargetInFront
diff --git a/Resources/Prototypes/Magic/spawn_spells.yml b/Resources/Prototypes/Magic/spawn_spells.yml
index fb6755212a..a151e6f028 100644
--- a/Resources/Prototypes/Magic/spawn_spells.yml
+++ b/Resources/Prototypes/Magic/spawn_spells.yml
@@ -1,15 +1,18 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionSpawnMagicarpSpell
name: Summon Magicarp
description: This spell summons three Magi-Carp to your aid! May or may not turn on user.
components:
- - type: WorldTargetAction
+ - type: Action
useDelay: 10
- range: 4
itemIconStyle: BigAction
icon:
sprite: Objects/Magic/magicactions.rsi
state: spell_default
+ - type: TargetAction
+ range: 4
+ - type: WorldTargetAction
event: !type:WorldSpawnSpellEvent
prototypes:
- id: MobCarpMagic
diff --git a/Resources/Prototypes/Magic/staves.yml b/Resources/Prototypes/Magic/staves.yml
index ff43db866e..97ca55e066 100644
--- a/Resources/Prototypes/Magic/staves.yml
+++ b/Resources/Prototypes/Magic/staves.yml
@@ -67,11 +67,11 @@
- WizardWand
- type: entity
+ parent: BaseAction
id: ActionRgbLight
components:
- type: EntityTargetAction
whitelist: { components: [ PointLight ] }
- sound: /Audio/Magic/blink.ogg
event: !type:ChangeComponentsSpellEvent
toAdd:
- type: RgbLightController
diff --git a/Resources/Prototypes/Magic/teleport_spells.yml b/Resources/Prototypes/Magic/teleport_spells.yml
index 6e359cc611..ab79a2f5d0 100644
--- a/Resources/Prototypes/Magic/teleport_spells.yml
+++ b/Resources/Prototypes/Magic/teleport_spells.yml
@@ -1,43 +1,43 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionBlink
name: Blink
description: Teleport to the clicked location.
components:
- - type: WorldTargetAction
+ - type: Action
useDelay: 10
- range: 16 # default examine-range.
- # ^ should probably add better validation that the clicked location is on the users screen somewhere,
+ itemIconStyle: BigAction
sound: !type:SoundPathSpecifier
path: /Audio/Magic/blink.ogg
- itemIconStyle: BigAction
- checkCanAccess: false
- repeat: false
icon:
sprite: Objects/Magic/magicactions.rsi
state: blink
+ - type: TargetAction
+ range: 16 # default examine-range.
+ # ^ should probably add better validation that the clicked location is on the users screen somewhere,
+ repeat: false
+ checkCanAccess: false
+ - type: WorldTargetAction
event: !type:TeleportSpellEvent
# TODO: Second level upgrade sometime that allows swapping with all objects
- type: entity
+ parent: BaseEntitySpellAction
id: ActionVoidApplause
name: Void Applause
description: Clap your hands and swap places with the target.
components:
- - type: EntityTargetAction
+ - type: Action
useDelay: 15
- range: 16
sound: !type:SoundPathSpecifier
path: /Audio/Magic/Eldritch/voidblink.ogg
- itemIconStyle: BigAction
- whitelist:
- components:
- - Body
- canTargetSelf: false
- interactOnMiss: false
- checkCanAccess: false
- repeat: false
icon:
sprite: Objects/Magic/Eldritch/eldritch_actions.rsi
state: voidblink
+ - type: TargetAction
+ checkCanAccess: false
+ repeat: false
+ range: 16
+ - type: EntityTargetAction
event: !type:VoidApplauseSpellEvent
effect: EffectVoidBlink
diff --git a/Resources/Prototypes/Magic/touch_spells.yml b/Resources/Prototypes/Magic/touch_spells.yml
index 3eba350dd0..f690288b0e 100644
--- a/Resources/Prototypes/Magic/touch_spells.yml
+++ b/Resources/Prototypes/Magic/touch_spells.yml
@@ -1,21 +1,40 @@
-- type: entity
- id: ActionSmite
- name: Smite
- description: Instantly gibs a target.
+# Smite-like spell that you click on someone else to use
+- type: entity
+ abstract: true
+ parent: BaseAction
+ id: BaseEntitySpellAction
components:
- - type: EntityTargetAction
- useDelay: 90
+ - type: Action
itemIconStyle: BigAction
+ - type: TargetAction
+ interactOnMiss: false
+ - type: EntityTargetAction
whitelist:
components:
- Body
canTargetSelf: false
- interactOnMiss: false
+
+- type: entity
+ abstract: true
+ parent: BaseEntitySpellAction
+ id: BaseSmiteAction
+ components:
+ - type: Magic
+ requiresClothes: true
+
+- type: entity
+ parent: BaseSmiteAction
+ id: ActionSmite
+ name: Smite
+ description: Instantly gibs a target.
+ components:
+ - type: Action
sound: !type:SoundPathSpecifier
path: /Audio/Magic/disintegrate.ogg
icon:
sprite: Objects/Magic/magicactions.rsi
state: gib
+ - type: EntityTargetAction
event: !type:SmiteSpellEvent
- type: SpeakOnAction
sentence: action-speech-spell-smite
@@ -24,29 +43,26 @@
# For the Snail
- type: entity
- id: ActionSmiteNoReq
parent: ActionSmite
+ id: ActionSmiteNoReq
components:
- type: Magic
+ requiresClothes: false
- type: entity
+ parent: BaseSmiteAction
id: ActionCluwne
name: Cluwne's Curse
description: Turns someone into a Cluwne!
components:
- - type: EntityTargetAction
+ - type: Action
useDelay: 120
- itemIconStyle: BigAction
- whitelist:
- components:
- - Body
- canTargetSelf: false
- interactOnMiss: false
sound: !type:SoundPathSpecifier
path: /Audio/Items/brokenbikehorn.ogg
icon:
sprite: Clothing/Mask/cluwne.rsi
state: icon
+ - type: EntityTargetAction
event: !type:ChangeComponentsSpellEvent
toAdd:
- type: Cluwne
@@ -56,23 +72,19 @@
requiresClothes: true
- type: entity
+ parent: BaseSmiteAction
id: ActionSlippery
name: Slippery Slope
description: Make someone slippery.
components:
- - type: EntityTargetAction
+ - type: Action
useDelay: 60
- itemIconStyle: BigAction
- whitelist:
- components:
- - Body
- canTargetSelf: false
- interactOnMiss: false
sound: !type:SoundPathSpecifier
path: /Audio/Effects/slip.ogg
icon:
sprite: Objects/Specific/Janitorial/soap.rsi
state: omega-4
+ - type: EntityTargetAction
event: !type:ChangeComponentsSpellEvent
toAdd:
- type: Slippery
diff --git a/Resources/Prototypes/Magic/utility_spells.yml b/Resources/Prototypes/Magic/utility_spells.yml
index c0074f496f..e95e4a6a28 100644
--- a/Resources/Prototypes/Magic/utility_spells.yml
+++ b/Resources/Prototypes/Magic/utility_spells.yml
@@ -1,14 +1,16 @@
-- type: entity
+- type: entity
+ parent: BaseAction
id: ActionChargeSpell
name: Charge
description: Adds a charge back to your wand
components:
- - type: InstantAction
+ - type: Action
useDelay: 30
itemIconStyle: BigAction
icon:
sprite: Objects/Weapons/Guns/Basic/wands.rsi
state: nothing
+ - type: InstantAction
event: !type:ChargeSpellEvent
charge: 1
- type: SpeakOnAction
diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml
index 243dafabf2..3fb89a719c 100644
--- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml
+++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml
@@ -32,14 +32,16 @@
- RubberStampMime
- type: entity
+ parent: BaseAction
id: ActionMimeInvisibleWall
name: Create Invisible Wall
description: Create an invisible wall in front of you, if placeable there.
components:
- - type: InstantAction
+ - type: Action
priority: -1
useDelay: 30
icon:
sprite: Structures/Walls/solid.rsi
state: full
+ - type: InstantAction
event: !type:InvisibleWallActionEvent
diff --git a/Resources/mapping_actions.yml b/Resources/mapping_actions.yml
index e7ab1b4bf3..8437d4915b 100644
--- a/Resources/mapping_actions.yml
+++ b/Resources/mapping_actions.yml
@@ -1,1136 +1,189 @@
-- action: !type:InstantActionComponent
- icon: /Textures/Tiles/cropped_parallax.png
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: AlignTileAny
- tileId: Space
+- tileId: Space
assignments:
- 0: 8
- name: space
-- action: !type:InstantActionComponent
- icon: Interface/VerbIcons/delete.svg.192dpi.png
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- eraser: True
+- action: ActionMappingEraser
assignments:
- 0: 9
- name: action-name-mapping-erase
-- action: !type:InstantActionComponent
- icon: /Textures/Tiles/plating.png
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: AlignTileAny
- tileId: Plating
+- tileId: Plating
assignments:
- 0: 7
- name: plating
-- action: !type:InstantActionComponent
- icon:
- entity: Grille
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Grille
+- tileId: FloorSteel
assignments:
- - 0: 4
- name: Grille
-- action: !type:InstantActionComponent
- icon:
- entity: WallSolid
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: WallSolid
- assignments:
- - 0: 0
- name: WallSolid
-- action: !type:InstantActionComponent
- icon:
- entity: Window
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Window
- assignments:
- - 0: 2
- name: Window
-- action: !type:InstantActionComponent
- icon:
- entity: WallReinforced
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: WallReinforced
- assignments:
- - 0: 1
- name: WallReinforced
-- action: !type:InstantActionComponent
- icon:
- entity: ReinforcedWindow
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: ReinforcedWindow
- assignments:
- - 0: 3
- name: ReinforcedWindow
-- action: !type:InstantActionComponent
- icon:
- entity: Firelock
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Firelock
+ - 0: 6
+- entity: Firelock
assignments:
- 0: 5
- name: Firelock
-- action: !type:InstantActionComponent
- icon:
- entity: GasPipeStraight
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: GasPipeStraight
+- entity: Grille
+ assignments:
+ - 0: 4
+- entity: ReinforcedWindow
+ assignments:
+ - 0: 3
+- entity: Window
+ assignments:
+ - 0: 2
+- entity: WallReinforced
+ assignments:
+ - 0: 1
+- entity: WallSolid
+ assignments:
+ - 0: 0
+- entity: GasPipeStraight
assignments:
- 1: 0
- name: GasPipeStraight
-- action: !type:InstantActionComponent
- icon:
- entity: GasPipeBend
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: GasPipeBend
+- entity: GasPipeBend
assignments:
- 1: 1
- name: GasPipeBend
-- action: !type:InstantActionComponent
- icon:
- entity: GasPipeTJunction
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: GasPipeTJunction
+- entity: GasPipeTJunction
assignments:
- 1: 2
- name: GasPipeTJunction
-- action: !type:InstantActionComponent
- icon:
- entity: GasPipeFourway
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: GasPipeFourway
+- entity: GasPipeFourway
assignments:
- 1: 3
- name: GasPipeFourway
-- action: !type:InstantActionComponent
- icon:
- entity: GasVentScrubber
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: GasVentScrubber
+- entity: GasVentScrubber
assignments:
- 1: 4
- name: GasVentScrubber
-- action: !type:InstantActionComponent
- icon:
- entity: GasVentPump
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: GasVentPump
+- entity: GasVentPump
assignments:
- 1: 5
- name: GasVentPump
-- action: !type:InstantActionComponent
- icon:
- entity: AirAlarm
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirAlarm
+- entity: AirAlarm
assignments:
- 1: 6
- name: AirAlarm
-- action: !type:InstantActionComponent
- icon:
- entity: FireAlarm
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: FireAlarm
+- entity: FireAlarm
assignments:
- 1: 7
- name: FireAlarm
-- action: !type:InstantActionComponent
- icon:
- entity: APCBasic
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: APCBasic
+- entity: APCBasic
assignments:
- 2: 3
- name: APCBasic
-- action: !type:InstantActionComponent
- icon:
- entity: CableApcExtension
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: CableApcExtension
+- entity: CableApcExtension
assignments:
- 2: 0
- name: CableApcExtension
-- action: !type:InstantActionComponent
- icon:
- entity: CableMV
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: CableMV
+- entity: CableMV
assignments:
- 2: 1
- name: CableMV
-- action: !type:InstantActionComponent
- icon:
- entity: CableHV
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: CableHV
+- entity: CableHV
assignments:
- 2: 2
- name: CableHV
-- action: !type:InstantActionComponent
- icon:
- entity: SubstationBasic
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: SubstationBasic
+- entity: SubstationBasic
assignments:
- 2: 4
- name: SubstationBasic
-- action: !type:InstantActionComponent
- icon:
- entity: Poweredlight
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Poweredlight
+- entity: Poweredlight
assignments:
- 2: 6
- name: Poweredlight
-- action: !type:InstantActionComponent
- icon:
- entity: EmergencyLight
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: PlaceFree
- entityType: EmergencyLight
+- entity: EmergencyLight
assignments:
- 2: 7
- name: EmergencyLight
-- action: !type:InstantActionComponent
- icon:
- entity: SMESBasic
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: SMESBasic
+- entity: SMESBasic
assignments:
- 2: 5
- name: SMESBasic
-- action: !type:InstantActionComponent
- icon:
- entity: TableWood
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: TableWood
+- entity: TableWood
assignments:
- 3: 0
- name: TableWood
-- action: !type:InstantActionComponent
- icon:
- entity: Table
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Table
+- entity: Table
assignments:
- 3: 1
- name: Table
-- action: !type:InstantActionComponent
- icon:
- entity: ChairWood
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: ChairWood
+- entity: ChairWood
assignments:
- 3: 2
- name: ChairWood
-- action: !type:InstantActionComponent
- icon:
- entity: Chair
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Chair
+- entity: Chair
assignments:
- 3: 3
- name: Chair
-- action: !type:InstantActionComponent
- icon:
- entity: ChairOfficeLight
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: ChairOfficeLight
+- entity: ChairOfficeLight
assignments:
- 3: 4
- name: ChairOfficeLight
-- action: !type:InstantActionComponent
- icon:
- entity: StoolBar
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: StoolBar
+- entity: StoolBar
assignments:
- 3: 6
- name: StoolBar
-- action: !type:InstantActionComponent
- icon:
- entity: Stool
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Stool
+- entity: Stool
assignments:
- 3: 7
- name: Stool
-- action: !type:InstantActionComponent
- icon:
- entity: Rack
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Rack
+- entity: Rack
assignments:
- 3: 8
- name: Rack
-- action: !type:InstantActionComponent
- icon:
- entity: ChairOfficeDark
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: ChairOfficeDark
+- entity: ChairOfficeDark
assignments:
- 3: 5
- name: ChairOfficeDark
-- action: !type:InstantActionComponent
- icon:
- entity: LampGold
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: PlaceFree
- entityType: LampGold
+- entity: LampGold
assignments:
- 3: 9
- name: LampGold
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalPipe
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalPipe
+- entity: DisposalPipe
assignments:
- 4: 0
- name: DisposalPipe
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalBend
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalBend
+- entity: DisposalBend
assignments:
- 4: 1
- name: DisposalBend
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalJunction
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalJunction
+- entity: DisposalJunction
assignments:
- 4: 2
- name: DisposalJunction
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalJunctionFlipped
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalJunctionFlipped
+- entity: DisposalJunctionFlipped
assignments:
- 4: 3
- name: DisposalJunctionFlipped
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalRouter
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalRouter
+- entity: DisposalRouter
assignments:
- 4: 4
- name: DisposalRouter
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalRouterFlipped
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalRouterFlipped
+- entity: DisposalRouterFlipped
assignments:
- 4: 5
- name: DisposalRouterFlipped
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalUnit
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalUnit
+- entity: DisposalUnit
assignments:
- 4: 6
- name: DisposalUnit
-- action: !type:InstantActionComponent
- icon:
- entity: DisposalTrunk
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: DisposalTrunk
+- entity: DisposalTrunk
assignments:
- 4: 7
- name: DisposalTrunk
-- action: !type:InstantActionComponent
- icon:
- entity: SignDisposalSpace
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: SignDisposalSpace
+- entity: SignDisposalSpace
assignments:
- 4: 8
- name: SignDisposalSpace
-- action: !type:InstantActionComponent
- icon:
- entity: Windoor
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Windoor
+- entity: Windoor
assignments:
- 5: 0
- name: Windoor
-- action: !type:InstantActionComponent
- icon:
- entity: WindowDirectional
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: WindowDirectional
+- entity: WindowDirectional
assignments:
- 5: 1
name: WindowDirectional
-- action: !type:InstantActionComponent
- icon:
- entity: WindowReinforcedDirectional
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: WindowReinforcedDirectional
+- entity: WindowReinforcedDirectional
assignments:
- 5: 2
- name: WindowReinforcedDirectional
-- action: !type:InstantActionComponent
- icon:
- entity: PlasmaWindowDirectional
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: PlasmaWindowDirectional
+- entity: PlasmaWindowDirectional
assignments:
- 5: 3
- name: PlasmaWindowDirectional
-- action: !type:InstantActionComponent
- icon:
- entity: Railing
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: Railing
+- entity: Railing
assignments:
- 5: 6
- name: Railing
-- action: !type:InstantActionComponent
- icon:
- entity: RailingCorner
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: RailingCorner
+- entity: RailingCorner
assignments:
- 5: 7
name: RailingCorner
-- action: !type:InstantActionComponent
- icon:
- entity: RailingCornerSmall
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: RailingCornerSmall
+- entity: RailingCornerSmall
assignments:
- 5: 8
- name: RailingCornerSmall
-- action: !type:InstantActionComponent
- icon:
- entity: RailingRound
- name: RailingRound
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: RailingRound
+- entity: RailingRound
assignments:
- 5: 9
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockMaintLocked
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockMaintLocked
+- entity: AirlockMaintLocked
assignments:
- 6: 0
- name: AirlockMaintLocked
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockGlass
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockGlass
+- entity: AirlockGlass
assignments:
- 6: 1
- name: AirlockGlass
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockServiceLocked
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockServiceLocked
+- entity: AirlockServiceLocked
assignments:
- 6: 2
- name: AirlockServiceLocked
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockSecurityLocked
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockSecurityLocked
+- entity: AirlockSecurityLocked
assignments:
- 6: 3
- name: AirlockSecurityLocked
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockCommand
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockCommand
+- entity: AirlockCommand
assignments:
- 6: 4
- name: AirlockCommand
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockScience
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockScience
+- entity: AirlockScience
assignments:
- 6: 5
- name: AirlockScience
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockMedical
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockMedical
+- entity: AirlockMedical
assignments:
- 6: 6
- name: AirlockMedical
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockEngineering
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockEngineering
+- entity: AirlockEngineering
assignments:
- 6: 7
- name: AirlockEngineering
-- action: !type:InstantActionComponent
- icon:
- entity: AirlockCargo
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: SnapgridCenter
- entityType: AirlockCargo
+- entity: AirlockCargo
assignments:
- 6: 8
- name: AirlockCargo
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/markings.rsi
- state: bot_left
- iconColor: '#EFB34196'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#EFB34196'
- decalId: BotLeft
- assignments:
- - 7: 0
- name: BotLeft
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/markings.rsi
- state: delivery
- iconColor: '#EFB34196'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#EFB34196'
- decalId: Delivery
- assignments:
- - 7: 1
- name: Delivery
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/markings.rsi
- state: warn_full
- iconColor: '#EFB34196'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#EFB34196'
- decalId: WarnFull
- assignments:
- - 7: 2
- name: WarnFull
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#EFB34196'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#EFB34196'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 3
- name: HalfTileOverlayGreyscale
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#334E6DC8'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#334E6DC8'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 4
- name: HalfTileOverlayGreyscale (#334E6DC8, 0)
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#52B4E996'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#52B4E996'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 5
- name: HalfTileOverlayGreyscale (#52B4E996, 0)
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#9FED5896'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#9FED5896'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 6
- name: HalfTileOverlayGreyscale (#9FED5896, 0)
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#DE3A3A96'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#DE3A3A96'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 7
- name: HalfTileOverlayGreyscale (#DE3A3A96, 0)
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#D381C996'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#D381C996'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 8
- name: HalfTileOverlayGreyscale (#D381C996, 0)
-- action: !type:WorldTargetActionComponent
- keywords: []
- repeat: True
- checkCanAccess: False
- range: -1
- icon:
- sprite: Decals/Overlays/greyscale.rsi
- state: halftile_overlay
- iconColor: '#A4610696'
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:PlaceDecalActionEvent
- snap: True
- color: '#A4610696'
- decalId: HalfTileOverlayGreyscale
- assignments:
- - 7: 9
- name: HalfTileOverlayGreyscale (#A4610696, 0)
-- action: !type:InstantActionComponent
- icon: /Textures/Tiles/steel.png
- keywords: []
- checkCanInteract: False
- checkConsciousness: False
- clientExclusive: True
- autoPopulate: False
- temporary: True
- event: !type:StartPlacementActionEvent
- placementOption: AlignTileAny
- tileId: FloorSteel
- assignments:
- - 0: 6
- name: steel floor
...