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 ...