action refactor proper ecs edition (#27422)
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
using Content.Shared.Actions.Components;
|
||||
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
||||
|
||||
namespace Content.Client.Actions;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,3 +10,17 @@ public sealed class FillActionSlotEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid? Action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client-side event used to attempt to trigger a targeted action.
|
||||
/// This only gets raised if the has <see cref="TargetActionComponent">.
|
||||
/// Handlers must set <c>Handled</c> to true, then if the action has been performed,
|
||||
/// i.e. a target is found, then FoundTarget must be set to true.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ActionTargetAttemptEvent(
|
||||
PointerInputCmdArgs Input,
|
||||
Entity<ActionsComponent> User,
|
||||
ActionComponent Action,
|
||||
bool Handled = false,
|
||||
bool FoundTarget = false);
|
||||
|
||||
@@ -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<List<SlotAssignment>>? AssignSlot;
|
||||
|
||||
private readonly List<EntityUid> _removed = new();
|
||||
private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
|
||||
private readonly List<Entity<ActionComponent>> _added = new();
|
||||
|
||||
public static readonly EntProtoId MappingEntityAction = "BaseMappingEntityAction";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActionsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<ActionsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
|
||||
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
|
||||
SubscribeLocalEvent<ActionComponent, AfterAutoHandleStateEvent>(OnActionAutoHandleState);
|
||||
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ActionTargetAttemptEvent>(OnEntityTargetAttempt);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ActionTargetAttemptEvent>(OnWorldTargetAttempt);
|
||||
}
|
||||
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InstantActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState<InstantActionComponent>(uid, component, state);
|
||||
private void OnActionAutoHandleState(Entity<ActionComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateAction(ent);
|
||||
}
|
||||
|
||||
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
|
||||
public override void UpdateAction(Entity<ActionComponent> ent)
|
||||
{
|
||||
if (args.Current is not EntityTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.Blacklist = state.Blacklist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not WorldTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState<WorldTargetActionComponent>(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<EntityWorldTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void BaseHandleState<T>(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<T>(state.Container, uid);
|
||||
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
component.CheckConsciousness = state.CheckConsciousness;
|
||||
component.ClientExclusive = state.ClientExclusive;
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = EnsureEntity<T>(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<ActionsComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ActionsComponentState state)
|
||||
return;
|
||||
|
||||
var (uid, comp) = ent;
|
||||
_added.Clear();
|
||||
_removed.Clear();
|
||||
var stateEnts = EnsureEntitySet<ActionsComponent>(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<ActionComponent> a, Entity<ActionComponent> 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<ActionsComponent> performer, Entity<ActionComponent> 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<ActionsComponent> performer, Entity<ActionComponent> 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<Entity<ActionComponent>> GetClientActions()
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } user)
|
||||
return Enumerable.Empty<(EntityUid, BaseActionComponent)>();
|
||||
return Enumerable.Empty<Entity<ActionComponent>>();
|
||||
|
||||
return GetActions(user);
|
||||
}
|
||||
@@ -254,24 +195,23 @@ namespace Content.Client.Actions
|
||||
CommandBinds.Unregister<ActionsSystem>();
|
||||
}
|
||||
|
||||
public void TriggerAction(EntityUid actionId, BaseActionComponent action)
|
||||
public void TriggerAction(Entity<ActionComponent> 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<InstantActionComponent>(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<ActionsComponent>(user);
|
||||
|
||||
ClearAssignments?.Invoke();
|
||||
|
||||
var assignments = new List<SlotAssignment>();
|
||||
|
||||
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<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||
var actionId = Spawn();
|
||||
AddComp(actionId, action);
|
||||
AddActionDirect(user, actionId);
|
||||
|
||||
if (map.TryGet<ValueDataNode>("name", out var nameNode))
|
||||
_metaData.SetEntityName(actionId, nameNode.Value);
|
||||
|
||||
if (!map.TryGet("assignments", out var assignmentNode))
|
||||
continue;
|
||||
|
||||
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(assignmentNode, notNullableOverride: true);
|
||||
|
||||
foreach (var index in nodeAssignments)
|
||||
var actionId = EntityUid.Invalid;
|
||||
if (map.TryGet<ValueDataNode>("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<ValueDataNode>("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<ValueDataNode>("tileId", out var tileNode))
|
||||
{
|
||||
var id = new ProtoId<ContentTileDefinition>(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<WorldTargetActionComponent> 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<EntityTargetActionComponent>(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<EntityTargetActionComponent> 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<WorldTargetActionComponent>(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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ActionComponent>(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})");
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <summary>
|
||||
/// The icon to use for space tiles.
|
||||
/// </summary>
|
||||
private readonly SpriteSpecifier _spaceIcon = new Texture(new ("Tiles/cropped_parallax.png"));
|
||||
|
||||
/// <summary>
|
||||
/// The icon to use for entity-eraser.
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
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<Component>(actionId, action);
|
||||
_metaData.SetEntityName(actionId, name);
|
||||
|
||||
ev.Action = actionId;
|
||||
}
|
||||
|
||||
private void OnStartPlacementAction(StartPlacementActionEvent args)
|
||||
|
||||
@@ -141,11 +141,8 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
else if (listing.ProductAction != null)
|
||||
{
|
||||
var actionId = _entityManager.Spawn(listing.ProductAction);
|
||||
if (_entityManager.System<ActionsSystem>().TryGetActionData(actionId, out var action) &&
|
||||
action.Icon != null)
|
||||
{
|
||||
texture = spriteSys.Frame0(action.Icon);
|
||||
}
|
||||
if (_entityManager.System<ActionsSystem>().GetAction(actionId)?.Comp?.Icon is {} icon)
|
||||
texture = spriteSys.Frame0(icon);
|
||||
}
|
||||
|
||||
var listingInStock = GetListingPriceString(listing);
|
||||
|
||||
@@ -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<GameplayS
|
||||
if (_playerManager.LocalEntity is not { } user)
|
||||
return false;
|
||||
|
||||
if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
|
||||
if (!EntityManager.TryGetComponent<ActionsComponent>(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<TargetActionComponent>(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<GameplayS
|
||||
|
||||
public void UnloadButton()
|
||||
{
|
||||
if (ActionButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ActionButton.OnPressed -= ActionButtonPressed;
|
||||
if (ActionButton != null)
|
||||
ActionButton.OnPressed -= ActionButtonPressed;
|
||||
}
|
||||
|
||||
public void LoadButton()
|
||||
{
|
||||
if (ActionButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ActionButton.OnPressed += ActionButtonPressed;
|
||||
if (ActionButton != null)
|
||||
ActionButton.OnPressed += ActionButtonPressed;
|
||||
}
|
||||
|
||||
private void OnWindowOpened()
|
||||
{
|
||||
if (ActionButton != null)
|
||||
ActionButton.SetClickPressed(true);
|
||||
ActionButton?.SetClickPressed(true);
|
||||
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnWindowClosed()
|
||||
{
|
||||
if (ActionButton != null)
|
||||
ActionButton.SetClickPressed(false);
|
||||
ActionButton?.SetClickPressed(false);
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
@@ -351,35 +233,33 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void TriggerAction(int index)
|
||||
{
|
||||
if (_actionsSystem == null ||
|
||||
!_actions.TryGetValue(index, out var actionId) ||
|
||||
!_actionsSystem.TryGetActionData(actionId, out var baseAction))
|
||||
if (!_actions.TryGetValue(index, out var actionId) ||
|
||||
_actionsSystem?.GetAction(actionId) is not {} action)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (baseAction is BaseTargetActionComponent action)
|
||||
ToggleTargeting(actionId.Value, action);
|
||||
// TODO: probably should have a clientside event raised for flexibility
|
||||
if (EntityManager.TryGetComponent<TargetActionComponent>(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<TargetActionComponent>(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<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchesFilter(BaseActionComponent action, Filters filter)
|
||||
private bool MatchesFilter(Entity<ActionComponent> 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<InstantActionComponent>(uid),
|
||||
Filters.Targeted => EntityManager.HasComponent<TargetActionComponent>(uid),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
|
||||
};
|
||||
}
|
||||
@@ -456,7 +337,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
_window.ResultsGrid.RemoveAllChildren();
|
||||
}
|
||||
|
||||
private void PopulateActions(IEnumerable<(EntityUid Id, BaseActionComponent Comp)> actions)
|
||||
private void PopulateActions(IEnumerable<Entity<ActionComponent>> actions)
|
||||
{
|
||||
if (_window is not { Disposed: false, IsOpen: true })
|
||||
return;
|
||||
@@ -478,7 +359,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
if (i < existing.Count)
|
||||
{
|
||||
existing[i++].UpdateData(action.Id, _actionsSystem);
|
||||
existing[i++].UpdateData(action, _actionsSystem);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -486,7 +367,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
button.ActionPressed += OnWindowActionPressed;
|
||||
button.ActionUnpressed += OnWindowActionUnPressed;
|
||||
button.ActionFocusExited += OnWindowActionFocusExisted;
|
||||
button.UpdateData(action.Id, _actionsSystem);
|
||||
button.UpdateData(action, _actionsSystem);
|
||||
_window.ResultsGrid.AddChild(button);
|
||||
}
|
||||
|
||||
@@ -525,13 +406,13 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
actions = actions.Where(action =>
|
||||
{
|
||||
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<MetaDataComponent>(action.Id).EntityName;
|
||||
var name = EntityManager.GetComponent<MetaDataComponent>(action).EntityName;
|
||||
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
@@ -581,7 +462,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void DragAction()
|
||||
{
|
||||
if (_menuDragHelper.Dragged is not {ActionId: {} action} dragged)
|
||||
if (_menuDragHelper.Dragged is not {Action: {} action} dragged)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
return;
|
||||
@@ -591,7 +472,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
var currentlyHovered = UIManager.MouseGetControl(_input.MouseScreenPosition);
|
||||
if (currentlyHovered is ActionButton button)
|
||||
{
|
||||
swapAction = button.ActionId;
|
||||
swapAction = button.Action;
|
||||
SetAction(button, action, false);
|
||||
}
|
||||
|
||||
@@ -665,16 +546,13 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
private void HandleActionPressed(GUIBoundKeyEventArgs args, ActionButton button)
|
||||
{
|
||||
args.Handle();
|
||||
if (button.ActionId != null)
|
||||
if (button.Action != null)
|
||||
{
|
||||
_menuDragHelper.MouseDown(button);
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new FillActionSlotEvent();
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
|
||||
if (ev.Action != null)
|
||||
SetAction(button, ev.Action);
|
||||
// good job
|
||||
}
|
||||
|
||||
private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button)
|
||||
@@ -700,12 +578,13 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (!_actionsSystem.TryGetActionData(button.ActionId, out var baseAction))
|
||||
if (button.Action is not {} action)
|
||||
return;
|
||||
|
||||
if (baseAction is not BaseTargetActionComponent action)
|
||||
// TODO: make this an event
|
||||
if (!EntityManager.TryGetComponent<TargetActionComponent>(action, out var target))
|
||||
{
|
||||
_actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
|
||||
_actionsSystem?.TriggerAction(action);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -714,7 +593,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||
// targeting
|
||||
ToggleTargeting(button.ActionId.Value, action);
|
||||
ToggleTargeting((action, action.Comp, target));
|
||||
}
|
||||
|
||||
private bool OnMenuBeginDrag()
|
||||
@@ -722,16 +601,16 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
// TODO ACTIONS
|
||||
// The dragging icon shuld be based on the entity's icon style. I.e. if the action has a large icon texture,
|
||||
// and a small item/provider sprite, then the dragged icon should be the big texture, not the provider.
|
||||
if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action))
|
||||
if (_menuDragHelper.Dragged?.Action is {} action)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(action.EntityIcon, out SpriteComponent? sprite)
|
||||
if (EntityManager.TryGetComponent(action.Comp.EntityIcon, out SpriteComponent? sprite)
|
||||
&& sprite.Icon?.GetFrame(RsiDirection.South, 0) is {} frame)
|
||||
{
|
||||
_dragShadow.Texture = frame;
|
||||
}
|
||||
else if (action.Icon != null)
|
||||
else if (action.Comp.Icon is {} icon)
|
||||
{
|
||||
_dragShadow.Texture = _spriteSystem.Frame0(action.Icon);
|
||||
_dragShadow.Texture = _spriteSystem.Frame0(icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -898,33 +777,35 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
/// If currently targeting with no slot or a different slot, switches to
|
||||
/// targeting with the specified slot.
|
||||
/// </summary>
|
||||
private void ToggleTargeting(EntityUid actionId, BaseTargetActionComponent action)
|
||||
private void ToggleTargeting(Entity<ActionComponent, TargetActionComponent> ent)
|
||||
{
|
||||
if (SelectingTargetFor == actionId)
|
||||
if (SelectingTargetFor == ent)
|
||||
{
|
||||
StopTargeting();
|
||||
return;
|
||||
}
|
||||
|
||||
StartTargeting(actionId, action);
|
||||
StartTargeting(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts us in targeting mode, where we need to pick either a target point or entity
|
||||
/// </summary>
|
||||
private void StartTargeting(EntityUid actionId, BaseTargetActionComponent action)
|
||||
private void StartTargeting(Entity<ActionComponent, TargetActionComponent> 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<ShowHandItemOverlay>(out var handOverlay))
|
||||
if (target.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
|
||||
{
|
||||
if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Container != null)
|
||||
{
|
||||
@@ -940,7 +821,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.ActionId == actionId)
|
||||
if (button.Action?.Owner == uid)
|
||||
button.UpdateIcons();
|
||||
}
|
||||
}
|
||||
@@ -950,19 +831,19 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
// - Add a yes/no checkmark where the HandItemOverlay usually is
|
||||
|
||||
// Highlight valid entity targets
|
||||
if (action is not EntityTargetActionComponent entityAction)
|
||||
if (!EntityManager.TryGetComponent<EntityTargetActionComponent>(uid, out var entity))
|
||||
return;
|
||||
|
||||
Func<EntityUid, bool>? 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -974,11 +855,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
return;
|
||||
|
||||
var oldAction = SelectingTargetFor;
|
||||
if (_actionsSystem != null && _actionsSystem.TryGetActionData(oldAction, out var action))
|
||||
{
|
||||
// TODO inform the server
|
||||
action.Toggled = false;
|
||||
}
|
||||
// TODO inform the server
|
||||
_actionsSystem?.SetToggled(oldAction, false);
|
||||
|
||||
SelectingTargetFor = null;
|
||||
|
||||
@@ -989,7 +867,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.ActionId == oldAction)
|
||||
if (button.Action?.Owner == oldAction)
|
||||
button.UpdateIcons();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Client.Actions.UI;
|
||||
using Content.Client.Cooldown;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -54,8 +55,7 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
|
||||
private Texture? _buttonBackgroundTexture;
|
||||
|
||||
public EntityUid? ActionId { get; private set; }
|
||||
private BaseActionComponent? _action;
|
||||
public Entity<ActionComponent>? Action { get; private set; }
|
||||
public bool Locked { get; set; }
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ActionButton>? 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<SpriteComponent>(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<ActionUIController>();
|
||||
_spriteSys ??= _entities.System<SpriteSystem>();
|
||||
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<ActionUIController>();
|
||||
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<ActionUIController>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<InstantActionComponent>();
|
||||
var cQuery = cEntMan.GetEntityQuery<InstantActionComponent>();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<EntityTargetActionComponent>(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<EntityWorldTargetActionComponent>(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<WorldTargetActionComponent>(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<EntityTargetActionComponent>(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<T>(List<EntityUid>? actions, bool canReach = true) where T : BaseActionComponent
|
||||
private List<Entity<ActionComponent, T>> GetValidActions<T>(List<EntityUid>? actions, bool canReach = true) where T: Component
|
||||
{
|
||||
var valid = new List<(EntityUid Id, T Comp)>();
|
||||
var valid = new List<Entity<ActionComponent, T>>();
|
||||
|
||||
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<T>(id, out var comp) ||
|
||||
!_actions.ValidAction(action, canReach))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
valid.Add((id, action));
|
||||
valid.Add((id, action, comp));
|
||||
}
|
||||
|
||||
return valid;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId<EntityWorldTargetActionComponent> ActionId;
|
||||
public EntProtoId<TargetActionComponent> ActionId;
|
||||
|
||||
[DataField]
|
||||
public EntityUid? ActionEnt;
|
||||
|
||||
@@ -28,24 +28,16 @@ public sealed class NPCUseActionOnTargetSystem : EntitySystem
|
||||
if (!Resolve(user, ref user.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!TryComp<EntityWorldTargetActionComponent>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PolymorphPrototype> id, Entity<PolymorphableComponent> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +61,10 @@ public sealed partial class BorgSystem
|
||||
|
||||
if (_actions.AddAction(chassis, ref component.ModuleSwapActionEntity, out var action, component.ModuleSwapActionId, uid))
|
||||
{
|
||||
if(TryComp<BorgModuleIconComponent>(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<BorgModuleIconComponent>(uid, out var moduleIconComp))
|
||||
_actions.SetIcon(actEnt, moduleIconComp.Icon);
|
||||
}
|
||||
|
||||
if (!TryComp(chassis, out BorgChassisComponent? chassisComp))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the entity that is spawned.
|
||||
|
||||
@@ -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<EntityCoordinates> spawnPos = new();
|
||||
spawnPos.Add(coords);
|
||||
|
||||
|
||||
@@ -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<ActionComponent> _query;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_query = GetEntityQuery<ActionComponent>();
|
||||
|
||||
SubscribeLocalEvent<ActionsContainerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
|
||||
@@ -77,7 +82,7 @@ public sealed class ActionContainerSystem : EntitySystem
|
||||
/// <inheritdoc cref="EnsureAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Content.Shared.Actions.ActionsContainerComponent?)"/>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -227,25 +241,25 @@ public sealed class ActionContainerSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it.
|
||||
/// </summary>
|
||||
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<ActionsContainerComponent>(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
|
||||
/// <summary>
|
||||
/// Removes an action from its container and any action-performer and moves the action to null-space
|
||||
/// </summary>
|
||||
public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null)
|
||||
public void RemoveAction(Entity<ActionComponent?>? 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;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
public EntityCoordinates Target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the type of event that gets raised when an <see cref="EntityWorldTargetActionComponent"/> is performed.
|
||||
/// The <see cref="BaseActionEvent.Performer"/>, <see cref="Entity"/>, and <see cref="Coords"/>
|
||||
/// fields will automatically be filled out by the <see cref="SharedActionsSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To define a new action for some system, you need to create an event that inherits from this class.
|
||||
/// </remarks>
|
||||
public abstract partial class EntityWorldTargetActionEvent : BaseActionEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity that the user targeted.
|
||||
/// When combined with <see cref="EntityTargetAction"/> (and <c>Event</c> 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.
|
||||
/// </summary>
|
||||
public EntityUid? Entity;
|
||||
|
||||
/// <summary>
|
||||
/// The coordinates of the location that the user targeted.
|
||||
/// </summary>
|
||||
public EntityCoordinates? Coords;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -187,7 +173,7 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs
|
||||
/// <summary>
|
||||
/// The action the event belongs to.
|
||||
/// </summary>
|
||||
public Entity<BaseActionComponent> Action;
|
||||
public Entity<ActionComponent> Action;
|
||||
|
||||
/// <summary>
|
||||
/// Should we toggle the action entity?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))]
|
||||
/// <summary>
|
||||
/// Component all actions are required to have.
|
||||
/// </summary>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon representing this action in the UI.
|
||||
/// </summary>
|
||||
[DataField("icon")] public SpriteSpecifier? Icon;
|
||||
[DataField, AutoNetworkedField]
|
||||
public SpriteSpecifier? Icon;
|
||||
|
||||
/// <summary>
|
||||
/// For toggle actions only, icon to show when toggled on. If omitted, the action will simply be highlighted
|
||||
/// when turned on.
|
||||
/// </summary>
|
||||
[DataField("iconOn")] public SpriteSpecifier? IconOn;
|
||||
[DataField, AutoNetworkedField]
|
||||
public SpriteSpecifier? IconOn;
|
||||
|
||||
/// <summary>
|
||||
/// For toggle actions only, background to show when toggled on.
|
||||
/// </summary>
|
||||
[DataField] public SpriteSpecifier? BackgroundOn;
|
||||
[DataField]
|
||||
public SpriteSpecifier? BackgroundOn;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
[DataField("iconColor")] public Color IconColor = Color.White;
|
||||
[DataField, AutoNetworkedField]
|
||||
public Color IconColor = Color.White;
|
||||
|
||||
/// <summary>
|
||||
/// The original <see cref="IconColor"/> this action was.
|
||||
/// </summary>
|
||||
[DataField] public Color OriginalIconColor;
|
||||
[DataField, AutoNetworkedField]
|
||||
public Color OriginalIconColor;
|
||||
|
||||
/// <summary>
|
||||
/// The color the action should turn to when disabled
|
||||
@@ -53,12 +59,14 @@ public abstract partial class BaseActionComponent : Component
|
||||
/// <summary>
|
||||
/// Keywords that can be used to search for this action in the action menu.
|
||||
/// </summary>
|
||||
[DataField("keywords")] public HashSet<string> Keywords = new();
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> Keywords = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action is currently enabled. If not enabled, this action cannot be performed.
|
||||
/// </summary>
|
||||
[DataField("enabled")] public bool Enabled = true;
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// The toggle state of this action. Toggling switches the currently displayed icon, see <see cref="Icon"/> and <see cref="IconOn"/>.
|
||||
@@ -67,14 +75,14 @@ public abstract partial class BaseActionComponent : Component
|
||||
/// The toggle can set directly via <see cref="SharedActionsSystem.SetToggled"/>, but it will also be
|
||||
/// automatically toggled for targeted-actions while selecting a target.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Toggled;
|
||||
|
||||
/// <summary>
|
||||
/// The current cooldown on the action.
|
||||
/// </summary>
|
||||
// TODO serialization
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown;
|
||||
[DataField, AutoNetworkedField]
|
||||
public ActionCooldown? Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the action will have an initial cooldown applied upon addition.
|
||||
@@ -84,14 +92,15 @@ public abstract partial class BaseActionComponent : Component
|
||||
/// <summary>
|
||||
/// Time interval between action uses.
|
||||
/// </summary>
|
||||
[DataField("useDelay")] public TimeSpan? UseDelay;
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? UseDelay;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that contains this action. If the action is innate, this may be the user themselves.
|
||||
/// This should almost always be non-null.
|
||||
/// </summary>
|
||||
[Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Container;
|
||||
|
||||
/// <summary>
|
||||
@@ -113,40 +122,45 @@ public abstract partial class BaseActionComponent : Component
|
||||
set => EntIcon = value;
|
||||
}
|
||||
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? EntIcon;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("checkCanInteract")] public bool CheckCanInteract = true;
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool CheckCanInteract = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to check if the user is conscious or not. Can be used instead of <see cref="CheckCanInteract"/>
|
||||
/// for a more permissive check.
|
||||
/// </summary>
|
||||
[DataField] public bool CheckConsciousness = true;
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool CheckConsciousness = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this will cause the action to only execute locally without ever notifying the server.
|
||||
/// </summary>
|
||||
[DataField("clientExclusive")] public bool ClientExclusive = false;
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ClientExclusive;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the order in which actions are automatically added the action bar.
|
||||
/// </summary>
|
||||
[DataField("priority")] public int Priority = 0;
|
||||
[DataField, AutoNetworkedField]
|
||||
public int Priority = 0;
|
||||
|
||||
/// <summary>
|
||||
/// What entity, if any, currently has this action in the actions component?
|
||||
/// </summary>
|
||||
[DataField] public EntityUid? AttachedEntity;
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? AttachedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool RaiseOnUser;
|
||||
|
||||
/// <summary>
|
||||
@@ -160,75 +174,34 @@ public abstract partial class BaseActionComponent : Component
|
||||
/// <summary>
|
||||
/// Whether or not to automatically add this action to the action bar when it becomes available.
|
||||
/// </summary>
|
||||
[DataField("autoPopulate")] public bool AutoPopulate = true;
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool AutoPopulate = true;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary actions are deleted when they get removed a <see cref="ActionsComponent"/>.
|
||||
/// </summary>
|
||||
[DataField("temporary")] public bool Temporary;
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Temporary;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the appearance of the entity-icon for actions that are enabled via some entity.
|
||||
/// </summary>
|
||||
[DataField("itemIconStyle")] public ItemActionIconStyle ItemIconStyle;
|
||||
[DataField, AutoNetworkedField]
|
||||
public ItemActionIconStyle ItemIconStyle;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this sound will be played when performing this action.
|
||||
/// </summary>
|
||||
[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<string> 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This component indicates that this entity contains actions inside of some container.
|
||||
/// </summary>
|
||||
[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";
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// For actions that can use basic upgrades
|
||||
/// Not all actions should be upgradable
|
||||
/// Requires <see cref="ActionComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(ActionUpgradeSystem))]
|
||||
[EntityCategory("Actions")]
|
||||
public sealed partial class ActionUpgradeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Current Level of the action.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public int Level = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("effectedLevels"), ViewVariables]
|
||||
[DataField]
|
||||
public Dictionary<int, EntProtoId> EffectedLevels = new();
|
||||
|
||||
// TODO: Branching level upgrades
|
||||
@@ -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))]
|
||||
/// <summary>
|
||||
/// Lets the player controlling this entity use actions.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
|
||||
public sealed partial class ActionsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of actions currently granted to this entity.
|
||||
/// On the client, this may contain a mixture of client-side and networked entities.
|
||||
/// </summary>
|
||||
[DataField] public HashSet<EntityUid> Actions = new();
|
||||
[DataField]
|
||||
public HashSet<EntityUid> Actions = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="ActionComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(ConfirmableActionSystem))]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[EntityCategory("Actions")]
|
||||
public sealed partial class ConfirmableActionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Used on action entities to define an action that triggers when targeting an entity.
|
||||
/// If used with <see cref="WorldTargetActionComponent"/>, the event here can be set to null and <c>Optional</c> should be set.
|
||||
/// Then <see cref="WorldActionEvent"> can have <c>TargetEntity</c> 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires <see cref="TargetActionComponent"/>.
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
|
||||
[EntityCategory("Actions")]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class EntityTargetActionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField, NonSerialized]
|
||||
public EntityTargetActionEvent? Event;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which entities are valid targets for this action.
|
||||
/// </summary>
|
||||
/// <remarks>No whitelist check when null.</remarks>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which entities cannot be valid targets for this action, even if matching the whitelist.
|
||||
/// </summary>
|
||||
/// <remarks>No blacklist check when null.</remarks>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action considers the user as a valid target entity when using this action.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool CanTargetSelf = true;
|
||||
}
|
||||
20
Content.Shared/Actions/Components/InstantActionComponent.cs
Normal file
20
Content.Shared/Actions/Components/InstantActionComponent.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Actions.Components;
|
||||
|
||||
/// <summary>
|
||||
/// An action that raises an event as soon as it gets used.
|
||||
/// Requires <see cref="ActionComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
|
||||
[EntityCategory("Actions")]
|
||||
public sealed partial class InstantActionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField(required: true), NonSerialized]
|
||||
public InstantActionEvent? Event;
|
||||
}
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// An action that targets an entity or map.
|
||||
/// Requires <see cref="ActionComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
|
||||
[EntityCategory("Actions")]
|
||||
public sealed partial class TargetActionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("repeat")] public bool Repeat;
|
||||
[DataField]
|
||||
public bool Repeat;
|
||||
|
||||
/// <summary>
|
||||
/// For entity- or map-targeting action, determines whether the action is deselected if the user doesn't click a valid target.
|
||||
/// </summary>
|
||||
[DataField("deselectOnMiss")] public bool DeselectOnMiss;
|
||||
[DataField]
|
||||
public bool DeselectOnMiss;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <remarks>
|
||||
/// Even if this is false, the <see cref="Range"/> will still be checked.
|
||||
/// </remarks>
|
||||
[DataField("checkCanAccess")] public bool CheckCanAccess = true;
|
||||
[DataField]
|
||||
public bool CheckCanAccess = true;
|
||||
|
||||
[DataField("range")] public float Range = SharedInteractionSystem.InteractionRange;
|
||||
[DataField]
|
||||
public float Range = SharedInteractionSystem.InteractionRange;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <remarks>
|
||||
/// Interactions will still be blocked if the target-validation generates a pop-up
|
||||
/// </remarks>
|
||||
[DataField("interactOnMiss")] public bool InteractOnMiss = false;
|
||||
[DataField]
|
||||
public bool InteractOnMiss;
|
||||
|
||||
/// <summary>
|
||||
/// If true, and if <see cref="ShowHandItemOverlay"/> is enabled, then this action's icon will be drawn by that
|
||||
/// over lay in place of the currently held item "held item".
|
||||
/// </summary>
|
||||
[DataField("targetingIndicator")] public bool TargetingIndicator = true;
|
||||
[DataField]
|
||||
public bool TargetingIndicator = true;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Actions.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used on action entities to define an action that triggers when targeting an entity coordinate.
|
||||
/// Can be combined with <see cref="EntityTargetActionComponent"/>, see its docs for more information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires <see cref="TargetActionComponent"/>.
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedActionsSystem))]
|
||||
[EntityCategory("Actions")]
|
||||
public sealed partial class WorldTargetActionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField(required: true), NonSerialized]
|
||||
public WorldTargetActionEvent? Event;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Used on action entities to define an action that triggers when targeting an entity.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class EntityTargetActionComponent : BaseTargetActionComponent
|
||||
{
|
||||
public override BaseActionEvent? BaseEvent => Event;
|
||||
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public EntityTargetActionEvent? Event;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which entities are valid targets for this action.
|
||||
/// </summary>
|
||||
/// <remarks>No whitelist check when null.</remarks>
|
||||
[DataField("whitelist")] public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which entities are NOT valid targets for this action.
|
||||
/// </summary>
|
||||
/// <remarks>No blacklist check when null.</remarks>
|
||||
[DataField] public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action considers the user as a valid target entity when using this action.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Used on action entities to define an action that triggers when targeting an entity or entity coordinates.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class EntityWorldTargetActionComponent : BaseTargetActionComponent
|
||||
{
|
||||
public override BaseActionEvent? BaseEvent => Event;
|
||||
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[NonSerialized]
|
||||
public EntityWorldTargetActionEvent? Event;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which entities are valid targets for this action.
|
||||
/// </summary>
|
||||
/// <remarks>No whitelist check when null.</remarks>
|
||||
[DataField] public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action considers the user as a valid target entity when using this action.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace Content.Shared.Actions.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ActionAttemptEvent(EntityUid User, bool Cancelled = false);
|
||||
|
||||
7
Content.Shared/Actions/Events/ActionGetEventEvent.cs
Normal file
7
Content.Shared/Actions/Events/ActionGetEventEvent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an action entity to get its event.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ActionGetEventEvent(BaseActionEvent? Event = null);
|
||||
8
Content.Shared/Actions/Events/ActionSetEventEvent.cs
Normal file
8
Content.Shared/Actions/Events/ActionSetEventEvent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an action entity to have the event-holding component cast and set its event.
|
||||
/// If it was set successfully then <c>Handled</c> must be set to true.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ActionSetEventEvent(BaseActionEvent Event, bool Handled = false);
|
||||
8
Content.Shared/Actions/Events/ActionSetTargetEvent.cs
Normal file
8
Content.Shared/Actions/Events/ActionSetTargetEvent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ActionSetTargetEvent(EntityUid Target, bool Handled = false);
|
||||
32
Content.Shared/Actions/Events/ActionValidateEvent.cs
Normal file
32
Content.Shared/Actions/Events/ActionValidateEvent.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct ActionValidateEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Request event the client sent.
|
||||
/// </summary>
|
||||
public RequestPerformActionEvent Input;
|
||||
|
||||
/// <summary>
|
||||
/// User trying to use the action.
|
||||
/// </summary>
|
||||
public EntityUid User;
|
||||
|
||||
/// <summary>
|
||||
/// Entity providing this action to the user, used for logging.
|
||||
/// </summary>
|
||||
public EntityUid Provider;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool Invalid;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetActionDataEvent(BaseActionComponent? Action);
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct ValidateActionEntityTargetEvent(EntityUid User, EntityUid Target, bool Cancelled = false);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public InstantActionEvent? Event;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InstantActionComponentState : BaseActionComponentState
|
||||
{
|
||||
public InstantActionComponentState(InstantActionComponent component, IEntityManager entManager) : base(component, entManager)
|
||||
{
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Used on action entities to define an action that triggers when targeting an entity coordinate.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class WorldTargetActionComponent : BaseTargetActionComponent
|
||||
{
|
||||
public override BaseActionEvent? BaseEvent => Event;
|
||||
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public WorldTargetActionEvent? Event;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class WorldTargetActionComponentState : BaseActionComponentState
|
||||
{
|
||||
public WorldTargetActionComponentState(WorldTargetActionComponent component, IEntityManager entManager) : base(component, entManager)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public abstract class SharedBedSystem : EntitySystem
|
||||
|
||||
private void OnUnstrapped(Entity<HealOnBuckleComponent> 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<HealOnBuckleHealingComponent>(bed);
|
||||
}
|
||||
|
||||
@@ -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<SleepingComponent> ent)
|
||||
{
|
||||
RemComp<SleepingComponent>(ent);
|
||||
_actionsSystem.RemoveAction(ent, ent.Comp.WakeAction);
|
||||
_actionsSystem.RemoveAction(ent.Owner, ent.Comp.WakeAction);
|
||||
|
||||
var ev = new SleepStateChangedEvent(false);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
@@ -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 <see cref="ToggleActionEvent"/> to then get handled.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId<InstantActionComponent> Action = string.Empty;
|
||||
public EntProtoId<InstantActionComponent> Action;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ActionEntity;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class EyeClosingSystem : EntitySystem
|
||||
|
||||
private void OnShutdown(Entity<EyeClosingComponent> eyelids, ref ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(eyelids, eyelids.Comp.EyeToggleActionEntity);
|
||||
_actionsSystem.RemoveAction(eyelids.Owner, eyelids.Comp.EyeToggleActionEntity);
|
||||
|
||||
SetEyelids((eyelids.Owner, eyelids.Comp), false);
|
||||
}
|
||||
|
||||
@@ -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<InstantActionComponent>(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<EmbeddableProjectileComponent>(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<RecallMarkerComponent> ent, ref ComponentShutdown args)
|
||||
@@ -99,24 +98,22 @@ public abstract partial class SharedItemRecallSystem : EntitySystem
|
||||
|
||||
private void TryMarkItem(Entity<ItemRecallComponent> ent, EntityUid item)
|
||||
{
|
||||
if (!TryComp<InstantActionComponent>(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<RecallMarkerComponent>(item);
|
||||
ent.Comp.MarkedEntity = item;
|
||||
Dirty(ent);
|
||||
|
||||
marker.MarkedByAction = ent.Owner;
|
||||
|
||||
UpdateActionAppearance(ent);
|
||||
var marker = AddComp<RecallMarkerComponent>(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<RecallMarkerComponent>(item, out var marker))
|
||||
return;
|
||||
|
||||
if (!TryComp<InstantActionComponent>(marker.MarkedByAction, out var instantAction))
|
||||
if (_actions.GetAction(marker.MarkedByAction) is not {} action)
|
||||
return;
|
||||
|
||||
if (TryComp<ItemRecallComponent>(marker.MarkedByAction, out var action))
|
||||
if (TryComp<ItemRecallComponent>(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<RecallMarkerComponent>(item);
|
||||
}
|
||||
|
||||
private void UpdateActionAppearance(Entity<ItemRecallComponent> action)
|
||||
private void UpdateActionAppearance(Entity<ActionComponent, ItemRecallComponent> action)
|
||||
{
|
||||
if (!TryComp<InstantActionComponent>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ContentTileDefinition>? TileId;
|
||||
|
||||
[DataField("placementOption")]
|
||||
[DataField]
|
||||
public string? PlacementOption;
|
||||
|
||||
[DataField("eraser")]
|
||||
[DataField]
|
||||
public bool Eraser;
|
||||
}
|
||||
|
||||
@@ -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<FakeMindShieldComponent>(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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
|
||||
namespace Content.Shared.Mobs.Systems;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId<InstantActionComponent> Action = string.Empty;
|
||||
public EntProtoId<InstantActionComponent> Action;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ActionEntity;
|
||||
|
||||
@@ -30,7 +30,7 @@ public abstract class SharedPAISystem : EntitySystem
|
||||
|
||||
private void OnShutdown(Entity<PAIComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(ent, ent.Comp.ShopAction);
|
||||
_actions.RemoveAction(ent.Owner, ent.Comp.ShopAction);
|
||||
}
|
||||
}
|
||||
public sealed partial class PAIShopActionEvent : InstantActionEvent
|
||||
|
||||
@@ -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<ActionsComponent?>(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)
|
||||
|
||||
@@ -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<BorgSwitchableTypeComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(ent, ent.Comp.SelectTypeAction);
|
||||
_actionsSystem.RemoveAction(ent.Owner, ent.Comp.SelectTypeAction);
|
||||
}
|
||||
|
||||
private void OnSelectBorgTypeAction(Entity<BorgSwitchableTypeComponent> 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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
48
Resources/Prototypes/Actions/mapping.yml
Normal file
48
Resources/Prototypes/Actions/mapping.yml
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user