Refactor actions to be entities with components (#19900)
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
|
||||
namespace Content.Client.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when a user clicks on an empty action slot. Enables other systems to fill this slow.
|
||||
/// This event is raised when a user clicks on an empty action slot. Enables other systems to fill this slot.
|
||||
/// </summary>
|
||||
public sealed class FillActionSlotEvent : EntityEventArgs
|
||||
{
|
||||
public ActionType? Action;
|
||||
public EntityUid? Action;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input.Binding;
|
||||
@@ -12,6 +12,7 @@ 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.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -20,14 +21,15 @@ namespace Content.Client.Actions
|
||||
[UsedImplicitly]
|
||||
public sealed class ActionsSystem : SharedActionsSystem
|
||||
{
|
||||
public delegate void OnActionReplaced(ActionType existing, ActionType action);
|
||||
public delegate void OnActionReplaced(EntityUid actionId);
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
public event Action<ActionType>? ActionAdded;
|
||||
public event Action<ActionType>? ActionRemoved;
|
||||
public event Action<EntityUid>? ActionAdded;
|
||||
public event Action<EntityUid>? ActionRemoved;
|
||||
public event OnActionReplaced? ActionReplaced;
|
||||
public event Action? ActionsUpdated;
|
||||
public event Action<ActionsComponent>? LinkActions;
|
||||
@@ -35,7 +37,11 @@ namespace Content.Client.Actions
|
||||
public event Action? ClearAssignments;
|
||||
public event Action<List<SlotAssignment>>? AssignSlot;
|
||||
|
||||
public ActionsComponent? PlayerActions { get; private set; }
|
||||
/// <summary>
|
||||
/// Queue of entities with <see cref="ActionsComponent"/> that needs to be updated after
|
||||
/// handling a state.
|
||||
/// </summary>
|
||||
private readonly Queue<EntityUid> _actionHoldersQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -45,110 +51,90 @@ namespace Content.Client.Actions
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
|
||||
}
|
||||
|
||||
public override void Dirty(ActionType action)
|
||||
public override void Dirty(EntityUid? actionId)
|
||||
{
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity != action.AttachedEntity)
|
||||
var action = GetActionData(actionId);
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity != action?.AttachedEntity)
|
||||
return;
|
||||
|
||||
base.Dirty(action);
|
||||
base.Dirty(actionId);
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ActionsComponentState state)
|
||||
if (args.Current is not ActionsComponentState)
|
||||
return;
|
||||
|
||||
state.SortedActions ??= new SortedSet<ActionType>(state.Actions);
|
||||
var serverActions = state.SortedActions;
|
||||
var removed = new List<ActionType>();
|
||||
|
||||
foreach (var act in component.Actions.ToList())
|
||||
{
|
||||
if (act.ClientExclusive)
|
||||
continue;
|
||||
|
||||
if (!serverActions.TryGetValue(act, out var serverAct))
|
||||
{
|
||||
component.Actions.Remove(act);
|
||||
if (act.AutoRemove)
|
||||
removed.Add(act);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
act.CopyFrom(serverAct);
|
||||
}
|
||||
|
||||
var added = new List<ActionType>();
|
||||
|
||||
// Anything that remains is a new action
|
||||
foreach (var newAct in serverActions)
|
||||
{
|
||||
if (component.Actions.Contains(newAct))
|
||||
continue;
|
||||
|
||||
// We create a new action, not just sorting a reference to the state's action.
|
||||
var action = (ActionType) newAct.Clone();
|
||||
component.Actions.Add(action);
|
||||
added.Add(action);
|
||||
}
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity != uid)
|
||||
return;
|
||||
|
||||
foreach (var action in removed)
|
||||
{
|
||||
ActionRemoved?.Invoke(action);
|
||||
}
|
||||
|
||||
foreach (var action in added)
|
||||
{
|
||||
ActionAdded?.Invoke(action);
|
||||
}
|
||||
|
||||
ActionsUpdated?.Invoke();
|
||||
_actionHoldersQueue.Enqueue(uid);
|
||||
}
|
||||
|
||||
protected override void AddActionInternal(ActionsComponent comp, ActionType action)
|
||||
protected override void AddActionInternal(EntityUid actionId, IContainer container)
|
||||
{
|
||||
// Sometimes the client receives actions from the server, before predicting that newly added components will add
|
||||
// their own shared actions. Just in case those systems ever decided to directly access action properties (e.g.,
|
||||
// action.Toggled), we will remove duplicates:
|
||||
if (comp.Actions.TryGetValue(action, out var existing))
|
||||
if (container.Contains(actionId))
|
||||
{
|
||||
comp.Actions.Remove(existing);
|
||||
ActionReplaced?.Invoke(existing, action);
|
||||
ActionReplaced?.Invoke(actionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
container.Insert(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddAction(EntityUid holderId, EntityUid actionId, EntityUid? provider, ActionsComponent? holder = null, BaseActionComponent? action = null, bool dirty = true, IContainer? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(holderId, ref holder, false))
|
||||
return;
|
||||
|
||||
action ??= GetActionData(actionId);
|
||||
if (action == null)
|
||||
{
|
||||
Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}");
|
||||
return;
|
||||
}
|
||||
|
||||
comp.Actions.Add(action);
|
||||
dirty &= !action.ClientExclusive;
|
||||
base.AddAction(holderId, actionId, provider, holder, action, dirty, actionContainer);
|
||||
|
||||
if (holderId == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
ActionAdded?.Invoke(actionId);
|
||||
}
|
||||
|
||||
public override void AddAction(EntityUid uid, ActionType action, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
public override void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
dirty &= !action.ClientExclusive;
|
||||
base.AddAction(uid, action, provider, comp, dirty);
|
||||
if (!Resolve(holderId, ref comp, false))
|
||||
return;
|
||||
|
||||
if (uid == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
ActionAdded?.Invoke(action);
|
||||
if (actionId == null)
|
||||
return;
|
||||
|
||||
action ??= GetActionData(actionId);
|
||||
|
||||
if (action is { ClientExclusive: false })
|
||||
return;
|
||||
|
||||
dirty &= !action?.ClientExclusive ?? true;
|
||||
base.RemoveAction(holderId, actionId, comp, action, dirty, actionContainer);
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity != holderId)
|
||||
return;
|
||||
|
||||
if (action == null || action.AutoRemove)
|
||||
ActionRemoved?.Invoke(actionId.Value);
|
||||
}
|
||||
|
||||
public override void RemoveAction(EntityUid uid, ActionType action, ActionsComponent? comp = null, bool dirty = true)
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
|
||||
{
|
||||
if (GameTiming.ApplyingState && !action.ClientExclusive)
|
||||
return;
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } user)
|
||||
return Enumerable.Empty<(EntityUid, BaseActionComponent)>();
|
||||
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return;
|
||||
|
||||
dirty &= !action.ClientExclusive;
|
||||
base.RemoveAction(uid, action, comp, dirty);
|
||||
|
||||
if (action.AutoRemove && uid == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
ActionRemoved?.Invoke(action);
|
||||
return GetActions(user);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, ActionsComponent component, PlayerAttachedEvent args)
|
||||
@@ -163,20 +149,18 @@ namespace Content.Client.Actions
|
||||
|
||||
public void UnlinkAllActions()
|
||||
{
|
||||
PlayerActions = null;
|
||||
UnlinkActions?.Invoke();
|
||||
}
|
||||
|
||||
public void LinkAllActions(ActionsComponent? actions = null)
|
||||
{
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (player == null || !Resolve(player.Value, ref actions, false))
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } user ||
|
||||
!Resolve(user, ref actions, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LinkActions?.Invoke(actions);
|
||||
PlayerActions = actions;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -185,29 +169,30 @@ namespace Content.Client.Actions
|
||||
CommandBinds.Unregister<ActionsSystem>();
|
||||
}
|
||||
|
||||
public void TriggerAction(ActionType? action)
|
||||
public void TriggerAction(EntityUid actionId, BaseActionComponent action)
|
||||
{
|
||||
if (PlayerActions == null || action == null || _playerManager.LocalPlayer?.ControlledEntity is not { Valid: true } user)
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } user ||
|
||||
!TryComp(user, out ActionsComponent? actions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.Provider != null && Deleted(action.Provider))
|
||||
return;
|
||||
|
||||
if (action is not InstantAction instantAction)
|
||||
{
|
||||
if (action is not InstantActionComponent instantAction)
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.ClientExclusive)
|
||||
{
|
||||
if (instantAction.Event != null)
|
||||
instantAction.Event.Performer = user;
|
||||
|
||||
PerformAction(user, PlayerActions, instantAction, instantAction.Event, GameTiming.CurTime);
|
||||
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new RequestPerformActionEvent(instantAction);
|
||||
var request = new RequestPerformActionEvent(actionId);
|
||||
EntityManager.RaisePredictiveEvent(request);
|
||||
}
|
||||
}
|
||||
@@ -239,7 +224,7 @@ namespace Content.Client.Actions
|
||||
/// </summary>
|
||||
public void LoadActionAssignments(string path, bool userData)
|
||||
{
|
||||
if (PlayerActions == null)
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } user)
|
||||
return;
|
||||
|
||||
var file = new ResPath(path).ToRootedPath();
|
||||
@@ -265,17 +250,13 @@ namespace Content.Client.Actions
|
||||
if (!map.TryGet("action", out var actionNode))
|
||||
continue;
|
||||
|
||||
var action = _serialization.Read<ActionType>(actionNode, notNullableOverride: true);
|
||||
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||
var actionId = Spawn(null);
|
||||
AddComp<Component>(actionId, action);
|
||||
AddAction(user, actionId, null);
|
||||
|
||||
if (PlayerActions.Actions.TryGetValue(action, out var existingAction))
|
||||
{
|
||||
existingAction.CopyFrom(action);
|
||||
action = existingAction;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerActions.Actions.Add(action);
|
||||
}
|
||||
if (map.TryGet<ValueDataNode>("name", out var nameNode))
|
||||
_metaData.SetEntityName(actionId, nameNode.Value);
|
||||
|
||||
if (!map.TryGet("assignments", out var assignmentNode))
|
||||
continue;
|
||||
@@ -284,7 +265,7 @@ namespace Content.Client.Actions
|
||||
|
||||
foreach (var index in nodeAssignments)
|
||||
{
|
||||
var assignment = new SlotAssignment(index.Hotbar, index.Slot, action);
|
||||
var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
|
||||
assignments.Add(assignment);
|
||||
}
|
||||
}
|
||||
@@ -292,6 +273,73 @@ namespace Content.Client.Actions
|
||||
AssignSlot?.Invoke(assignments);
|
||||
}
|
||||
|
||||
public record struct SlotAssignment(byte Hotbar, byte Slot, ActionType Action);
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (_actionHoldersQueue.Count == 0)
|
||||
return;
|
||||
|
||||
var removed = new List<EntityUid>();
|
||||
var added = new List<EntityUid>();
|
||||
var query = GetEntityQuery<ActionsComponent>();
|
||||
var queue = new Queue<EntityUid>(_actionHoldersQueue);
|
||||
_actionHoldersQueue.Clear();
|
||||
|
||||
while (queue.TryDequeue(out var holderId))
|
||||
{
|
||||
if (!TryGetContainer(holderId, out var container) || container.ExpectedEntities.Count > 0)
|
||||
{
|
||||
_actionHoldersQueue.Enqueue(holderId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!query.TryGetComponent(holderId, out var holder))
|
||||
continue;
|
||||
|
||||
removed.Clear();
|
||||
added.Clear();
|
||||
|
||||
foreach (var (act, data) in holder.OldClientActions.ToList())
|
||||
{
|
||||
if (data.ClientExclusive)
|
||||
continue;
|
||||
|
||||
if (!container.Contains(act))
|
||||
{
|
||||
holder.OldClientActions.Remove(act);
|
||||
if (data.AutoRemove)
|
||||
removed.Add(act);
|
||||
}
|
||||
}
|
||||
|
||||
// Anything that remains is a new action
|
||||
foreach (var newAct in container.ContainedEntities)
|
||||
{
|
||||
if (!holder.OldClientActions.ContainsKey(newAct))
|
||||
added.Add(newAct);
|
||||
|
||||
if (TryGetActionData(newAct, out var serverData))
|
||||
holder.OldClientActions[newAct] = new ActionMetaData(serverData.ClientExclusive, serverData.AutoRemove);
|
||||
}
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity != holderId)
|
||||
return;
|
||||
|
||||
foreach (var action in removed)
|
||||
{
|
||||
ActionRemoved?.Invoke(action);
|
||||
}
|
||||
|
||||
foreach (var action in added)
|
||||
{
|
||||
ActionAdded?.Invoke(action);
|
||||
}
|
||||
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Numerics;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Decals.Overlays;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Decals;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -10,7 +9,6 @@ using Robust.Client.Input;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Client.Decals;
|
||||
|
||||
@@ -22,6 +20,7 @@ public sealed class DecalPlacementSystem : EntitySystem
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly InputSystem _inputSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
@@ -153,9 +152,10 @@ public sealed class DecalPlacementSystem : EntitySystem
|
||||
Cleanable = _cleanable,
|
||||
};
|
||||
|
||||
ev.Action = new WorldTargetAction()
|
||||
var actionId = Spawn(null);
|
||||
AddComp(actionId, new WorldTargetActionComponent
|
||||
{
|
||||
DisplayName = $"{_decalId} ({_decalColor.ToHex()}, {(int) _decalAngle.Degrees})", // non-unique actions may be considered duplicates when saving/loading.
|
||||
// non-unique actions may be considered duplicates when saving/loading.
|
||||
Icon = decalProto.Sprite,
|
||||
Repeat = true,
|
||||
ClientExclusive = true,
|
||||
@@ -164,7 +164,11 @@ public sealed class DecalPlacementSystem : EntitySystem
|
||||
Range = -1,
|
||||
Event = actionEvent,
|
||||
IconColor = _decalColor,
|
||||
};
|
||||
});
|
||||
|
||||
_metaData.SetEntityName(actionId, $"{_decalId} ({_decalColor.ToHex()}, {(int) _decalAngle.Degrees})");
|
||||
|
||||
ev.Action = actionId;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -194,24 +198,3 @@ public sealed class DecalPlacementSystem : EntitySystem
|
||||
_inputSystem.SetEntityContextActive();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class PlaceDecalActionEvent : WorldTargetActionEvent
|
||||
{
|
||||
[DataField("decalId", customTypeSerializer:typeof(PrototypeIdSerializer<DecalPrototype>), required:true)]
|
||||
public string DecalId = string.Empty;
|
||||
|
||||
[DataField("color")]
|
||||
public Color Color;
|
||||
|
||||
[DataField("rotation")]
|
||||
public double Rotation;
|
||||
|
||||
[DataField("snap")]
|
||||
public bool Snap;
|
||||
|
||||
[DataField("zIndex")]
|
||||
public int ZIndex;
|
||||
|
||||
[DataField("cleanable")]
|
||||
public bool Cleanable;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ using Content.Client.Movement.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Ghost
|
||||
{
|
||||
@@ -81,9 +79,9 @@ namespace Content.Client.Ghost
|
||||
sprite.Visible = GhostVisibility;
|
||||
}
|
||||
|
||||
_actions.AddAction(uid, component.ToggleLightingAction, null);
|
||||
_actions.AddAction(uid, component.ToggleFoVAction, null);
|
||||
_actions.AddAction(uid, component.ToggleGhostsAction, null);
|
||||
_actions.AddAction(uid, ref component.ToggleLightingActionEntity, component.ToggleGhostsAction);
|
||||
_actions.AddAction(uid, ref component.ToggleFoVActionEntity, component.ToggleFoVAction);
|
||||
_actions.AddAction(uid, ref component.ToggleGhostsActionEntity, component.ToggleGhostsAction);
|
||||
}
|
||||
|
||||
private void OnToggleLighting(EntityUid uid, GhostComponent component, ToggleLightingActionEvent args)
|
||||
@@ -118,9 +116,9 @@ namespace Content.Client.Ghost
|
||||
|
||||
private void OnGhostRemove(EntityUid uid, GhostComponent component, ComponentRemove args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.ToggleLightingAction);
|
||||
_actions.RemoveAction(uid, component.ToggleFoVAction);
|
||||
_actions.RemoveAction(uid, component.ToggleGhostsAction);
|
||||
_actions.RemoveAction(uid, component.ToggleLightingActionEntity);
|
||||
_actions.RemoveAction(uid, component.ToggleFoVActionEntity);
|
||||
_actions.RemoveAction(uid, component.ToggleGhostsActionEntity);
|
||||
|
||||
if (uid != _playerManager.LocalPlayer?.ControlledEntity)
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Mapping;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Shared.Utility.SpriteSpecifier;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
@@ -13,16 +14,17 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
[Dependency] private readonly IPlacementManager _placementMan = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The icon to use for space tiles.
|
||||
/// </summary>
|
||||
private readonly SpriteSpecifier _spaceIcon = new SpriteSpecifier.Texture(new ("Tiles/cropped_parallax.png"));
|
||||
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 SpriteSpecifier.Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
|
||||
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
|
||||
|
||||
public string DefaultMappingActions = "/mapping_actions.yml";
|
||||
|
||||
@@ -73,6 +75,9 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
else
|
||||
return;
|
||||
|
||||
InstantActionComponent action;
|
||||
string name;
|
||||
|
||||
if (tileDef != null)
|
||||
{
|
||||
if (tileDef is not ContentTileDefinition contentTileDef)
|
||||
@@ -80,45 +85,51 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
|
||||
var tileIcon = contentTileDef.IsSpace
|
||||
? _spaceIcon
|
||||
: new SpriteSpecifier.Texture(contentTileDef.Sprite!.Value);
|
||||
: new Texture(contentTileDef.Sprite!.Value);
|
||||
|
||||
ev.Action = new InstantAction()
|
||||
action = new InstantActionComponent
|
||||
{
|
||||
ClientExclusive = true,
|
||||
CheckCanInteract = false,
|
||||
Event = actionEvent,
|
||||
DisplayName = Loc.GetString(tileDef.Name),
|
||||
Icon = tileIcon
|
||||
};
|
||||
|
||||
return;
|
||||
name = Loc.GetString(tileDef.Name);
|
||||
}
|
||||
|
||||
if (actionEvent.Eraser)
|
||||
else if (actionEvent.Eraser)
|
||||
{
|
||||
ev.Action = new InstantAction()
|
||||
action = new InstantActionComponent
|
||||
{
|
||||
ClientExclusive = true,
|
||||
CheckCanInteract = false,
|
||||
Event = actionEvent,
|
||||
DisplayName = "action-name-mapping-erase",
|
||||
Icon = _deleteIcon,
|
||||
};
|
||||
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(actionEvent.EntityType))
|
||||
return;
|
||||
var actionId = Spawn(null);
|
||||
AddComp<Component>(actionId, action);
|
||||
_metaData.SetEntityName(actionId, name);
|
||||
|
||||
ev.Action = new InstantAction()
|
||||
{
|
||||
ClientExclusive = true,
|
||||
CheckCanInteract = false,
|
||||
Event = actionEvent,
|
||||
DisplayName = actionEvent.EntityType,
|
||||
Icon = new SpriteSpecifier.EntityPrototype(actionEvent.EntityType),
|
||||
};
|
||||
ev.Action = actionId;
|
||||
}
|
||||
|
||||
private void OnStartPlacementAction(StartPlacementActionEvent args)
|
||||
@@ -140,18 +151,3 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
_placementMan.ToggleEraser();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class StartPlacementActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField("entityType")]
|
||||
public string? EntityType;
|
||||
|
||||
[DataField("tileId")]
|
||||
public string? TileId;
|
||||
|
||||
[DataField("placementOption")]
|
||||
public string? PlacementOption;
|
||||
|
||||
[DataField("eraser")]
|
||||
public bool Eraser;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Client.Actions;
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
using Content.Shared.Input;
|
||||
@@ -22,11 +21,11 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
private const string Action = "ClearNetworkLinkOverlays";
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string Action = "ActionClearNetworkLinkOverlays";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -71,7 +70,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
||||
if (!EntityQuery<NetworkConfiguratorActiveLinkOverlayComponent>().Any())
|
||||
{
|
||||
_overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
|
||||
_actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, _prototypeManager.Index<InstantActionPrototype>(Action));
|
||||
_actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, Action);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +80,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
||||
if (!_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
|
||||
{
|
||||
_overlay.AddOverlay(new NetworkConfiguratorLinkOverlay());
|
||||
_actions.AddAction(_playerManager.LocalPlayer.ControlledEntity.Value, new InstantAction(_prototypeManager.Index<InstantActionPrototype>(Action)), null);
|
||||
_actions.AddAction(_playerManager.LocalPlayer.ControlledEntity.Value, Spawn(Action), null);
|
||||
}
|
||||
|
||||
EnsureComp<NetworkConfiguratorActiveLinkOverlayComponent>(component.ActiveDeviceList.Value);
|
||||
@@ -103,7 +102,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity != null)
|
||||
{
|
||||
_actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, _prototypeManager.Index<InstantActionPrototype>(Action));
|
||||
_actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, Action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Client.Graphics;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Client.Store.Ui;
|
||||
|
||||
@@ -128,9 +128,12 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
}
|
||||
else if (listing.ProductAction != null)
|
||||
{
|
||||
var action = _prototypeManager.Index<InstantActionPrototype>(listing.ProductAction);
|
||||
if (action.Icon != null)
|
||||
var actionId = _entityManager.Spawn(listing.ProductAction);
|
||||
if (_entityManager.System<ActionsSystem>().TryGetActionData(actionId, out var action) &&
|
||||
action.Icon != null)
|
||||
{
|
||||
texture = spriteSys.Frame0(action.Icon);
|
||||
}
|
||||
}
|
||||
|
||||
var newListing = new StoreListingControl(listingName, listingDesc, GetListingPriceString(listing), canBuy, texture);
|
||||
|
||||
@@ -13,7 +13,6 @@ 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.ActionTypes;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -65,7 +64,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
/// <summary>
|
||||
/// Action slot we are currently selecting a target for.
|
||||
/// </summary>
|
||||
public TargetedAction? SelectingTargetFor { get; private set; }
|
||||
public EntityUid? SelectingTargetFor { get; private set; }
|
||||
|
||||
public ActionUIController()
|
||||
{
|
||||
@@ -175,7 +174,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
/// </summary>
|
||||
private bool TargetingOnUse(in PointerInputCmdArgs args)
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } action)
|
||||
if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } actionId)
|
||||
return false;
|
||||
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } user)
|
||||
@@ -184,6 +183,12 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
|
||||
return false;
|
||||
|
||||
if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
|
||||
baseAction is not BaseTargetActionComponent action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the action currently valid?
|
||||
if (!action.Enabled
|
||||
|| action.Charges is 0
|
||||
@@ -196,19 +201,19 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case WorldTargetAction mapTarget:
|
||||
return TryTargetWorld(args, mapTarget, user, comp) || !action.InteractOnMiss;
|
||||
case WorldTargetActionComponent mapTarget:
|
||||
return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;
|
||||
|
||||
case EntityTargetAction entTarget:
|
||||
return TryTargetEntity(args, entTarget, user, comp) || !action.InteractOnMiss;
|
||||
case EntityTargetActionComponent entTarget:
|
||||
return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;
|
||||
|
||||
default:
|
||||
Logger.Error($"Unknown targeting action: {action.GetType()}");
|
||||
Logger.Error($"Unknown targeting action: {actionId.GetType()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryTargetWorld(in PointerInputCmdArgs args, WorldTargetAction action, EntityUid user, ActionsComponent actionComp)
|
||||
private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, WorldTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return false;
|
||||
@@ -232,10 +237,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
action.Event.Performer = user;
|
||||
}
|
||||
|
||||
_actionsSystem.PerformAction(user, actionComp, action, action.Event, _timing.CurTime);
|
||||
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
|
||||
}
|
||||
else
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(action, coords));
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(actionId, coords));
|
||||
|
||||
if (!action.Repeat)
|
||||
StopTargeting();
|
||||
@@ -243,7 +248,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityTargetAction action, EntityUid user, ActionsComponent actionComp)
|
||||
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, EntityTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
|
||||
{
|
||||
if (_actionsSystem == null)
|
||||
return false;
|
||||
@@ -264,10 +269,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
action.Event.Performer = user;
|
||||
}
|
||||
|
||||
_actionsSystem.PerformAction(user, actionComp, action, action.Event, _timing.CurTime);
|
||||
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
|
||||
}
|
||||
else
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(action, args.EntityUid));
|
||||
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(actionId, args.EntityUid));
|
||||
|
||||
if (!action.Repeat)
|
||||
StopTargeting();
|
||||
@@ -322,13 +327,17 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void TriggerAction(int index)
|
||||
{
|
||||
if (CurrentPage[index] is not { } type)
|
||||
if (_actionsSystem == null ||
|
||||
CurrentPage[index] is not { } actionId ||
|
||||
!_actionsSystem.TryGetActionData(actionId, out var baseAction))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (type is TargetedAction action)
|
||||
ToggleTargeting(action);
|
||||
if (baseAction is BaseTargetActionComponent action)
|
||||
ToggleTargeting(actionId, action);
|
||||
else
|
||||
_actionsSystem?.TriggerAction(type);
|
||||
_actionsSystem?.TriggerAction(actionId, baseAction);
|
||||
}
|
||||
|
||||
private void ChangePage(int index)
|
||||
@@ -360,14 +369,14 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
ChangePage(_currentPageIndex + 1);
|
||||
}
|
||||
|
||||
private void AppendAction(ActionType action)
|
||||
private void AppendAction(EntityUid action)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.Action != null)
|
||||
if (button.ActionId != null)
|
||||
continue;
|
||||
|
||||
SetAction(button, action);
|
||||
@@ -388,39 +397,45 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActionAdded(ActionType action)
|
||||
private void OnActionAdded(EntityUid actionId)
|
||||
{
|
||||
if (_actionsSystem == null ||
|
||||
!_actionsSystem.TryGetActionData(actionId, out var action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if the action is toggled when we add it, start targeting
|
||||
if (action is TargetedAction targetAction && action.Toggled)
|
||||
StartTargeting(targetAction);
|
||||
if (action is BaseTargetActionComponent targetAction && action.Toggled)
|
||||
StartTargeting(actionId, targetAction);
|
||||
|
||||
foreach (var page in _pages)
|
||||
{
|
||||
for (var i = 0; i < page.Size; i++)
|
||||
{
|
||||
if (page[i] == action)
|
||||
if (page[i] == actionId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppendAction(action);
|
||||
AppendAction(actionId);
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnActionRemoved(ActionType action)
|
||||
private void OnActionRemoved(EntityUid actionId)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
// stop targeting if the action is removed
|
||||
if (action == SelectingTargetFor)
|
||||
if (actionId == SelectingTargetFor)
|
||||
StopTargeting();
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.Action == action)
|
||||
if (button.ActionId == actionId)
|
||||
{
|
||||
SetAction(button, null);
|
||||
}
|
||||
@@ -430,7 +445,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
for (var i = 0; i < page.Size; i++)
|
||||
{
|
||||
if (page[i] == action)
|
||||
if (page[i] == actionId)
|
||||
{
|
||||
page[i] = null;
|
||||
}
|
||||
@@ -440,15 +455,15 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnActionReplaced(ActionType existing, ActionType action)
|
||||
private void OnActionReplaced(EntityUid actionId)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
foreach (var button in _container.GetButtons())
|
||||
{
|
||||
if (button.Action == existing)
|
||||
button.UpdateData(action);
|
||||
if (button.ActionId == actionId)
|
||||
button.UpdateData(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,15 +514,15 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchesFilter(ActionType action, Filters filter)
|
||||
private bool MatchesFilter(BaseActionComponent action, Filters filter)
|
||||
{
|
||||
return filter switch
|
||||
{
|
||||
Filters.Enabled => action.Enabled,
|
||||
Filters.Item => action.Provider != null && action.Provider != _playerManager.LocalPlayer?.ControlledEntity,
|
||||
Filters.Innate => action.Provider == null || action.Provider == _playerManager.LocalPlayer?.ControlledEntity,
|
||||
Filters.Instant => action is InstantAction,
|
||||
Filters.Targeted => action is TargetedAction,
|
||||
Filters.Instant => action is InstantActionComponent,
|
||||
Filters.Targeted => action is BaseTargetActionComponent,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
|
||||
};
|
||||
}
|
||||
@@ -518,7 +533,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
_window.ResultsGrid.RemoveAllChildren();
|
||||
}
|
||||
|
||||
private void PopulateActions(IEnumerable<ActionType> actions)
|
||||
private void PopulateActions(IEnumerable<(EntityUid Id, BaseActionComponent Comp)> actions)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
@@ -529,7 +544,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
var button = new ActionButton {Locked = true};
|
||||
|
||||
button.UpdateData(action);
|
||||
button.UpdateData(action.Id);
|
||||
button.ActionPressed += OnWindowActionPressed;
|
||||
button.ActionUnpressed += OnWindowActionUnPressed;
|
||||
button.ActionFocusExited += OnWindowActionFocusExisted;
|
||||
@@ -538,16 +553,14 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchAndDisplay(ActionsComponent? component = null)
|
||||
private void SearchAndDisplay()
|
||||
{
|
||||
if (_window == null)
|
||||
if (_window is not { Disposed: false } || _actionsSystem == null)
|
||||
return;
|
||||
|
||||
var search = _window.SearchBar.Text;
|
||||
var filters = _window.FilterButton.SelectedKeys;
|
||||
|
||||
IEnumerable<ActionType>? actions = (component ?? _actionsSystem?.PlayerActions)?.Actions;
|
||||
actions ??= Array.Empty<ActionType>();
|
||||
var actions = _actionsSystem.GetClientActions();
|
||||
|
||||
if (filters.Count == 0 && string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
@@ -557,45 +570,46 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
actions = actions.Where(action =>
|
||||
{
|
||||
if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action, filter)))
|
||||
if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action.Comp, filter)))
|
||||
return false;
|
||||
|
||||
if (action.Keywords.Any(keyword => search.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
|
||||
if (action.Comp.Keywords.Any(keyword => search.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
if (action.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
var name = EntityManager.GetComponent<MetaDataComponent>(action.Id).EntityName;
|
||||
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (action.Provider == null || action.Provider == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
if (action.Comp.Provider == null || action.Comp.Provider == _playerManager.LocalPlayer?.ControlledEntity)
|
||||
return false;
|
||||
|
||||
var name = EntityManager.GetComponent<MetaDataComponent>(action.Provider.Value).EntityName;
|
||||
return name.Contains(search, StringComparison.OrdinalIgnoreCase);
|
||||
var providerName = EntityManager.GetComponent<MetaDataComponent>(action.Comp.Provider.Value).EntityName;
|
||||
return providerName.Contains(search, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
PopulateActions(actions);
|
||||
}
|
||||
|
||||
private void SetAction(ActionButton button, ActionType? type)
|
||||
private void SetAction(ActionButton button, EntityUid? actionId)
|
||||
{
|
||||
int position;
|
||||
|
||||
if (type == null)
|
||||
if (actionId == null)
|
||||
{
|
||||
button.ClearData();
|
||||
if (_container?.TryGetButtonIndex(button, out position) ?? false)
|
||||
{
|
||||
CurrentPage[position] = type;
|
||||
CurrentPage[position] = actionId;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.TryReplaceWith(type) &&
|
||||
if (button.TryReplaceWith(actionId.Value) &&
|
||||
_container != null &&
|
||||
_container.TryGetButtonIndex(button, out position))
|
||||
{
|
||||
CurrentPage[position] = type;
|
||||
CurrentPage[position] = actionId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +617,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
if (UIManager.CurrentlyHovered is ActionButton button)
|
||||
{
|
||||
if (!_menuDragHelper.IsDragging || _menuDragHelper.Dragged?.Action is not { } type)
|
||||
if (!_menuDragHelper.IsDragging || _menuDragHelper.Dragged?.ActionId is not { } type)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
return;
|
||||
@@ -669,7 +683,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
if (args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
if (button.Action == null)
|
||||
if (button.ActionId == null)
|
||||
{
|
||||
var ev = new FillActionSlotEvent();
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
|
||||
@@ -692,25 +706,28 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
if (args.Function != EngineKeyFunctions.UIClick || _actionsSystem == null)
|
||||
return;
|
||||
|
||||
if (UIManager.CurrentlyHovered == button)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (button.Action is TargetedAction action)
|
||||
if (_actionsSystem.TryGetActionData(button.ActionId, out var baseAction))
|
||||
{
|
||||
// for target actions, we go into "select target" mode, we don't
|
||||
// message the server until we actually pick our target.
|
||||
if (baseAction is BaseTargetActionComponent action)
|
||||
{
|
||||
// for target actions, we go into "select target" mode, we don't
|
||||
// message the server until we actually pick our target.
|
||||
|
||||
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||
// targeting
|
||||
ToggleTargeting(action);
|
||||
return;
|
||||
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||
// targeting
|
||||
ToggleTargeting(button.ActionId.Value, action);
|
||||
return;
|
||||
}
|
||||
|
||||
_actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
|
||||
}
|
||||
|
||||
_actionsSystem?.TriggerAction(button.Action);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -722,7 +739,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private bool OnMenuBeginDrag()
|
||||
{
|
||||
if (_menuDragHelper.Dragged?.Action is { } action)
|
||||
if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action))
|
||||
{
|
||||
if (action.EntityIcon != null)
|
||||
{
|
||||
@@ -828,7 +845,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
foreach (ref var assignment in CollectionsMarshal.AsSpan(assignments))
|
||||
{
|
||||
_pages[assignment.Hotbar][assignment.Slot] = assignment.Action;
|
||||
_pages[assignment.Hotbar][assignment.Slot] = assignment.ActionId;
|
||||
}
|
||||
|
||||
_container?.SetActionData(_pages[_currentPageIndex]);
|
||||
@@ -864,7 +881,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
LoadDefaultActions(component);
|
||||
_container?.SetActionData(_pages[DefaultPageIndex]);
|
||||
SearchAndDisplay(component);
|
||||
SearchAndDisplay();
|
||||
}
|
||||
|
||||
private void OnComponentUnlinked()
|
||||
@@ -876,7 +893,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void LoadDefaultActions(ActionsComponent component)
|
||||
{
|
||||
var actions = component.Actions.Where(actionType => actionType.AutoPopulate).ToList();
|
||||
if (_actionsSystem == null)
|
||||
return;
|
||||
|
||||
var actions = _actionsSystem.GetClientActions().Where(action => action.Comp.AutoPopulate).ToList();
|
||||
|
||||
var offset = 0;
|
||||
var totalPages = _pages.Count;
|
||||
@@ -892,7 +912,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
var actionIndex = slot + offset;
|
||||
if (actionIndex < actions.Count)
|
||||
{
|
||||
page[slot] = actions[slot + offset];
|
||||
page[slot] = actions[slot + offset].Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -916,26 +936,26 @@ 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(TargetedAction action)
|
||||
private void ToggleTargeting(EntityUid actionId, BaseTargetActionComponent action)
|
||||
{
|
||||
if (SelectingTargetFor == action)
|
||||
if (SelectingTargetFor == actionId)
|
||||
{
|
||||
StopTargeting();
|
||||
return;
|
||||
}
|
||||
|
||||
StartTargeting(action);
|
||||
StartTargeting(actionId, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts us in targeting mode, where we need to pick either a target point or entity
|
||||
/// </summary>
|
||||
private void StartTargeting(TargetedAction action)
|
||||
private void StartTargeting(EntityUid actionId, BaseTargetActionComponent action)
|
||||
{
|
||||
// If we were targeting something else we should stop
|
||||
StopTargeting();
|
||||
|
||||
SelectingTargetFor = action;
|
||||
SelectingTargetFor = actionId;
|
||||
|
||||
// override "held-item" overlay
|
||||
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
|
||||
@@ -955,7 +975,7 @@ 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 EntityTargetAction entityAction)
|
||||
if (action is not EntityTargetActionComponent entityAction)
|
||||
return;
|
||||
|
||||
Func<EntityUid, bool>? predicate = null;
|
||||
@@ -992,20 +1012,20 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
//TODO: Serialize this shit
|
||||
private sealed class ActionPage
|
||||
{
|
||||
private readonly ActionType?[] _data;
|
||||
private readonly EntityUid?[] _data;
|
||||
|
||||
public ActionPage(int size)
|
||||
{
|
||||
_data = new ActionType?[size];
|
||||
_data = new EntityUid?[size];
|
||||
}
|
||||
|
||||
public ActionType? this[int index]
|
||||
public EntityUid? this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set => _data[index] = value;
|
||||
}
|
||||
|
||||
public static implicit operator ActionType?[](ActionPage p)
|
||||
public static implicit operator EntityUid?[](ActionPage p)
|
||||
{
|
||||
return p._data.ToArray();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Actions.UI;
|
||||
using Content.Client.Cooldown;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -19,11 +19,14 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls;
|
||||
|
||||
public sealed class ActionButton : Control
|
||||
{
|
||||
private IEntityManager? _entities;
|
||||
|
||||
private ActionUIController Controller => UserInterfaceManager.GetUIController<ActionUIController>();
|
||||
private IEntityManager Entities => _entities ??= IoCManager.Resolve<IEntityManager>();
|
||||
private ActionsSystem Actions => Entities.System<ActionsSystem>();
|
||||
private bool _beingHovered;
|
||||
private bool _depressed;
|
||||
private bool _toggled;
|
||||
private bool _spriteViewDirty;
|
||||
|
||||
public BoundKeyFunction? KeyBind
|
||||
{
|
||||
@@ -48,7 +51,7 @@ public sealed class ActionButton : Control
|
||||
private readonly SpriteView _smallItemSpriteView;
|
||||
private readonly SpriteView _bigItemSpriteView;
|
||||
|
||||
public ActionType? Action { get; private set; }
|
||||
public EntityUid? ActionId { get; private set; }
|
||||
public bool Locked { get; set; }
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
|
||||
@@ -176,11 +179,11 @@ public sealed class ActionButton : Control
|
||||
|
||||
private Control? SupplyTooltip(Control sender)
|
||||
{
|
||||
if (Action == null)
|
||||
if (!Entities.TryGetComponent(ActionId, out MetaDataComponent? metadata))
|
||||
return null;
|
||||
|
||||
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.DisplayName));
|
||||
var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(Action.Description));
|
||||
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
|
||||
var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
|
||||
|
||||
return new ActionAlertTooltip(name, decr);
|
||||
}
|
||||
@@ -192,18 +195,9 @@ public sealed class ActionButton : Control
|
||||
|
||||
private void UpdateItemIcon()
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (Action?.EntityIcon != null && !entityManager.EntityExists(Action.EntityIcon))
|
||||
{
|
||||
// This is almost certainly because a player received/processed their own actions component state before
|
||||
// being send the entity in their inventory that enabled this action.
|
||||
|
||||
// Defer updating icons to the next FrameUpdate().
|
||||
_spriteViewDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Action?.EntityIcon is not { } entity || !entityManager.HasComponent<SpriteComponent>(entity))
|
||||
if (!Actions.TryGetActionData(ActionId, out var action) ||
|
||||
action is not { EntityIcon: { } entity } ||
|
||||
!Entities.HasComponent<SpriteComponent>(entity))
|
||||
{
|
||||
_bigItemSpriteView.Visible = false;
|
||||
_bigItemSpriteView.SetEntity(null);
|
||||
@@ -212,7 +206,7 @@ public sealed class ActionButton : Control
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Action.ItemIconStyle)
|
||||
switch (action.ItemIconStyle)
|
||||
{
|
||||
case ItemActionIconStyle.BigItem:
|
||||
_bigItemSpriteView.Visible = true;
|
||||
@@ -238,17 +232,17 @@ public sealed class ActionButton : Control
|
||||
|
||||
private void SetActionIcon(Texture? texture)
|
||||
{
|
||||
if (texture == null || Action == null)
|
||||
if (!Actions.TryGetActionData(ActionId, out var 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;
|
||||
@@ -256,7 +250,7 @@ public sealed class ActionButton : Control
|
||||
else
|
||||
{
|
||||
_bigActionIcon.Texture = texture;
|
||||
_bigActionIcon.Modulate = Action.IconColor;
|
||||
_bigActionIcon.Modulate = action.IconColor;
|
||||
_bigActionIcon.Visible = true;
|
||||
_smallActionIcon.Texture = null;
|
||||
_smallActionIcon.Visible = false;
|
||||
@@ -267,39 +261,39 @@ public sealed class ActionButton : Control
|
||||
{
|
||||
UpdateItemIcon();
|
||||
|
||||
if (Action == null)
|
||||
if (!Actions.TryGetActionData(ActionId, out var action))
|
||||
{
|
||||
SetActionIcon(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((Controller.SelectingTargetFor == Action || Action.Toggled) && Action.IconOn != null)
|
||||
SetActionIcon(Action.IconOn.Frame0());
|
||||
if ((Controller.SelectingTargetFor == ActionId || action.Toggled) && action.IconOn != null)
|
||||
SetActionIcon(action.IconOn.Frame0());
|
||||
else
|
||||
SetActionIcon(Action.Icon?.Frame0());
|
||||
SetActionIcon(action.Icon?.Frame0());
|
||||
}
|
||||
|
||||
public bool TryReplaceWith(ActionType action)
|
||||
public bool TryReplaceWith(EntityUid actionId)
|
||||
{
|
||||
if (Locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateData(action);
|
||||
UpdateData(actionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateData(ActionType action)
|
||||
public void UpdateData(EntityUid actionId)
|
||||
{
|
||||
Action = action;
|
||||
ActionId = actionId;
|
||||
Label.Visible = true;
|
||||
UpdateIcons();
|
||||
}
|
||||
|
||||
public void ClearData()
|
||||
{
|
||||
Action = null;
|
||||
ActionId = null;
|
||||
Cooldown.Visible = false;
|
||||
Cooldown.Progress = 1;
|
||||
Label.Visible = false;
|
||||
@@ -310,20 +304,19 @@ public sealed class ActionButton : Control
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_spriteViewDirty)
|
||||
if (!Actions.TryGetActionData(ActionId, out var action))
|
||||
{
|
||||
_spriteViewDirty = false;
|
||||
UpdateIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Action?.Cooldown != null)
|
||||
if (action.Cooldown != null)
|
||||
{
|
||||
Cooldown.FromTime(Action.Cooldown.Value.Start, Action.Cooldown.Value.End);
|
||||
Cooldown.FromTime(action.Cooldown.Value.Start, action.Cooldown.Value.End);
|
||||
}
|
||||
|
||||
if (Action != null && _toggled != Action.Toggled)
|
||||
if (ActionId != null && _toggled != action.Toggled)
|
||||
{
|
||||
_toggled = Action.Toggled;
|
||||
_toggled = action.Toggled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +343,7 @@ public sealed class ActionButton : Control
|
||||
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 (!Actions.TryGetActionData(ActionId, out var action) || action is not { Enabled: true })
|
||||
return;
|
||||
|
||||
if (_depressed && !depress)
|
||||
@@ -368,14 +361,14 @@ public sealed class ActionButton : Control
|
||||
HighlightRect.Visible = _beingHovered;
|
||||
|
||||
// always show the normal empty button style if no action in this slot
|
||||
if (Action == null)
|
||||
if (!Actions.TryGetActionData(ActionId, out var 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);
|
||||
}
|
||||
@@ -390,16 +383,16 @@ public sealed class ActionButton : Control
|
||||
}
|
||||
|
||||
// if it's toggled on, always show the toggled on style (currently same as depressed style)
|
||||
if (Action.Toggled || Controller.SelectingTargetFor == Action)
|
||||
if (action.Toggled || Controller.SelectingTargetFor == ActionId)
|
||||
{
|
||||
// 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;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -30,7 +29,7 @@ public class ActionButtonContainer : GridContainer
|
||||
}
|
||||
}
|
||||
|
||||
public void SetActionData(params ActionType?[] actionTypes)
|
||||
public void SetActionData(params EntityUid?[] actionTypes)
|
||||
{
|
||||
ClearActionData();
|
||||
|
||||
@@ -40,7 +39,7 @@ public class ActionButtonContainer : GridContainer
|
||||
if (action == null)
|
||||
continue;
|
||||
|
||||
((ActionButton) GetChild(i)).UpdateData(action);
|
||||
((ActionButton) GetChild(i)).UpdateData(action.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.CombatMode;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -25,6 +24,8 @@ public sealed class ActionsAddedTest
|
||||
var sEntMan = server.ResolveDependency<IEntityManager>();
|
||||
var cEntMan = client.ResolveDependency<IEntityManager>();
|
||||
var session = server.ResolveDependency<IPlayerManager>().ServerSessions.Single();
|
||||
var sActionSystem = server.System<SharedActionsSystem>();
|
||||
var cActionSystem = client.System<SharedActionsSystem>();
|
||||
|
||||
// Dummy ticker is disabled - client should be in control of a normal mob.
|
||||
Assert.NotNull(session.AttachedEntity);
|
||||
@@ -43,21 +44,24 @@ public sealed class ActionsAddedTest
|
||||
// This action should have a non-null event both on the server & client.
|
||||
var evType = typeof(ToggleCombatActionEvent);
|
||||
|
||||
var sActions = sComp!.Actions.Where(
|
||||
x => x is InstantAction act && act.Event?.GetType() == evType).ToArray();
|
||||
var cActions = cComp!.Actions.Where(
|
||||
x => x is InstantAction act && act.Event?.GetType() == evType).ToArray();
|
||||
var sActions = sActionSystem.GetActions(ent).Where(
|
||||
x => x.Comp is InstantActionComponent act && act.Event?.GetType() == evType).ToArray();
|
||||
var cActions = cActionSystem.GetActions(ent).Where(
|
||||
x => x.Comp is InstantActionComponent act && act.Event?.GetType() == evType).ToArray();
|
||||
|
||||
Assert.That(sActions.Length, Is.EqualTo(1));
|
||||
Assert.That(cActions.Length, Is.EqualTo(1));
|
||||
|
||||
var sAct = (InstantAction) sActions[0];
|
||||
var cAct = (InstantAction) cActions[0];
|
||||
var sAct = sActions[0].Comp;
|
||||
var cAct = cActions[0].Comp;
|
||||
|
||||
Assert.NotNull(sAct);
|
||||
Assert.NotNull(cAct);
|
||||
|
||||
// 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.Event, cAct.Event), Is.False);
|
||||
Assert.That(ReferenceEquals(sAct.BaseEvent, cAct.BaseEvent), Is.False);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Abilities.Mime
|
||||
{
|
||||
@@ -22,18 +20,12 @@ namespace Content.Server.Abilities.Mime
|
||||
/// The wall prototype to use.
|
||||
/// </summary>
|
||||
[DataField("wallPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string WallPrototype = "WallInvisible";
|
||||
public string WallPrototype = "ActionMimeInvisibleWall";
|
||||
|
||||
[DataField("invisibleWallAction")]
|
||||
public InstantAction InvisibleWallAction = new()
|
||||
{
|
||||
UseDelay = TimeSpan.FromSeconds(30),
|
||||
Icon = new SpriteSpecifier.Texture(new("Structures/Walls/solid.rsi/full.png")),
|
||||
DisplayName = "mime-invisible-wall",
|
||||
Description = "mime-invisible-wall-desc",
|
||||
Priority = -1,
|
||||
Event = new InvisibleWallActionEvent(),
|
||||
};
|
||||
[DataField("invisibleWallAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? InvisibleWallAction;
|
||||
|
||||
[DataField("invisibleWallActionEntity")] public EntityUid? InvisibleWallActionEntity;
|
||||
|
||||
// The vow zone lies below
|
||||
public bool VowBroken = false;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Speech.Muting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Speech.Muting;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Abilities.Mime
|
||||
{
|
||||
@@ -29,8 +28,10 @@ namespace Content.Server.Abilities.Mime
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MimePowersComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<MimePowersComponent, MapInitEvent>(OnComponentMapInit);
|
||||
SubscribeLocalEvent<MimePowersComponent, InvisibleWallActionEvent>(OnInvisibleWall);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -53,10 +54,14 @@ namespace Content.Server.Abilities.Mime
|
||||
private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args)
|
||||
{
|
||||
EnsureComp<MutedComponent>(uid);
|
||||
_actionsSystem.AddAction(uid, component.InvisibleWallAction, uid);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
|
||||
}
|
||||
|
||||
private void OnComponentMapInit(EntityUid uid, MimePowersComponent component, MapInitEvent args)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an invisible wall in a free space after some checks.
|
||||
/// </summary>
|
||||
@@ -116,7 +121,7 @@ namespace Content.Server.Abilities.Mime
|
||||
RemComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowBroken);
|
||||
_actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallAction);
|
||||
_actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -139,11 +144,7 @@ namespace Content.Server.Abilities.Mime
|
||||
AddComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.VowBroken);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
|
||||
_actionsSystem.AddAction(uid, mimePowers.InvisibleWallAction, uid);
|
||||
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class InvisibleWallActionEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Actions;
|
||||
|
||||
@@ -19,12 +20,8 @@ namespace Content.Server.Actions;
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActionOnInteractComponent : Component
|
||||
{
|
||||
[DataField("activateActions")]
|
||||
public List<InstantAction>? ActivateActions;
|
||||
[DataField("actions", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string>? Actions;
|
||||
|
||||
[DataField("entityActions")]
|
||||
public List<EntityTargetAction>? EntityActions;
|
||||
|
||||
[DataField("worldActions")]
|
||||
public List<WorldTargetAction>? WorldActions;
|
||||
[DataField("actionEntities")] public List<EntityUid>? ActionEntities;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -25,51 +24,41 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
|
||||
private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || component.ActivateActions == null)
|
||||
if (args.Handled || component.ActionEntities == null)
|
||||
return;
|
||||
|
||||
var options = new List<InstantAction>();
|
||||
foreach (var action in component.ActivateActions)
|
||||
{
|
||||
if (ValidAction(action))
|
||||
options.Add(action);
|
||||
}
|
||||
|
||||
var options = GetValidActions<InstantActionComponent>(component.ActionEntities);
|
||||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
var act = _random.Pick(options);
|
||||
var (actId, act) = _random.Pick(options);
|
||||
if (act.Event != null)
|
||||
act.Event.Performer = args.User;
|
||||
|
||||
act.Provider = uid;
|
||||
_actions.PerformAction(args.User, null, act, act.Event, _timing.CurTime, false);
|
||||
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
if (args.Handled || component.ActionEntities == null)
|
||||
return;
|
||||
|
||||
// First, try entity target actions
|
||||
if (args.Target != null && component.EntityActions != null)
|
||||
if (args.Target != null)
|
||||
{
|
||||
var entOptions = new List<EntityTargetAction>();
|
||||
foreach (var action in component.EntityActions)
|
||||
var entOptions = GetValidActions<EntityTargetActionComponent>(component.ActionEntities, args.CanReach);
|
||||
for (var i = entOptions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!ValidAction(action, args.CanReach))
|
||||
continue;
|
||||
|
||||
var action = entOptions[i].Comp;
|
||||
if (!_actions.ValidateEntityTarget(args.User, args.Target.Value, action))
|
||||
continue;
|
||||
|
||||
entOptions.Add(action);
|
||||
entOptions.RemoveAt(i);
|
||||
}
|
||||
|
||||
if (entOptions.Count > 0)
|
||||
{
|
||||
var entAct = _random.Pick(entOptions);
|
||||
var (entActId, entAct) = _random.Pick(entOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
entAct.Event.Performer = args.User;
|
||||
@@ -77,32 +66,25 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
}
|
||||
|
||||
entAct.Provider = uid;
|
||||
_actions.PerformAction(args.User, null, entAct, entAct.Event, _timing.CurTime, false);
|
||||
_actions.PerformAction(args.User, null, entActId, entAct, entAct.Event, _timing.CurTime, false);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else: try world target actions
|
||||
if (component.WorldActions == null)
|
||||
return;
|
||||
|
||||
var options = new List<WorldTargetAction>();
|
||||
foreach (var action in component.WorldActions)
|
||||
var options = GetValidActions<WorldTargetActionComponent>(component.ActionEntities, args.CanReach);
|
||||
for (var i = options.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!ValidAction(action, args.CanReach))
|
||||
continue;
|
||||
|
||||
var action = options[i].Comp;
|
||||
if (!_actions.ValidateWorldTarget(args.User, args.ClickLocation, action))
|
||||
continue;
|
||||
|
||||
options.Add(action);
|
||||
options.RemoveAt(i);
|
||||
}
|
||||
|
||||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
var act = _random.Pick(options);
|
||||
var (actId, act) = _random.Pick(options);
|
||||
if (act.Event != null)
|
||||
{
|
||||
act.Event.Performer = args.User;
|
||||
@@ -110,22 +92,44 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
}
|
||||
|
||||
act.Provider = uid;
|
||||
_actions.PerformAction(args.User, null, act, act.Event, _timing.CurTime, false);
|
||||
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private bool ValidAction(ActionType act, bool canReach = true)
|
||||
private bool ValidAction(BaseActionComponent action, bool canReach = true)
|
||||
{
|
||||
if (!act.Enabled)
|
||||
if (!action.Enabled)
|
||||
return false;
|
||||
|
||||
if (act.Charges.HasValue && act.Charges <= 0)
|
||||
if (action.Charges.HasValue && action.Charges <= 0)
|
||||
return false;
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
if (act.Cooldown.HasValue && act.Cooldown.Value.End > curTime)
|
||||
if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
|
||||
return false;
|
||||
|
||||
return canReach || act is TargetedAction { CheckCanAccess: false };
|
||||
return canReach || action is BaseTargetActionComponent { CheckCanAccess: false };
|
||||
}
|
||||
|
||||
private List<(EntityUid Id, T Comp)> GetValidActions<T>(List<EntityUid>? actions, bool canReach = true) where T : BaseActionComponent
|
||||
{
|
||||
var valid = new List<(EntityUid Id, T Comp)>();
|
||||
|
||||
if (actions == null)
|
||||
return valid;
|
||||
|
||||
foreach (var id in actions)
|
||||
{
|
||||
if (!_actions.TryGetActionData(id, out var baseAction) ||
|
||||
baseAction as T is not { } action ||
|
||||
!ValidAction(action, canReach))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
valid.Add((id, action));
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Actions
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Animals.Components;
|
||||
@@ -14,8 +12,8 @@ namespace Content.Server.Animals.Components;
|
||||
[RegisterComponent]
|
||||
public sealed partial class EggLayerComponent : Component
|
||||
{
|
||||
[DataField("eggLayAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string EggLayAction = "AnimalLayEgg";
|
||||
[DataField("eggLayAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string EggLayAction = "ActionAnimalLayEgg";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("hungerUsage")]
|
||||
@@ -51,5 +49,3 @@ public sealed partial class EggLayerComponent : Component
|
||||
[DataField("accumulatedFrametime")]
|
||||
public float AccumulatedFrametime;
|
||||
}
|
||||
|
||||
public sealed partial class EggLayInstantActionEvent : InstantActionEvent {}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Animals.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Animals.Systems;
|
||||
|
||||
public sealed class EggLayerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
@@ -54,10 +52,10 @@ public sealed class EggLayerSystem : EntitySystem
|
||||
|
||||
private void OnComponentInit(EntityUid uid, EggLayerComponent component, ComponentInit args)
|
||||
{
|
||||
if (!_prototype.TryIndex<InstantActionPrototype>(component.EggLayAction, out var action))
|
||||
if (string.IsNullOrWhiteSpace(component.EggLayAction))
|
||||
return;
|
||||
|
||||
_actions.AddAction(uid, new InstantAction(action), uid);
|
||||
_actions.AddAction(uid, Spawn(component.EggLayAction), uid);
|
||||
component.CurrentEggLayCooldown = _random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
@@ -93,8 +94,10 @@ namespace Content.Server.Atmos.Components
|
||||
[DataField("tankFragmentScale"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TankFragmentScale = 2 * Atmospherics.OneAtmosphere;
|
||||
|
||||
[DataField("toggleAction", required: true)]
|
||||
public InstantAction ToggleAction = new();
|
||||
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ToggleAction = "ActionToggleInternals";
|
||||
|
||||
[DataField("toggleActionEntity")] public EntityUid? ToggleActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Valve to release gas from tank
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
@@ -8,17 +7,17 @@ using Content.Server.UserInterface;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
@@ -105,7 +104,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
private void OnGetActions(EntityUid uid, GasTankComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.Actions.Add(component.ToggleAction);
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, GasTankComponent component, ExaminedEvent args)
|
||||
@@ -233,7 +232,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (_internals.TryConnectTank(internals, component.Owner))
|
||||
component.User = internals.Owner;
|
||||
|
||||
_actions.SetToggled(component.ToggleAction, component.IsConnected);
|
||||
_actions.SetToggled(component.ToggleActionEntity, component.IsConnected);
|
||||
|
||||
// Couldn't toggle!
|
||||
if (!component.IsConnected)
|
||||
@@ -255,7 +254,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var internals = GetInternalsComponent(component);
|
||||
component.User = null;
|
||||
|
||||
_actions.SetToggled(component.ToggleAction, false);
|
||||
_actions.SetToggled(component.ToggleActionEntity, false);
|
||||
|
||||
_internals.DisconnectTank(internals);
|
||||
component.DisconnectStream?.Stop();
|
||||
|
||||
@@ -2,9 +2,9 @@ using Content.Server.Actions;
|
||||
using Content.Server.Bed.Components;
|
||||
using Content.Server.Bed.Sleep;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Bed;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -12,9 +12,7 @@ using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Bed
|
||||
@@ -23,7 +21,6 @@ namespace Content.Server.Bed
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SleepingSystem _sleepingSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
@@ -42,18 +39,17 @@ namespace Content.Server.Bed
|
||||
|
||||
private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args)
|
||||
{
|
||||
_prototypeManager.TryIndex<InstantActionPrototype>("Sleep", out var sleepAction);
|
||||
if (args.Buckling)
|
||||
{
|
||||
AddComp<HealOnBuckleHealingComponent>(uid);
|
||||
component.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(component.HealTime);
|
||||
if (sleepAction != null)
|
||||
_actionsSystem.AddAction(args.BuckledEntity, new InstantAction(sleepAction), null);
|
||||
component.SleepAction = Spawn(SleepingSystem.SleepActionId);
|
||||
_actionsSystem.AddAction(args.BuckledEntity, component.SleepAction.Value, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sleepAction != null)
|
||||
_actionsSystem.RemoveAction(args.BuckledEntity, sleepAction);
|
||||
if (component.SleepAction != null)
|
||||
_actionsSystem.RemoveAction(args.BuckledEntity, component.SleepAction.Value);
|
||||
|
||||
_sleepingSystem.TryWaking(args.BuckledEntity);
|
||||
RemComp<HealOnBuckleHealingComponent>(uid);
|
||||
|
||||
@@ -17,5 +17,7 @@ namespace Content.Server.Bed.Components
|
||||
public float SleepMultiplier = 3f;
|
||||
|
||||
public TimeSpan NextHealTime = TimeSpan.Zero; //Next heal
|
||||
|
||||
[DataField("sleepAction")] public EntityUid? SleepAction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Sound.Components;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
@@ -10,16 +9,14 @@ using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Interaction.Components;
|
||||
|
||||
namespace Content.Server.Bed.Sleep
|
||||
{
|
||||
@@ -33,6 +30,8 @@ namespace Content.Server.Bed.Sleep
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] public const string SleepActionId = "ActionSleep";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -53,7 +52,6 @@ namespace Content.Server.Bed.Sleep
|
||||
/// </summary>
|
||||
private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, SleepStateChangedEvent args)
|
||||
{
|
||||
_prototypeManager.TryIndex<InstantActionPrototype>("Wake", out var wakeAction);
|
||||
if (args.FellAsleep)
|
||||
{
|
||||
// Expiring status effects would remove the components needed for sleeping
|
||||
@@ -72,16 +70,8 @@ namespace Content.Server.Bed.Sleep
|
||||
emitSound.PopUp = sleepSound.PopUp;
|
||||
}
|
||||
|
||||
if (wakeAction != null)
|
||||
{
|
||||
var wakeInstance = new InstantAction(wakeAction);
|
||||
wakeInstance.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(15));
|
||||
_actionsSystem.AddAction(uid, wakeInstance, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (wakeAction != null)
|
||||
_actionsSystem.RemoveAction(uid, wakeAction);
|
||||
|
||||
RemComp<StunnedComponent>(uid);
|
||||
RemComp<KnockedDownComponent>(uid);
|
||||
@@ -194,8 +184,7 @@ namespace Content.Server.Bed.Sleep
|
||||
RaiseLocalEvent(uid, ref tryingToSleepEvent);
|
||||
if (tryingToSleepEvent.Cancelled) return false;
|
||||
|
||||
if (_prototypeManager.TryIndex<InstantActionPrototype>("Sleep", out var sleepAction))
|
||||
_actionsSystem.RemoveAction(uid, sleepAction);
|
||||
_actionsSystem.RemoveAction(uid, SleepActionId);
|
||||
|
||||
EnsureComp<SleepingComponent>(uid);
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Ghost.Roles.Events;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bible;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -179,8 +180,9 @@ namespace Content.Server.Bible
|
||||
if (component.AlreadySummoned)
|
||||
return;
|
||||
|
||||
args.Actions.Add(component.SummonAction);
|
||||
args.AddAction(ref component.SummonActionEntity, component.SummonAction);
|
||||
}
|
||||
|
||||
private void OnSummon(EntityUid uid, SummonableComponent component, SummonActionEvent args)
|
||||
{
|
||||
AttemptSummon(component, args.Performer, Transform(args.Performer));
|
||||
@@ -238,12 +240,7 @@ namespace Content.Server.Bible
|
||||
Transform(familiar).AttachParent(component.Owner);
|
||||
}
|
||||
component.AlreadySummoned = true;
|
||||
_actionsSystem.RemoveAction(user, component.SummonAction);
|
||||
_actionsSystem.RemoveAction(user, component.SummonActionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class SummonActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
@@ -18,7 +16,7 @@ namespace Content.Server.Bible.Components
|
||||
public string? SpecialItemPrototype = null;
|
||||
public bool AlreadySummoned = false;
|
||||
|
||||
[DataField("requriesBibleUser")]
|
||||
[DataField("requiresBibleUser")]
|
||||
public bool RequiresBibleUser = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,14 +25,11 @@ namespace Content.Server.Bible.Components
|
||||
[ViewVariables]
|
||||
public EntityUid? Summon = null;
|
||||
|
||||
[DataField("summonAction")]
|
||||
public InstantAction SummonAction = new()
|
||||
{
|
||||
Icon = new SpriteSpecifier.Texture(new ("Clothing/Head/Hats/witch.rsi/icon.png")),
|
||||
DisplayName = "bible-summon-verb",
|
||||
Description = "bible-summon-verb-desc",
|
||||
Event = new SummonActionEvent(),
|
||||
};
|
||||
[DataField("summonAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string SummonAction = "ActionBibleSummon";
|
||||
|
||||
[DataField("summonActionEntity")]
|
||||
public EntityUid? SummonActionEntity;
|
||||
|
||||
/// Used for respawning
|
||||
[DataField("accumulator")]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Clothing.Components
|
||||
{
|
||||
@@ -7,14 +7,15 @@ namespace Content.Server.Clothing.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class MaskComponent : Component
|
||||
{
|
||||
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ToggleAction = "ActionToggleMask";
|
||||
|
||||
/// <summary>
|
||||
/// This mask can be toggled (pulled up/down)
|
||||
/// </summary>
|
||||
[DataField("toggleAction")]
|
||||
public InstantAction? ToggleAction = null;
|
||||
[DataField("toggleActionEntity")]
|
||||
public EntityUid? ToggleActionEntity;
|
||||
|
||||
public bool IsToggled = false;
|
||||
}
|
||||
|
||||
public sealed partial class ToggleMaskEvent : InstantActionEvent { }
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.VoiceMask;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
@@ -38,20 +39,20 @@ namespace Content.Server.Clothing
|
||||
|
||||
private void OnGetActions(EntityUid uid, MaskComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
if (component.ToggleAction != null && !args.InHands)
|
||||
args.Actions.Add(component.ToggleAction);
|
||||
if (!args.InHands)
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnToggleMask(EntityUid uid, MaskComponent mask, ToggleMaskEvent args)
|
||||
{
|
||||
if (mask.ToggleAction == null)
|
||||
if (mask.ToggleActionEntity == null)
|
||||
return;
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(args.Performer, "mask", out var existing) || !mask.Owner.Equals(existing))
|
||||
return;
|
||||
|
||||
mask.IsToggled ^= true;
|
||||
_actionSystem.SetToggled(mask.ToggleAction, mask.IsToggled);
|
||||
_actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled);
|
||||
|
||||
// Pulling mask down can change identity, so we want to update that
|
||||
_identity.QueueIdentityUpdate(args.Performer);
|
||||
@@ -67,11 +68,11 @@ namespace Content.Server.Clothing
|
||||
// set to untoggled when unequipped, so it isn't left in a 'pulled down' state
|
||||
private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args)
|
||||
{
|
||||
if (mask.ToggleAction == null)
|
||||
if (mask.ToggleActionEntity == null)
|
||||
return;
|
||||
|
||||
mask.IsToggled = false;
|
||||
_actionSystem.SetToggled(mask.ToggleAction, mask.IsToggled);
|
||||
_actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled);
|
||||
|
||||
ToggleMaskComponents(uid, mask, args.Equipee, true);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Content.Shared.Devour;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Server.Devour.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.Devour;
|
||||
using Content.Shared.Devour.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
|
||||
namespace Content.Server.Devour;
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
using System.Threading;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
@@ -42,11 +36,14 @@ namespace Content.Server.Dragon
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxAccumulator")] public float RiftMaxAccumulator = 300f;
|
||||
|
||||
[DataField("spawnRiftAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string SpawnRiftAction = "ActionSpawnRift";
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a rift which can summon more mobs.
|
||||
/// </summary>
|
||||
[DataField("spawnRiftAction")]
|
||||
public InstantAction? SpawnRiftAction;
|
||||
[DataField("spawnRiftActionEntity")]
|
||||
public EntityUid? SpawnRiftActionEntity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("riftPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string RiftPrototype = "CarpRift";
|
||||
@@ -61,8 +58,4 @@ namespace Content.Server.Dragon
|
||||
Params = AudioParams.Default.WithVolume(3f),
|
||||
};
|
||||
}
|
||||
|
||||
public sealed partial class DragonDevourActionEvent : EntityTargetActionEvent {}
|
||||
|
||||
public sealed partial class DragonSpawnRiftActionEvent : InstantActionEvent {}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using System.Numerics;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.NPC;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Dragon;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Server.Dragon;
|
||||
@@ -56,6 +48,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<DragonComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
|
||||
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
|
||||
@@ -302,10 +295,12 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
|
||||
private void OnStartup(EntityUid uid, DragonComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.SpawnRiftAction != null)
|
||||
_actionsSystem.AddAction(uid, component.SpawnRiftAction, null);
|
||||
|
||||
Roar(component);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, DragonComponent component, MapInitEvent args)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, ref component.SpawnRiftActionEntity, component.SpawnRiftAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
@@ -94,6 +94,6 @@ public sealed partial class ZombieRuleComponent : Component
|
||||
[DataField("shuttleCalled")]
|
||||
public bool ShuttleCalled;
|
||||
|
||||
[ValidatePrototypeId<InstantActionPrototype>]
|
||||
public const string ZombifySelfActionPrototype = "TurnUndead";
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
public const string ZombifySelfActionPrototype = "ActionTurnUndead";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Zombies;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind;
|
||||
@@ -195,9 +194,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
||||
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
|
||||
{
|
||||
_zombie.ZombifyEntity(uid);
|
||||
|
||||
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombieRuleComponent.ZombifySelfActionPrototype));
|
||||
_action.RemoveAction(uid, action);
|
||||
_action.RemoveAction(uid, ZombieRuleComponent.ZombifySelfActionPrototype);
|
||||
}
|
||||
|
||||
private float GetInfectedFraction(bool includeOffStation = true, bool includeDead = false)
|
||||
@@ -325,7 +322,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
||||
EnsureComp<ZombifyOnDeathComponent>(ownedEntity);
|
||||
EnsureComp<IncurableZombieComponent>(ownedEntity);
|
||||
var inCharacterName = MetaData(ownedEntity).EntityName;
|
||||
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombieRuleComponent.ZombifySelfActionPrototype));
|
||||
var action = Spawn(ZombieRuleComponent.ZombifySelfActionPrototype);
|
||||
_action.AddAction(mind.OwnedEntity.Value, action, null);
|
||||
|
||||
var message = Loc.GetString("zombie-patientzero-role-greeting");
|
||||
|
||||
@@ -16,7 +16,6 @@ using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
@@ -48,6 +47,7 @@ namespace Content.Server.Ghost
|
||||
|
||||
SubscribeLocalEvent<GhostComponent, ComponentStartup>(OnGhostStartup);
|
||||
SubscribeLocalEvent<GhostComponent, ComponentShutdown>(OnGhostShutdown);
|
||||
SubscribeLocalEvent<GhostComponent, MapInitEvent>(OnGhostMapInit);
|
||||
|
||||
SubscribeLocalEvent<GhostComponent, ExaminedEvent>(OnGhostExamine);
|
||||
|
||||
@@ -121,13 +121,7 @@ namespace Content.Server.Ghost
|
||||
eye.VisibilityMask |= (uint) VisibilityFlags.Ghost;
|
||||
}
|
||||
|
||||
var time = _gameTiming.CurTime;
|
||||
component.TimeOfDeath = time;
|
||||
|
||||
// TODO ghost: remove once ghosts are persistent and aren't deleted when returning to body
|
||||
if (component.Action.UseDelay != null)
|
||||
component.Action.Cooldown = (time, time + component.Action.UseDelay.Value);
|
||||
_actions.AddAction(uid, component.Action, null);
|
||||
component.TimeOfDeath = _gameTiming.CurTime;
|
||||
}
|
||||
|
||||
private void OnGhostShutdown(EntityUid uid, GhostComponent component, ComponentShutdown args)
|
||||
@@ -149,10 +143,26 @@ namespace Content.Server.Ghost
|
||||
eye.VisibilityMask &= ~(uint) VisibilityFlags.Ghost;
|
||||
}
|
||||
|
||||
_actions.RemoveAction(uid, component.Action);
|
||||
_actions.RemoveAction(uid, component.ActionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGhostMapInit(EntityUid uid, GhostComponent component, MapInitEvent args)
|
||||
{
|
||||
// TODO ghost: remove once ghosts are persistent and aren't deleted when returning to body
|
||||
var time = _gameTiming.CurTime;
|
||||
var action = _actions.AddAction(uid, ref component.ActionEntity, component.Action);
|
||||
if (action?.UseDelay != null)
|
||||
{
|
||||
action.Cooldown = (time, time + action.UseDelay.Value);
|
||||
Dirty(component.ActionEntity!.Value, action);
|
||||
}
|
||||
|
||||
_actions.AddAction(uid, ref component.ToggleLightingActionEntity, component.ToggleLightingAction);
|
||||
_actions.AddAction(uid, ref component.ToggleFoVActionEntity, component.ToggleFoVAction);
|
||||
_actions.AddAction(uid, ref component.ToggleGhostsActionEntity, component.ToggleGhostsAction);
|
||||
}
|
||||
|
||||
private void OnGhostExamine(EntityUid uid, GhostComponent component, ExaminedEvent args)
|
||||
{
|
||||
var timeSinceDeath = _gameTiming.RealTime.Subtract(component.TimeOfDeath);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Guardian
|
||||
{
|
||||
@@ -24,17 +23,9 @@ namespace Content.Server.Guardian
|
||||
/// </summary>
|
||||
[ViewVariables] public ContainerSlot GuardianContainer = default!;
|
||||
|
||||
[DataField("action")]
|
||||
public InstantAction Action = new()
|
||||
{
|
||||
DisplayName = "action-name-guardian",
|
||||
Description = "action-description-guardian",
|
||||
Icon = new SpriteSpecifier.Texture(new ("Interface/Actions/manifest.png")),
|
||||
UseDelay = TimeSpan.FromSeconds(2),
|
||||
CheckCanInteract = false, // allow use while stunned, etc. Gets removed on death anyways.
|
||||
Event = new GuardianToggleActionEvent(),
|
||||
};
|
||||
}
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Action = "ActionToggleGuardian";
|
||||
|
||||
public sealed partial class GuardianToggleActionEvent : InstantActionEvent { };
|
||||
[DataField("actionEntity")] public EntityUid? ActionEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Guardian;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
@@ -16,7 +16,6 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
namespace Content.Server.Guardian
|
||||
{
|
||||
@@ -47,6 +46,7 @@ namespace Content.Server.Guardian
|
||||
SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianUnplayer);
|
||||
|
||||
SubscribeLocalEvent<GuardianHostComponent, ComponentInit>(OnHostInit);
|
||||
SubscribeLocalEvent<GuardianHostComponent, MapInitEvent>(OnHostMapInit);
|
||||
SubscribeLocalEvent<GuardianHostComponent, MoveEvent>(OnHostMove);
|
||||
SubscribeLocalEvent<GuardianHostComponent, MobStateChangedEvent>(OnHostStateChange);
|
||||
SubscribeLocalEvent<GuardianHostComponent, ComponentShutdown>(OnHostShutdown);
|
||||
@@ -90,7 +90,11 @@ namespace Content.Server.Guardian
|
||||
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
|
||||
{
|
||||
component.GuardianContainer = uid.EnsureContainer<ContainerSlot>("GuardianContainer");
|
||||
_actionSystem.AddAction(uid, component.Action, null);
|
||||
}
|
||||
|
||||
private void OnHostMapInit(EntityUid uid, GuardianHostComponent component, MapInitEvent args)
|
||||
{
|
||||
_actionSystem.AddAction(uid, ref component.ActionEntity, component.Action);
|
||||
}
|
||||
|
||||
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
|
||||
@@ -102,7 +106,7 @@ namespace Content.Server.Guardian
|
||||
_bodySystem.GibBody(component.HostedGuardian.Value);
|
||||
|
||||
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
|
||||
_actionSystem.RemoveAction(uid, component.Action);
|
||||
_actionSystem.RemoveAction(uid, component.ActionEntity);
|
||||
}
|
||||
|
||||
private void OnGuardianAttackAttempt(EntityUid uid, GuardianComponent component, AttackAttemptEvent args)
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Actions;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light;
|
||||
@@ -12,11 +11,8 @@ using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
@@ -27,7 +23,6 @@ namespace Content.Server.Light.EntitySystems
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
@@ -77,14 +72,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
|
||||
private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
if (component.ToggleAction == null
|
||||
&& _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act))
|
||||
{
|
||||
component.ToggleAction = new(act);
|
||||
}
|
||||
|
||||
if (component.ToggleAction != null)
|
||||
args.Actions.Add(component.ToggleAction);
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnToggleAction(EntityUid uid, HandheldLightComponent component, ToggleActionEvent args)
|
||||
@@ -107,20 +95,12 @@ namespace Content.Server.Light.EntitySystems
|
||||
|
||||
private void OnMapInit(EntityUid uid, HandheldLightComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.ToggleAction == null
|
||||
&& _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act))
|
||||
{
|
||||
component.ToggleAction = new(act);
|
||||
}
|
||||
|
||||
if (component.ToggleAction != null)
|
||||
_actions.AddAction(uid, component.ToggleAction, null);
|
||||
_actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, HandheldLightComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.ToggleAction != null)
|
||||
_actions.RemoveAction(uid, component.ToggleAction);
|
||||
_actions.RemoveAction(uid, component.ToggleActionEntity);
|
||||
}
|
||||
|
||||
private byte? GetLevel(EntityUid uid, HandheldLightComponent component)
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
|
||||
private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.Actions.Add(component.ToggleAction);
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
@@ -66,7 +66,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
|
||||
private void OnMindAdded(EntityUid uid, UnpoweredFlashlightComponent component, MindAddedMessage args)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, component.ToggleAction, null);
|
||||
_actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args)
|
||||
@@ -97,7 +97,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
_audioSystem.PlayPvs(flashlight.ToggleSound, uid);
|
||||
|
||||
RaiseLocalEvent(uid, new LightToggleEvent(flashlight.LightOn), true);
|
||||
_actionsSystem.SetToggled(flashlight.ToggleAction, flashlight.LightOn);
|
||||
_actionsSystem.SetToggled(flashlight.ToggleActionEntity, flashlight.LightOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Magic.Components;
|
||||
@@ -13,22 +13,14 @@ public sealed partial class SpellbookComponent : Component
|
||||
/// List of spells that this book has. This is a combination of the WorldSpells, EntitySpells, and InstantSpells.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly List<ActionType> Spells = new();
|
||||
public readonly List<EntityUid> Spells = new();
|
||||
|
||||
/// <summary>
|
||||
/// The three fields below is just used for initialization.
|
||||
/// </summary>
|
||||
[DataField("worldSpells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, WorldTargetActionPrototype>))]
|
||||
[DataField("spells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, EntityPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> WorldSpells = new();
|
||||
|
||||
[DataField("entitySpells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, EntityTargetActionPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> EntitySpells = new();
|
||||
|
||||
[DataField("instantSpells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, InstantActionPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> InstantSpells = new();
|
||||
public Dictionary<string, int> SpellActions = new();
|
||||
|
||||
[DataField("learnTime")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
|
||||
@@ -4,10 +4,8 @@ using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Server.Magic.Components;
|
||||
using Content.Server.Magic.Events;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.DoAfter;
|
||||
@@ -15,6 +13,7 @@ using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic.Events;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Spawners.Components;
|
||||
@@ -22,11 +21,8 @@ using Content.Shared.Storage;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Exceptions;
|
||||
|
||||
namespace Content.Server.Magic;
|
||||
|
||||
@@ -38,7 +34,6 @@ public sealed class MagicSystem : EntitySystem
|
||||
[Dependency] private readonly ISerializationManager _seriMan = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFact = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DoorBoltSystem _boltsSystem = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
@@ -81,23 +76,9 @@ public sealed class MagicSystem : EntitySystem
|
||||
private void OnInit(EntityUid uid, SpellbookComponent component, ComponentInit args)
|
||||
{
|
||||
//Negative charges means the spell can be used without it running out.
|
||||
foreach (var (id, charges) in component.WorldSpells)
|
||||
foreach (var (id, charges) in component.SpellActions)
|
||||
{
|
||||
var spell = new WorldTargetAction(_prototypeManager.Index<WorldTargetActionPrototype>(id));
|
||||
_actionsSystem.SetCharges(spell, charges < 0 ? null : charges);
|
||||
component.Spells.Add(spell);
|
||||
}
|
||||
|
||||
foreach (var (id, charges) in component.InstantSpells)
|
||||
{
|
||||
var spell = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(id));
|
||||
_actionsSystem.SetCharges(spell, charges < 0 ? null : charges);
|
||||
component.Spells.Add(spell);
|
||||
}
|
||||
|
||||
foreach (var (id, charges) in component.EntitySpells)
|
||||
{
|
||||
var spell = new EntityTargetAction(_prototypeManager.Index<EntityTargetActionPrototype>(id));
|
||||
var spell = Spawn(id);
|
||||
_actionsSystem.SetCharges(spell, charges < 0 ? null : charges);
|
||||
component.Spells.Add(spell);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Threading;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Medical.Components
|
||||
namespace Content.Server.Medical.Stethoscope.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds an innate verb when equipped to use a stethoscope.
|
||||
@@ -15,12 +14,9 @@ namespace Content.Server.Medical.Components
|
||||
[DataField("delay")]
|
||||
public float Delay = 2.5f;
|
||||
|
||||
public EntityTargetAction Action = new()
|
||||
{
|
||||
Icon = new SpriteSpecifier.Texture(new ("Clothing/Neck/Misc/stethoscope.rsi/icon.png")),
|
||||
DisplayName = "stethoscope-verb",
|
||||
Priority = -1,
|
||||
Event = new StethoscopeActionEvent(),
|
||||
};
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Action = "ActionStethoscope";
|
||||
|
||||
[DataField("actionEntity")] public EntityUid? ActionEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Server.Medical.Stethoscope.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Medical;
|
||||
using Content.Shared.Medical.Stethoscope;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Medical;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Medical
|
||||
namespace Content.Server.Medical.Stethoscope
|
||||
{
|
||||
public sealed class StethoscopeSystem : EntitySystem
|
||||
{
|
||||
@@ -97,7 +99,7 @@ namespace Content.Server.Medical
|
||||
|
||||
private void OnGetActions(EntityUid uid, StethoscopeComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.Actions.Add(component.Action);
|
||||
args.AddAction(ref component.ActionEntity, component.Action);
|
||||
}
|
||||
|
||||
// construct the doafter and start it
|
||||
@@ -156,6 +158,4 @@ namespace Content.Server.Medical
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class StethoscopeActionEvent : EntityTargetActionEvent {}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Speech.Muting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Server.Console;
|
||||
@@ -82,24 +82,3 @@ public sealed class CritMobActionsSystem : EntitySystem
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only applies to mobs in crit capable of ghosting/succumbing
|
||||
/// </summary>
|
||||
public sealed partial class CritSuccumbEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only applies/has functionality to mobs in crit that have <see cref="DeathgaspComponent"/>
|
||||
/// </summary>
|
||||
public sealed partial class CritFakeDeathEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only applies to mobs capable of speaking, as a last resort in crit
|
||||
/// </summary>
|
||||
public sealed partial class CritLastWordsEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Polymorph;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
@@ -11,7 +10,7 @@ namespace Content.Server.Polymorph.Components
|
||||
/// A list of all the polymorphs that the entity has.
|
||||
/// Used to manage them and remove them if needed.
|
||||
/// </summary>
|
||||
public Dictionary<string, InstantAction>? PolymorphActions = null;
|
||||
public Dictionary<string, EntityUid>? PolymorphActions = null;
|
||||
|
||||
/// <summary>
|
||||
/// The polymorphs that the entity starts out being able to do.
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Mind.Commands;
|
||||
using Content.Server.Nutrition;
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Buckle;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
@@ -46,6 +45,8 @@ namespace Content.Server.Polymorph.Systems
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private const string RevertPolymorphId = "ActionRevertPolymorph";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -97,23 +98,21 @@ namespace Content.Server.Polymorph.Systems
|
||||
if (proto.Forced)
|
||||
return;
|
||||
|
||||
var act = new InstantAction
|
||||
var actionId = Spawn(RevertPolymorphId);
|
||||
if (_actions.TryGetActionData(actionId, out var action))
|
||||
{
|
||||
Event = new RevertPolymorphActionEvent(),
|
||||
EntityIcon = component.Parent,
|
||||
DisplayName = Loc.GetString("polymorph-revert-action-name"),
|
||||
Description = Loc.GetString("polymorph-revert-action-description"),
|
||||
UseDelay = TimeSpan.FromSeconds(proto.Delay),
|
||||
};
|
||||
action.EntityIcon = component.Parent;
|
||||
action.UseDelay = TimeSpan.FromSeconds(proto.Delay);
|
||||
}
|
||||
|
||||
_actions.AddAction(uid, act, null);
|
||||
_actions.AddAction(uid, actionId, null, null, action);
|
||||
}
|
||||
|
||||
private void OnBeforeFullyEaten(EntityUid uid, PolymorphedEntityComponent comp, BeforeFullyEatenEvent args)
|
||||
{
|
||||
if (!_proto.TryIndex<PolymorphPrototype>(comp.Prototype, out var proto))
|
||||
{
|
||||
_sawmill.Error("Invalid polymorph prototype {comp.Prototype}");
|
||||
_sawmill.Error($"Invalid polymorph prototype {comp.Prototype}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -334,23 +333,19 @@ namespace Content.Server.Polymorph.Systems
|
||||
return;
|
||||
|
||||
var entproto = _proto.Index<EntityPrototype>(polyproto.Entity);
|
||||
|
||||
var act = new InstantAction
|
||||
var actionId = Spawn(RevertPolymorphId);
|
||||
if (_actions.TryGetActionData(actionId, out var baseAction) &&
|
||||
baseAction is InstantActionComponent action)
|
||||
{
|
||||
Event = new PolymorphActionEvent
|
||||
{
|
||||
Prototype = polyproto,
|
||||
},
|
||||
DisplayName = Loc.GetString("polymorph-self-action-name", ("target", entproto.Name)),
|
||||
Description = Loc.GetString("polymorph-self-action-description", ("target", entproto.Name)),
|
||||
Icon = new SpriteSpecifier.EntityPrototype(polyproto.Entity),
|
||||
ItemIconStyle = ItemActionIconStyle.NoItem,
|
||||
};
|
||||
action.Event = new PolymorphActionEvent { Prototype = polyproto };
|
||||
action.Icon = new SpriteSpecifier.EntityPrototype(polyproto.Entity);
|
||||
_metaData.SetEntityName(actionId, Loc.GetString("polymorph-self-action-name", ("target", entproto.Name)));
|
||||
_metaData.SetEntityDescription(actionId, Loc.GetString("polymorph-self-action-description", ("target", entproto.Name)));
|
||||
|
||||
polycomp.PolymorphActions ??= new();
|
||||
|
||||
polycomp.PolymorphActions.Add(id, act);
|
||||
_actions.AddAction(target, act, target);
|
||||
polycomp.PolymorphActions ??= new Dictionary<string, EntityUid>();
|
||||
polycomp.PolymorphActions.Add(id, actionId);
|
||||
_actions.AddAction(target, actionId, target);
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -399,18 +394,4 @@ namespace Content.Server.Polymorph.Systems
|
||||
UpdateCollide();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class PolymorphActionEvent : InstantActionEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The polymorph prototype containing all the information about
|
||||
/// the specific polymorph.
|
||||
/// </summary>
|
||||
public PolymorphPrototype Prototype = default!;
|
||||
}
|
||||
|
||||
public sealed partial class RevertPolymorphActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
@@ -7,11 +6,13 @@ namespace Content.Server.RatKing
|
||||
[RegisterComponent]
|
||||
public sealed partial class RatKingComponent : Component
|
||||
{
|
||||
[DataField("actionRaiseArmy", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ActionRaiseArmy = "ActionRatKingRaiseArmy";
|
||||
|
||||
/// <summary>
|
||||
/// The action for the Raise Army ability
|
||||
/// </summary>
|
||||
[DataField("actionRaiseArmy", required: true)]
|
||||
public InstantAction ActionRaiseArmy = new();
|
||||
[DataField("actionRaiseArmyEntity")] public EntityUid? ActionRaiseArmyEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of hunger one use of Raise Army consumes
|
||||
@@ -25,11 +26,14 @@ namespace Content.Server.RatKing
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("armyMobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ArmyMobSpawnId = "MobRatServant";
|
||||
|
||||
[DataField("actionDomain", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ActionDomain = "ActionRatKingDomain";
|
||||
|
||||
/// <summary>
|
||||
/// The action for the Domain ability
|
||||
/// </summary>
|
||||
[DataField("actionDomain", required: true)]
|
||||
public InstantAction ActionDomain = new();
|
||||
[DataField("actionDomainEntity")]
|
||||
public EntityUid? ActionDomainEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of hunger one use of Domain consumes
|
||||
@@ -43,4 +47,4 @@ namespace Content.Server.RatKing
|
||||
[DataField("molesMiasmaPerDomain")]
|
||||
public float MolesMiasmaPerDomain = 100f;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.RatKing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.RatKing
|
||||
{
|
||||
@@ -23,16 +21,16 @@ namespace Content.Server.RatKing
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RatKingComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<RatKingComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<RatKingComponent, RatKingRaiseArmyActionEvent>(OnRaiseArmy);
|
||||
SubscribeLocalEvent<RatKingComponent, RatKingDomainActionEvent>(OnDomain);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, RatKingComponent component, ComponentStartup args)
|
||||
private void OnMapInit(EntityUid uid, RatKingComponent component, MapInitEvent args)
|
||||
{
|
||||
_action.AddAction(uid, component.ActionRaiseArmy, null);
|
||||
_action.AddAction(uid, component.ActionDomain, null);
|
||||
_action.AddAction(uid, ref component.ActionRaiseArmyEntity, component.ActionRaiseArmy);
|
||||
_action.AddAction(uid, ref component.ActionDomainEntity, component.ActionDomain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,14 +84,4 @@ namespace Content.Server.RatKing
|
||||
tileMix?.AdjustMoles(Gas.Miasma, component.MolesMiasmaPerDomain);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class RatKingRaiseArmyActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class RatKingDomainActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Actions;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Revenant;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Server.Visible;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Server.Visible;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Revenant;
|
||||
using Content.Shared.Revenant.Components;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Revenant.EntitySystems;
|
||||
|
||||
public sealed partial class RevenantSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ActionsSystem _action = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
@@ -46,11 +44,15 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
[Dependency] private readonly VisibilitySystem _visibility = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string RevenantShopId = "ActionRevenantShop";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RevenantComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<RevenantComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<RevenantComponent, RevenantShopActionEvent>(OnShop);
|
||||
SubscribeLocalEvent<RevenantComponent, DamageChangedEvent>(OnDamage);
|
||||
@@ -82,9 +84,11 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
//ghost vision
|
||||
if (TryComp(uid, out EyeComponent? eye))
|
||||
eye.VisibilityMask |= (uint) (VisibilityFlags.Ghost);
|
||||
}
|
||||
|
||||
var shopaction = new InstantAction(_proto.Index<InstantActionPrototype>("RevenantShop"));
|
||||
_action.AddAction(uid, shopaction, null);
|
||||
private void OnMapInit(EntityUid uid, RevenantComponent component, MapInitEvent args)
|
||||
{
|
||||
_action.AddAction(uid, Spawn(RevenantShopId), null);
|
||||
}
|
||||
|
||||
private void OnStatusAdded(EntityUid uid, RevenantComponent component, StatusEffectAddedEvent args)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Sericulture;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class SericultureComponent : Component
|
||||
{
|
||||
|
||||
[DataField("popupText")]
|
||||
public string PopupText = "sericulture-failure-hunger";
|
||||
|
||||
@@ -13,8 +15,10 @@ public sealed partial class SericultureComponent : Component
|
||||
[DataField("entityProduced", required: true)]
|
||||
public string EntityProduced = "";
|
||||
|
||||
[DataField("actionProto", required: true)]
|
||||
public string ActionProto = "";
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Action = "ActionSericulture";
|
||||
|
||||
[DataField("actionEntity")] public EntityUid? ActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// How long will it take to make.
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Sericulture;
|
||||
using Content.Server.Stack;
|
||||
|
||||
namespace Content.Server.Sericulture;
|
||||
|
||||
@@ -16,7 +13,6 @@ public sealed partial class SericultureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly HungerSystem _hungerSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
@@ -25,26 +21,20 @@ public sealed partial class SericultureSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SericultureComponent, ComponentInit>(OnCompInit);
|
||||
SubscribeLocalEvent<SericultureComponent, MapInitEvent>(OnCompMapInit);
|
||||
SubscribeLocalEvent<SericultureComponent, ComponentShutdown>(OnCompRemove);
|
||||
SubscribeLocalEvent<SericultureComponent, SericultureActionEvent>(OnSericultureStart);
|
||||
SubscribeLocalEvent<SericultureComponent, SericultureDoAfterEvent>(OnSericultureDoAfter);
|
||||
}
|
||||
|
||||
private void OnCompInit(EntityUid uid, SericultureComponent comp, ComponentInit args)
|
||||
private void OnCompMapInit(EntityUid uid, SericultureComponent comp, MapInitEvent args)
|
||||
{
|
||||
if (!_protoManager.TryIndex<InstantActionPrototype>(comp.ActionProto, out var actionProto))
|
||||
return;
|
||||
|
||||
_actionsSystem.AddAction(uid, new InstantAction(actionProto), uid);
|
||||
_actionsSystem.AddAction(uid, ref comp.ActionEntity, comp.Action, uid);
|
||||
}
|
||||
|
||||
private void OnCompRemove(EntityUid uid, SericultureComponent comp, ComponentShutdown args)
|
||||
{
|
||||
if (!_protoManager.TryIndex<InstantActionPrototype>(comp.ActionProto, out var actionProto))
|
||||
return;
|
||||
|
||||
_actionsSystem.RemoveAction(uid, new InstantAction(actionProto));
|
||||
_actionsSystem.RemoveAction(uid, comp.ActionEntity);
|
||||
}
|
||||
|
||||
private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args)
|
||||
@@ -96,6 +86,4 @@ public sealed partial class SericultureSystem : EntitySystem
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public sealed partial class SericultureActionEvent : InstantActionEvent { }
|
||||
}
|
||||
|
||||
@@ -57,8 +57,14 @@ public sealed partial class BorgSystem
|
||||
private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
component.ModuleSwapAction.EntityIcon = uid;
|
||||
_actions.AddAction(chassis, component.ModuleSwapAction, uid);
|
||||
|
||||
var action = _actions.AddAction(chassis, ref component.ModuleSwapActionEntity, component.ModuleSwapActionId, uid);
|
||||
if (action != null)
|
||||
{
|
||||
action.EntityIcon = uid;
|
||||
Dirty(component.ModuleSwapActionEntity!.Value, action);
|
||||
}
|
||||
|
||||
SelectModule(chassis, uid, moduleComp: component);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Radio.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Emag.Components;
|
||||
@@ -43,7 +42,6 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SiliconLawBoundComponent, MindAddedMessage>(OnMindAdded);
|
||||
@@ -58,20 +56,15 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnComponentStartup(EntityUid uid, SiliconLawBoundComponent component, ComponentStartup args)
|
||||
{
|
||||
component.ProvidedAction = new(_prototype.Index<InstantActionPrototype>(component.ViewLawsAction));
|
||||
_actions.AddAction(uid, component.ProvidedAction, null);
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(EntityUid uid, SiliconLawBoundComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.ProvidedAction != null)
|
||||
_actions.RemoveAction(uid, component.ProvidedAction);
|
||||
if (component.ViewLawsActionEntity != null)
|
||||
_actions.RemoveAction(uid, component.ViewLawsActionEntity);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args)
|
||||
{
|
||||
_actions.AddAction(uid, ref component.ViewLawsActionEntity, component.ViewLawsAction);
|
||||
GetLaws(uid, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
@@ -33,11 +31,11 @@ public sealed partial class VocalComponent : Component
|
||||
[DataField("wilhelmProbability")]
|
||||
public float WilhelmProbability = 0.0002f;
|
||||
|
||||
[DataField("screamActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ScreamActionId = "Scream";
|
||||
[DataField("screamAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ScreamAction = "ActionScream";
|
||||
|
||||
[DataField("screamAction")]
|
||||
public InstantAction? ScreamAction;
|
||||
[DataField("screamActionEntity")]
|
||||
public EntityUid? ScreamActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Currently loaded emote sounds prototype, based on entity sex.
|
||||
@@ -46,8 +44,3 @@ public sealed partial class VocalComponent : Component
|
||||
[ViewVariables]
|
||||
public EmoteSoundsPrototype? EmoteSounds = null;
|
||||
}
|
||||
|
||||
public sealed partial class ScreamActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Speech.Components;
|
||||
using Content.Shared.Speech.EntitySystems;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Speech.EntitySystems;
|
||||
@@ -18,17 +18,15 @@ public sealed class MeleeSpeechSystem : SharedMeleeSpeechSystem
|
||||
SubscribeLocalEvent<MeleeSpeechComponent, MeleeSpeechBattlecryChangedMessage>(OnBattlecryChanged);
|
||||
SubscribeLocalEvent<MeleeSpeechComponent, MeleeSpeechConfigureActionEvent>(OnConfigureAction);
|
||||
SubscribeLocalEvent<MeleeSpeechComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<MeleeSpeechComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<MeleeSpeechComponent, MapInitEvent>(OnComponentMapInit);
|
||||
}
|
||||
private void OnComponentInit(EntityUid uid, MeleeSpeechComponent component, ComponentInit args)
|
||||
private void OnComponentMapInit(EntityUid uid, MeleeSpeechComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.ConfigureAction != null)
|
||||
_actionSystem.AddAction(uid, component.ConfigureAction, uid);
|
||||
_actionSystem.AddAction(uid, ref component.ConfigureActionEntity, component.ConfigureAction, uid);
|
||||
}
|
||||
private void OnGetActions(EntityUid uid, MeleeSpeechComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
if (component.ConfigureAction != null)
|
||||
args.Actions.Add(component.ConfigureAction);
|
||||
args.AddAction(ref component.ConfigureActionEntity, component.ConfigureAction);
|
||||
}
|
||||
private void OnBattlecryChanged(EntityUid uid, MeleeSpeechComponent comp, MeleeSpeechBattlecryChangedMessage args)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -32,21 +31,16 @@ public sealed class VocalSystem : EntitySystem
|
||||
private void OnMapInit(EntityUid uid, VocalComponent component, MapInitEvent args)
|
||||
{
|
||||
// try to add scream action when vocal comp added
|
||||
if (_proto.TryIndex(component.ScreamActionId, out InstantActionPrototype? proto))
|
||||
{
|
||||
component.ScreamAction = new InstantAction(proto);
|
||||
_actions.AddAction(uid, component.ScreamAction, null);
|
||||
}
|
||||
|
||||
_actions.AddAction(uid, ref component.ScreamActionEntity, component.ScreamAction);
|
||||
LoadSounds(uid, component);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args)
|
||||
{
|
||||
// remove scream action when component removed
|
||||
if (component.ScreamAction != null)
|
||||
if (component.ScreamActionEntity != null)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.ScreamAction);
|
||||
_actions.RemoveAction(uid, component.ScreamActionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +70,7 @@ public sealed class VocalSystem : EntitySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_chat.TryEmoteWithChat(uid, component.ScreamActionId);
|
||||
_chat.TryEmoteWithChat(uid, component.ScreamId);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.PDA.Ringer;
|
||||
using Content.Server.Stack;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.GameObjects;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
@@ -162,10 +161,9 @@ public sealed partial class StoreSystem
|
||||
}
|
||||
|
||||
//give action
|
||||
if (listing.ProductAction != null)
|
||||
if (!string.IsNullOrWhiteSpace(listing.ProductAction))
|
||||
{
|
||||
var action = new InstantAction(_proto.Index<InstantActionPrototype>(listing.ProductAction));
|
||||
_actions.AddAction(buyer, action, null);
|
||||
_actions.AddAction(buyer, Spawn(listing.ProductAction), null);
|
||||
}
|
||||
|
||||
//broadcast event
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
|
||||
@@ -1,57 +1,32 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.UserInterface;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class IntrinsicUIComponent : Component, ISerializationHooks
|
||||
public sealed partial class IntrinsicUIComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of UIs and their actions that this entity has.
|
||||
/// </summary>
|
||||
[DataField("uis", required: true)]
|
||||
public List<IntrinsicUIEntry> UIs = new();
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
for (var i = 0; i < UIs.Count; i++)
|
||||
{
|
||||
var ui = UIs[i];
|
||||
ui.AfterDeserialization();
|
||||
UIs[i] = ui;
|
||||
}
|
||||
}
|
||||
[DataField("uis", required: true)] public List<IntrinsicUIEntry> UIs = new();
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public partial struct IntrinsicUIEntry
|
||||
public partial class IntrinsicUIEntry
|
||||
{
|
||||
[ViewVariables] public Enum? Key { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// The BUI key that this intrinsic UI should open.
|
||||
/// </summary>
|
||||
[DataField("key", required: true)]
|
||||
private string _keyRaw = default!;
|
||||
public Enum? Key { get; private set; }
|
||||
|
||||
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
|
||||
public string? ToggleAction;
|
||||
|
||||
/// <summary>
|
||||
/// The action used for this BUI.
|
||||
/// </summary>
|
||||
[DataField("toggleAction", required: true)]
|
||||
public InstantAction ToggleAction = new();
|
||||
|
||||
public void AfterDeserialization()
|
||||
{
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
if (reflectionManager.TryParseEnumReference(_keyRaw, out var key))
|
||||
Key = key;
|
||||
|
||||
if (ToggleAction.Event is ToggleIntrinsicUIEvent ev)
|
||||
{
|
||||
ev.Key = Key;
|
||||
}
|
||||
}
|
||||
|
||||
public IntrinsicUIEntry() {}
|
||||
[DataField("toggleActionEntity")]
|
||||
public EntityUid? ToggleActionEntity = new();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Shared.Actions;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.UserInterface;
|
||||
@@ -28,7 +28,7 @@ public sealed class IntrinsicUISystem : EntitySystem
|
||||
|
||||
foreach (var entry in component.UIs)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, entry.ToggleAction, null, actions);
|
||||
_actionsSystem.AddAction(uid, ref entry.ToggleActionEntity, entry.ToggleAction, null, actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,13 +68,6 @@ public sealed class IntrinsicUISystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ToggleIntrinsicUIEvent : InstantActionEvent
|
||||
{
|
||||
[ViewVariables]
|
||||
public Enum? Key { get; set; }
|
||||
}
|
||||
|
||||
// Competing with ActivatableUI for horrible event names.
|
||||
public sealed class IntrinsicUIOpenAttemptEvent : CancellableEntityEventArgs
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using Content.Server.UserInterface;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DoAfter;
|
||||
@@ -44,6 +43,7 @@ namespace Content.Server.VendingMachines
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = Logger.GetSawmill("vending");
|
||||
SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnComponentMapInit);
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
@@ -62,6 +62,12 @@ namespace Content.Server.VendingMachines
|
||||
SubscribeLocalEvent<VendingMachineRestockComponent, PriceCalculationEvent>(OnPriceCalculation);
|
||||
}
|
||||
|
||||
private void OnComponentMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
|
||||
{
|
||||
_action.AddAction(uid, ref component.ActionEntity, component.Action, uid);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
var price = 0.0;
|
||||
@@ -88,12 +94,6 @@ namespace Content.Server.VendingMachines
|
||||
{
|
||||
TryUpdateVisualState(uid, component);
|
||||
}
|
||||
|
||||
if (component.Action != null)
|
||||
{
|
||||
var action = new InstantAction(PrototypeManager.Index<InstantActionPrototype>(component.Action));
|
||||
_action.AddAction(uid, action, uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActivatableUIOpenAttempt(EntityUid uid, VendingMachineComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.VoiceMask;
|
||||
|
||||
@@ -12,7 +9,6 @@ public sealed partial class VoiceMaskSystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private const string MaskSlot = "mask";
|
||||
|
||||
@@ -26,12 +22,7 @@ public sealed partial class VoiceMaskSystem
|
||||
var comp = EnsureComp<VoiceMaskComponent>(user);
|
||||
comp.VoiceName = component.LastSetName;
|
||||
|
||||
if (!_prototypeManager.TryIndex<InstantActionPrototype>(component.Action, out var action))
|
||||
{
|
||||
throw new ArgumentException("Could not get voice masking prototype.");
|
||||
}
|
||||
|
||||
_actions.AddAction(user, (InstantAction) action.Clone(), uid);
|
||||
_actions.AddAction(user, ref component.ActionEntity, component.Action, uid);
|
||||
}
|
||||
|
||||
private void OnUnequip(EntityUid uid, VoiceMaskerComponent compnent, GotUnequippedEvent args)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Preferences;
|
||||
@@ -88,7 +87,3 @@ public sealed partial class VoiceMaskSystem : EntitySystem
|
||||
UserInterfaceSystem.SetUiState(bui, new VoiceMaskBuiState(component.VoiceName));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class VoiceMaskSetNameEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.VoiceMask;
|
||||
@@ -8,6 +8,8 @@ public sealed partial class VoiceMaskerComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] public string LastSetName = "Unknown";
|
||||
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string Action = "ChangeVoiceMask";
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Action = "ActionChangeVoiceMask";
|
||||
|
||||
[DataField("actionEntity")] public EntityUid? ActionEntity;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -12,32 +10,29 @@ public partial class ArtifactSystem
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] private const string ArtifactActivateActionId = "ActionArtifactActivate";
|
||||
|
||||
/// <summary>
|
||||
/// Used to add the artifact activation action (hehe), which lets sentient artifacts activate themselves,
|
||||
/// either through admemery or the sentience effect.
|
||||
/// </summary>
|
||||
public void InitializeActions()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ArtifactComponent, MapInitEvent>(OnStartup);
|
||||
SubscribeLocalEvent<ArtifactComponent, ComponentRemove>(OnRemove);
|
||||
|
||||
SubscribeLocalEvent<ArtifactComponent, ArtifactSelfActivateEvent>(OnSelfActivate);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, ArtifactComponent component, ComponentStartup args)
|
||||
private void OnStartup(EntityUid uid, ArtifactComponent component, MapInitEvent args)
|
||||
{
|
||||
if (_prototype.TryIndex<InstantActionPrototype>("ArtifactActivate", out var proto))
|
||||
{
|
||||
_actions.AddAction(uid, new InstantAction(proto), null);
|
||||
}
|
||||
RandomizeArtifact(uid, component);
|
||||
_actions.AddAction(uid, Spawn(ArtifactActivateActionId), null);
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, ArtifactComponent component, ComponentRemove args)
|
||||
{
|
||||
if (_prototype.TryIndex<InstantActionPrototype>("ArtifactActivate", out var proto))
|
||||
{
|
||||
_actions.RemoveAction(uid, new InstantAction(proto));
|
||||
}
|
||||
_actions.RemoveAction(uid, ArtifactActivateActionId);
|
||||
}
|
||||
|
||||
private void OnSelfActivate(EntityUid uid, ArtifactComponent component, ArtifactSelfActivateEvent args)
|
||||
@@ -52,4 +47,3 @@ public partial class ArtifactSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Configuration;
|
||||
using Content.Shared.CCVar;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
|
||||
@@ -30,7 +30,6 @@ public sealed partial class ArtifactSystem : EntitySystem
|
||||
|
||||
_sawmill = Logger.GetSawmill("artifact");
|
||||
|
||||
SubscribeLocalEvent<ArtifactComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<ArtifactComponent, PriceCalculationEvent>(GetPrice);
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
|
||||
@@ -38,11 +37,6 @@ public sealed partial class ArtifactSystem : EntitySystem
|
||||
InitializeActions();
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, ArtifactComponent component, MapInitEvent args)
|
||||
{
|
||||
RandomizeArtifact(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the price of an artifact based on
|
||||
/// how many nodes have been unlocked/triggered
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Magic.Events;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
||||
using Content.Shared.Magic.Events;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
@@ -18,7 +18,9 @@ namespace Content.Shared.Actions;
|
||||
/// </remarks>
|
||||
public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
{
|
||||
public SortedSet<ActionType> Actions = new();
|
||||
private readonly IEntityManager _entities;
|
||||
private readonly INetManager _net;
|
||||
public readonly SortedSet<EntityUid> Actions = new();
|
||||
|
||||
/// <summary>
|
||||
/// User equipping the item.
|
||||
@@ -35,11 +37,26 @@ public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
/// </summary>
|
||||
public bool InHands => SlotFlags == null;
|
||||
|
||||
public GetItemActionsEvent(EntityUid user, SlotFlags? slotFlags = null)
|
||||
public GetItemActionsEvent(IEntityManager entities, INetManager net, EntityUid user, SlotFlags? slotFlags = null)
|
||||
{
|
||||
_entities = entities;
|
||||
_net = net;
|
||||
User = user;
|
||||
SlotFlags = slotFlags;
|
||||
}
|
||||
|
||||
public void AddAction(ref EntityUid? actionId, string? prototypeId)
|
||||
{
|
||||
if (_entities.Deleted(actionId))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prototypeId) || _net.IsClient)
|
||||
return;
|
||||
|
||||
actionId = _entities.Spawn(prototypeId);
|
||||
}
|
||||
|
||||
Actions.Add(actionId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,22 +65,22 @@ public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RequestPerformActionEvent : EntityEventArgs
|
||||
{
|
||||
public readonly ActionType Action;
|
||||
public readonly EntityUid Action;
|
||||
public readonly EntityUid? EntityTarget;
|
||||
public readonly EntityCoordinates? EntityCoordinatesTarget;
|
||||
|
||||
public RequestPerformActionEvent(InstantAction action)
|
||||
public RequestPerformActionEvent(EntityUid action)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public RequestPerformActionEvent(EntityTargetAction action, EntityUid entityTarget)
|
||||
public RequestPerformActionEvent(EntityUid action, EntityUid entityTarget)
|
||||
{
|
||||
Action = action;
|
||||
EntityTarget = entityTarget;
|
||||
}
|
||||
|
||||
public RequestPerformActionEvent(WorldTargetAction action, EntityCoordinates entityCoordinatesTarget)
|
||||
public RequestPerformActionEvent(EntityUid action, EntityCoordinates entityCoordinatesTarget)
|
||||
{
|
||||
Action = action;
|
||||
EntityCoordinatesTarget = entityCoordinatesTarget;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Actions.ActionTypes;
|
||||
|
||||
// These are just prototype definitions for actions. Allows actions to be defined once in yaml and re-used elsewhere.
|
||||
// Note that you still need to create a new instance of each action to properly track the state (cooldown, toggled,
|
||||
// enabled, etc). The prototypes should not be modified directly.
|
||||
//
|
||||
// If ever action states data is separated from the rest of the data, this might not be required
|
||||
// anymore.
|
||||
|
||||
[Prototype("worldTargetAction")]
|
||||
public sealed partial class WorldTargetActionPrototype : WorldTargetAction, IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
// This is a shitty hack to get around the fact that action-prototypes should not in general be sever-exclusive
|
||||
// prototypes, but some actions may need to use server-exclusive events, and there is no way to specify on a
|
||||
// per-prototype basis whether the client should ignore it when validating yaml.
|
||||
[DataField("serverEvent", serverOnly: true)]
|
||||
public WorldTargetActionEvent? ServerEvent
|
||||
{
|
||||
get => Event;
|
||||
set => Event = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Prototype("entityTargetAction")]
|
||||
public sealed partial class EntityTargetActionPrototype : EntityTargetAction, IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("serverEvent", serverOnly: true)]
|
||||
public EntityTargetActionEvent? ServerEvent
|
||||
{
|
||||
get => Event;
|
||||
set => Event = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Prototype("instantAction")]
|
||||
public sealed partial class InstantActionPrototype : InstantAction, IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("serverEvent", serverOnly: true)]
|
||||
public InstantActionEvent? ServerEvent
|
||||
{
|
||||
get => Event;
|
||||
set => Event = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Actions.ActionTypes;
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
[Serializable, NetSerializable]
|
||||
public abstract partial class ActionType : IEquatable<ActionType>, IComparable, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Icon representing this action in the UI.
|
||||
/// </summary>
|
||||
[DataField("icon")]
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this color will modulate the action icon color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Name to show in UI.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string DisplayName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// This is just <see cref="DisplayName"/> with localized strings resolved and markup removed. If null, will be
|
||||
/// inferred from <see cref="DisplayName"/>. This is cached to speed up game state handling.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public string? RawName;
|
||||
|
||||
/// <summary>
|
||||
/// Description to show in UI. Accepts formatting.
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Keywords that can be used to search for this action in the action menu.
|
||||
/// </summary>
|
||||
[DataField("keywords")]
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// The toggle state of this action. Toggling switches the currently displayed icon, see <see cref="Icon"/> and <see cref="IconOn"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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>
|
||||
public bool Toggled;
|
||||
|
||||
/// <summary>
|
||||
/// The current cooldown on the action.
|
||||
/// </summary>
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Time interval between action uses.
|
||||
/// </summary>
|
||||
[DataField("useDelay")]
|
||||
public TimeSpan? UseDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience tool for actions with limited number of charges. Automatically decremented on use, and the
|
||||
/// action is disabled when it reaches zero. Does NOT automatically remove the action from the action bar.
|
||||
/// </summary>
|
||||
[DataField("charges")]
|
||||
public int? Charges;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that enables / provides this action. If the action is innate, this may be the user themselves. If
|
||||
/// this action has no provider (e.g., mapping tools), the this will result in broadcast events.
|
||||
/// </summary>
|
||||
public EntityUid? Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Entity to use for the action icon. Defaults to using <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
public EntityUid? EntityIcon
|
||||
{
|
||||
get => _entityIcon ?? Provider;
|
||||
set => _entityIcon = value;
|
||||
}
|
||||
|
||||
private EntityUid? _entityIcon;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will simply execute the action locally without sending to the server.
|
||||
/// </summary>
|
||||
[DataField("clientExclusive")]
|
||||
public bool ClientExclusive = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the order in which actions are automatically added the action bar.
|
||||
/// </summary>
|
||||
[DataField("priority")]
|
||||
public int Priority = 0;
|
||||
|
||||
/// <summary>
|
||||
/// What entity, if any, currently has this action in the actions component?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? AttachedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically add this action to the action bar when it becomes available.
|
||||
/// </summary>
|
||||
[DataField("autoPopulate")]
|
||||
public bool AutoPopulate = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically remove this action to the action bar when it becomes unavailable.
|
||||
/// </summary>
|
||||
[DataField("autoRemove")]
|
||||
public bool AutoRemove = true;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary actions are removed from the action component when removed from the action-bar/GUI. Currently,
|
||||
/// should only be used for client-exclusive actions (server is not notified).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently there is no way for a player to just voluntarily remove actions. They can hide them from the
|
||||
/// toolbar, but not actually remove them. This is undesirable for things like dynamically added mapping
|
||||
/// entity-selection actions, as the # of actions would just keep increasing.
|
||||
/// </remarks>
|
||||
[DataField("temporary")]
|
||||
public bool Temporary;
|
||||
// TODO re-add support for this
|
||||
// UI refactor seems to have just broken it.
|
||||
|
||||
/// <summary>
|
||||
/// Determines the appearance of the entity-icon for actions that are enabled via some entity.
|
||||
/// </summary>
|
||||
[DataField("itemIconStyle")]
|
||||
public ItemActionIconStyle ItemIconStyle;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this sound will be played when performing this action.
|
||||
/// </summary>
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier? Sound;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two actions based on their properties. This is used to determine equality when the client requests the
|
||||
/// server to perform some action. Also determines the order in which actions are automatically added to the action bar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Basically: if an action has the same priority, name, and is enabled by the same entity, then the actions are considered equal.
|
||||
/// The entity-check is required to avoid toggling all flashlights simultaneously whenever a flashlight-hoarder uses an action.
|
||||
/// </remarks>
|
||||
public virtual int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is not ActionType otherAction)
|
||||
return -1;
|
||||
|
||||
if (Priority != otherAction.Priority)
|
||||
return otherAction.Priority - Priority;
|
||||
|
||||
RawName ??= FormattedMessage.RemoveMarkup(Loc.GetString(DisplayName));
|
||||
otherAction.RawName ??= FormattedMessage.RemoveMarkup(Loc.GetString(otherAction.DisplayName));
|
||||
var cmp = string.Compare(RawName, otherAction.RawName, StringComparison.CurrentCulture);
|
||||
if (cmp != 0)
|
||||
return cmp;
|
||||
|
||||
if (Provider != otherAction.Provider)
|
||||
{
|
||||
if (Provider == null)
|
||||
return -1;
|
||||
|
||||
if (otherAction.Provider == null)
|
||||
return 1;
|
||||
|
||||
// uid to int casting... it says "Do NOT use this in content". You can't tell me what to do.
|
||||
return (int) Provider - (int) otherAction.Provider;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proper client-side state handling requires the ability to clone an action from the component state.
|
||||
/// Otherwise modifying the action can lead to modifying the stored server state.
|
||||
/// </summary>
|
||||
public abstract object Clone();
|
||||
|
||||
public virtual void CopyFrom(object objectToClone)
|
||||
{
|
||||
if (objectToClone is not ActionType toClone)
|
||||
return;
|
||||
|
||||
// This is pretty Ugly to look at. But actions are sent to the client in a component state, so they have to be
|
||||
// cloneable. Would be easy if this were a struct of only value-types, but I don't want to restrict actions like
|
||||
// that.
|
||||
Priority = toClone.Priority;
|
||||
Icon = toClone.Icon;
|
||||
IconOn = toClone.IconOn;
|
||||
DisplayName = toClone.DisplayName;
|
||||
RawName = null;
|
||||
Description = toClone.Description;
|
||||
Provider = toClone.Provider;
|
||||
AttachedEntity = toClone.AttachedEntity;
|
||||
Enabled = toClone.Enabled;
|
||||
Toggled = toClone.Toggled;
|
||||
Cooldown = toClone.Cooldown;
|
||||
Charges = toClone.Charges;
|
||||
Keywords = new(toClone.Keywords);
|
||||
AutoPopulate = toClone.AutoPopulate;
|
||||
AutoRemove = toClone.AutoRemove;
|
||||
ItemIconStyle = toClone.ItemIconStyle;
|
||||
CheckCanInteract = toClone.CheckCanInteract;
|
||||
UseDelay = toClone.UseDelay;
|
||||
Sound = toClone.Sound;
|
||||
ItemIconStyle = toClone.ItemIconStyle;
|
||||
_entityIcon = toClone._entityIcon;
|
||||
}
|
||||
|
||||
public bool Equals(ActionType? other)
|
||||
{
|
||||
return CompareTo(other) == 0;
|
||||
}
|
||||
|
||||
public static bool operator ==(ActionType? left, ActionType? right)
|
||||
{
|
||||
if (left is null)
|
||||
return right is null;
|
||||
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ActionType? left, ActionType? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = Priority.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ DisplayName.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Provider.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions.ActionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Instantaneous action with no extra targeting information. Will result in <see cref="InstantActionEvent"/> being raised.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual]
|
||||
public partial class InstantAction : ActionType
|
||||
{
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public InstantActionEvent? Event;
|
||||
|
||||
public InstantAction() { }
|
||||
public InstantAction(InstantAction toClone)
|
||||
{
|
||||
CopyFrom(toClone);
|
||||
}
|
||||
|
||||
public override void CopyFrom(object objectToClone)
|
||||
{
|
||||
base.CopyFrom(objectToClone);
|
||||
|
||||
// Server doesn't serialize events to us.
|
||||
// As such we don't want them to bulldoze any events we may have gotten locally.
|
||||
if (objectToClone is not InstantAction toClone)
|
||||
return;
|
||||
|
||||
// Events should be re-usable, and shouldn't be modified during prediction.
|
||||
if (toClone.Event != null)
|
||||
Event = toClone.Event;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new InstantAction(this);
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions.ActionTypes;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public abstract partial class TargetedAction : ActionType
|
||||
{
|
||||
/// <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;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the action system should block this action if the user cannot actually access the target
|
||||
/// (unobstructed, in inventory, in backpack, etc). Some spells or abilities may want to disable this and
|
||||
/// implement their own checks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Even if this is false, the <see cref="Range"/> will still be checked.
|
||||
/// </remarks>
|
||||
[DataField("checkCanAccess")]
|
||||
public bool CheckCanAccess = true;
|
||||
|
||||
[DataField("range")]
|
||||
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
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Interactions will still be blocked if the target-validation generates a pop-up
|
||||
/// </remarks>
|
||||
[DataField("interactOnMiss")]
|
||||
public bool InteractOnMiss = false;
|
||||
|
||||
/// <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;
|
||||
|
||||
public override void CopyFrom(object objectToClone)
|
||||
{
|
||||
base.CopyFrom(objectToClone);
|
||||
|
||||
if (objectToClone is not TargetedAction toClone)
|
||||
return;
|
||||
|
||||
Range = toClone.Range;
|
||||
CheckCanAccess = toClone.CheckCanAccess;
|
||||
DeselectOnMiss = toClone.DeselectOnMiss;
|
||||
Repeat = toClone.Repeat;
|
||||
InteractOnMiss = toClone.InteractOnMiss;
|
||||
TargetingIndicator = toClone.TargetingIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that targets some entity. Will result in <see cref="EntityTargetActionEvent"/> being raised.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual]
|
||||
public partial class EntityTargetAction : TargetedAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
[DataField("event")]
|
||||
public EntityTargetActionEvent? Event;
|
||||
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
[DataField("canTargetSelf")]
|
||||
public bool CanTargetSelf = true;
|
||||
|
||||
public EntityTargetAction() { }
|
||||
public EntityTargetAction(EntityTargetAction toClone)
|
||||
{
|
||||
CopyFrom(toClone);
|
||||
}
|
||||
public override void CopyFrom(object objectToClone)
|
||||
{
|
||||
base.CopyFrom(objectToClone);
|
||||
|
||||
if (objectToClone is not EntityTargetAction toClone)
|
||||
return;
|
||||
|
||||
CanTargetSelf = toClone.CanTargetSelf;
|
||||
|
||||
// This isn't a deep copy, but I don't expect white-lists to ever be edited during prediction. So good enough?
|
||||
Whitelist = toClone.Whitelist;
|
||||
|
||||
// Events should be re-usable, and shouldn't be modified during prediction.
|
||||
if (toClone.Event != null)
|
||||
Event = toClone.Event;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new EntityTargetAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that targets some map coordinates. Will result in <see cref="WorldTargetActionEvent"/> being raised.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual]
|
||||
public partial class WorldTargetAction : TargetedAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The local-event to raise when this action is performed.
|
||||
/// </summary>
|
||||
[DataField("event")]
|
||||
[NonSerialized]
|
||||
public WorldTargetActionEvent? Event;
|
||||
|
||||
public WorldTargetAction() { }
|
||||
public WorldTargetAction(WorldTargetAction toClone)
|
||||
{
|
||||
CopyFrom(toClone);
|
||||
}
|
||||
|
||||
public override void CopyFrom(object objectToClone)
|
||||
{
|
||||
base.CopyFrom(objectToClone);
|
||||
|
||||
if (objectToClone is not WorldTargetAction toClone)
|
||||
return;
|
||||
|
||||
// Events should be re-usable, and shouldn't be modified during prediction.
|
||||
if (toClone.Event != null)
|
||||
Event = toClone.Event;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new WorldTargetAction(this);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -9,10 +8,10 @@ namespace Content.Shared.Actions;
|
||||
[Access(typeof(SharedActionsSystem))]
|
||||
public sealed partial class ActionsComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
[Access(typeof(SharedActionsSystem), Other = AccessPermissions.ReadExecute)]
|
||||
// FIXME Friends
|
||||
public SortedSet<ActionType> Actions = new();
|
||||
/// <summary>
|
||||
/// Handled on the client to track added and removed actions.
|
||||
/// </summary>
|
||||
[ViewVariables] public readonly Dictionary<EntityUid, ActionMetaData> OldClientActions = new();
|
||||
|
||||
public override bool SendOnlyToOwner => true;
|
||||
}
|
||||
@@ -20,17 +19,16 @@ public sealed partial class ActionsComponent : Component
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ActionsComponentState : ComponentState
|
||||
{
|
||||
public readonly List<ActionType> Actions;
|
||||
public readonly List<EntityUid> Actions;
|
||||
|
||||
[NonSerialized]
|
||||
public SortedSet<ActionType>? SortedActions;
|
||||
|
||||
public ActionsComponentState(List<ActionType> actions)
|
||||
public ActionsComponentState(List<EntityUid> actions)
|
||||
{
|
||||
Actions = actions;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ActionMetaData(bool ClientExclusive, bool AutoRemove);
|
||||
|
||||
/// <summary>
|
||||
/// Determines how the action icon appears in the hotbar for item actions.
|
||||
/// </summary>
|
||||
|
||||
187
Content.Shared/Actions/BaseActionComponent.cs
Normal file
187
Content.Shared/Actions/BaseActionComponent.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
// TODO this should be an IncludeDataFields of each action component type, not use inheritance
|
||||
public abstract partial class BaseActionComponent : Component
|
||||
{
|
||||
public abstract BaseActionEvent? BaseEvent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon representing this action in the UI.
|
||||
/// </summary>
|
||||
[DataField("icon")] 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;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this color will modulate the action icon color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Keywords that can be used to search for this action in the action menu.
|
||||
/// </summary>
|
||||
[DataField("keywords")] 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;
|
||||
|
||||
/// <summary>
|
||||
/// The toggle state of this action. Toggling switches the currently displayed icon, see <see cref="Icon"/> and <see cref="IconOn"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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>
|
||||
public bool Toggled;
|
||||
|
||||
/// <summary>
|
||||
/// The current cooldown on the action.
|
||||
/// </summary>
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Time interval between action uses.
|
||||
/// </summary>
|
||||
[DataField("useDelay")] public TimeSpan? UseDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience tool for actions with limited number of charges. Automatically decremented on use, and the
|
||||
/// action is disabled when it reaches zero. Does NOT automatically remove the action from the action bar.
|
||||
/// </summary>
|
||||
[DataField("charges")] public int? Charges;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that enables / provides this action. If the action is innate, this may be the user themselves. If
|
||||
/// this action has no provider (e.g., mapping tools), the this will result in broadcast events.
|
||||
/// </summary>
|
||||
public EntityUid? Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Entity to use for the action icon. Defaults to using <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
public EntityUid? EntityIcon
|
||||
{
|
||||
get => _entityIcon ?? Provider;
|
||||
set => _entityIcon = value;
|
||||
}
|
||||
|
||||
private EntityUid? _entityIcon;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will simply execute the action locally without sending to the server.
|
||||
/// </summary>
|
||||
[DataField("clientExclusive")] public bool ClientExclusive = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the order in which actions are automatically added the action bar.
|
||||
/// </summary>
|
||||
[DataField("priority")] public int Priority = 0;
|
||||
|
||||
/// <summary>
|
||||
/// What entity, if any, currently has this action in the actions component?
|
||||
/// </summary>
|
||||
[ViewVariables] public EntityUid? AttachedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically add this action to the action bar when it becomes available.
|
||||
/// </summary>
|
||||
[DataField("autoPopulate")] public bool AutoPopulate = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically remove this action to the action bar when it becomes unavailable.
|
||||
/// </summary>
|
||||
[DataField("autoRemove")] public bool AutoRemove = true;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary actions are removed from the action component when removed from the action-bar/GUI. Currently,
|
||||
/// should only be used for client-exclusive actions (server is not notified).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently there is no way for a player to just voluntarily remove actions. They can hide them from the
|
||||
/// toolbar, but not actually remove them. This is undesirable for things like dynamically added mapping
|
||||
/// entity-selection actions, as the # of actions would just keep increasing.
|
||||
/// </remarks>
|
||||
[DataField("temporary")] public bool Temporary;
|
||||
// TODO re-add support for this
|
||||
// UI refactor seems to have just broken it.
|
||||
|
||||
/// <summary>
|
||||
/// Determines the appearance of the entity-icon for actions that are enabled via some entity.
|
||||
/// </summary>
|
||||
[DataField("itemIconStyle")] 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 HashSet<string> Keywords;
|
||||
public bool Enabled;
|
||||
public bool Toggled;
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown;
|
||||
public TimeSpan? UseDelay;
|
||||
public int? Charges;
|
||||
public EntityUid? Provider;
|
||||
public EntityUid? EntityIcon;
|
||||
public bool CheckCanInteract;
|
||||
public bool ClientExclusive;
|
||||
public int Priority;
|
||||
public EntityUid? AttachedEntity;
|
||||
public bool AutoPopulate;
|
||||
public bool AutoRemove;
|
||||
public bool Temporary;
|
||||
public ItemActionIconStyle ItemIconStyle;
|
||||
public SoundSpecifier? Sound;
|
||||
|
||||
protected BaseActionComponentState(BaseActionComponent component)
|
||||
{
|
||||
Icon = component.Icon;
|
||||
IconOn = component.IconOn;
|
||||
IconColor = component.IconColor;
|
||||
Keywords = component.Keywords;
|
||||
Enabled = component.Enabled;
|
||||
Toggled = component.Toggled;
|
||||
Cooldown = component.Cooldown;
|
||||
UseDelay = component.UseDelay;
|
||||
Charges = component.Charges;
|
||||
Provider = component.Provider;
|
||||
EntityIcon = component.EntityIcon;
|
||||
CheckCanInteract = component.CheckCanInteract;
|
||||
ClientExclusive = component.ClientExclusive;
|
||||
Priority = component.Priority;
|
||||
AttachedEntity = component.AttachedEntity;
|
||||
AutoPopulate = component.AutoPopulate;
|
||||
AutoRemove = component.AutoRemove;
|
||||
Temporary = component.Temporary;
|
||||
ItemIconStyle = component.ItemIconStyle;
|
||||
Sound = component.Sound;
|
||||
}
|
||||
}
|
||||
43
Content.Shared/Actions/BaseTargetActionComponent.cs
Normal file
43
Content.Shared/Actions/BaseTargetActionComponent.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Shared.Interaction;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
public abstract partial class BaseTargetActionComponent : BaseActionComponent
|
||||
{
|
||||
/// <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;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the action system should block this action if the user cannot actually access the target
|
||||
/// (unobstructed, in inventory, in backpack, etc). Some spells or abilities may want to disable this and
|
||||
/// implement their own checks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Even if this is false, the <see cref="Range"/> will still be checked.
|
||||
/// </remarks>
|
||||
[DataField("checkCanAccess")] public bool CheckCanAccess = true;
|
||||
|
||||
[DataField("range")] 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
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Interactions will still be blocked if the target-validation generates a pop-up
|
||||
/// </remarks>
|
||||
[DataField("interactOnMiss")] public bool InteractOnMiss = false;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
35
Content.Shared/Actions/EntityTargetActionComponent.cs
Normal file
35
Content.Shared/Actions/EntityTargetActionComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
[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;
|
||||
|
||||
[DataField("whitelist")] public EntityWhitelist? Whitelist;
|
||||
|
||||
[DataField("canTargetSelf")] public bool CanTargetSelf = true;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EntityTargetActionComponentState : BaseActionComponentState
|
||||
{
|
||||
public EntityWhitelist? Whitelist;
|
||||
public bool CanTargetSelf;
|
||||
|
||||
public EntityTargetActionComponentState(EntityTargetActionComponent component) : base(component)
|
||||
{
|
||||
Whitelist = component.Whitelist;
|
||||
CanTargetSelf = component.CanTargetSelf;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
public sealed partial class EggLayInstantActionEvent : InstantActionEvent {}
|
||||
4
Content.Shared/Actions/Events/GetActionDataEvent.cs
Normal file
4
Content.Shared/Actions/Events/GetActionDataEvent.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetActionDataEvent(BaseActionComponent? Action);
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Content.Shared.Actions.Events;
|
||||
|
||||
public sealed partial class InvisibleWallActionEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
25
Content.Shared/Actions/InstantActionComponent.cs
Normal file
25
Content.Shared/Actions/InstantActionComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
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) : base(component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands;
|
||||
@@ -8,16 +10,18 @@ using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
public abstract class SharedActionsSystem : EntitySystem
|
||||
{
|
||||
private const string ActionContainerId = "ActionContainer";
|
||||
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -34,14 +38,149 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
SubscribeLocalEvent<ActionsComponent, DidUnequipEvent>(OnDidUnequip);
|
||||
SubscribeLocalEvent<ActionsComponent, DidUnequipHandEvent>(OnHandUnequipped);
|
||||
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentGetState>(GetState);
|
||||
SubscribeLocalEvent<ActionsComponent, MapInitEvent>(OnActionsMapInit);
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentGetState>(OnActionsGetState);
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentShutdown>(OnActionsShutdown);
|
||||
|
||||
SubscribeLocalEvent<InstantActionComponent, ComponentGetState>(OnInstantGetState);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ComponentGetState>(OnEntityTargetGetState);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ComponentGetState>(OnWorldTargetGetState);
|
||||
|
||||
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
|
||||
|
||||
SubscribeLocalEvent<InstantActionComponent, GetActionDataEvent>(OnGetActionData);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, GetActionDataEvent>(OnGetActionData);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, GetActionDataEvent>(OnGetActionData);
|
||||
|
||||
SubscribeAllEvent<RequestPerformActionEvent>(OnActionRequest);
|
||||
}
|
||||
|
||||
#region ComponentStateManagement
|
||||
public virtual void Dirty(ActionType action)
|
||||
private void OnInstantGetState(EntityUid uid, InstantActionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new InstantActionComponentState(component);
|
||||
}
|
||||
|
||||
private void OnEntityTargetGetState(EntityUid uid, EntityTargetActionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new EntityTargetActionComponentState(component);
|
||||
}
|
||||
|
||||
private void OnWorldTargetGetState(EntityUid uid, WorldTargetActionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new WorldTargetActionComponentState(component);
|
||||
}
|
||||
|
||||
private void BaseHandleState(BaseActionComponent component, BaseActionComponentState state)
|
||||
{
|
||||
component.Icon = state.Icon;
|
||||
component.IconOn = state.IconOn;
|
||||
component.IconColor = state.IconColor;
|
||||
component.Keywords = new HashSet<string>(state.Keywords);
|
||||
component.Enabled = state.Enabled;
|
||||
component.Toggled = state.Toggled;
|
||||
component.Cooldown = state.Cooldown;
|
||||
component.UseDelay = state.UseDelay;
|
||||
component.Charges = state.Charges;
|
||||
component.Provider = state.Provider;
|
||||
component.EntityIcon = state.EntityIcon;
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
component.ClientExclusive = state.ClientExclusive;
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = state.AttachedEntity;
|
||||
component.AutoPopulate = state.AutoPopulate;
|
||||
component.AutoRemove = state.AutoRemove;
|
||||
component.Temporary = state.Temporary;
|
||||
component.ItemIconStyle = state.ItemIconStyle;
|
||||
component.Sound = state.Sound;
|
||||
}
|
||||
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InstantActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState(component, state);
|
||||
}
|
||||
|
||||
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EntityTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState(component, state);
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
}
|
||||
|
||||
private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not WorldTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState(component, state);
|
||||
}
|
||||
|
||||
private void OnGetActionData<T>(EntityUid uid, T component, ref GetActionDataEvent args) where T : BaseActionComponent
|
||||
{
|
||||
args.Action = component;
|
||||
}
|
||||
|
||||
public BaseActionComponent? GetActionData(EntityUid? actionId)
|
||||
{
|
||||
if (actionId == null)
|
||||
return null;
|
||||
|
||||
// TODO split up logic between each action component with different subscriptions
|
||||
// good luck future coder
|
||||
var ev = new GetActionDataEvent();
|
||||
RaiseLocalEvent(actionId.Value, ref ev);
|
||||
return ev.Action;
|
||||
}
|
||||
|
||||
public bool TryGetActionData(
|
||||
[NotNullWhen(true)] EntityUid? actionId,
|
||||
[NotNullWhen(true)] out BaseActionComponent? action)
|
||||
{
|
||||
action = null;
|
||||
return actionId != null && (action = GetActionData(actionId)) != null;
|
||||
}
|
||||
|
||||
protected Container EnsureContainer(EntityUid holderId)
|
||||
{
|
||||
return _containerSystem.EnsureContainer<Container>(holderId, ActionContainerId);
|
||||
}
|
||||
|
||||
protected bool TryGetContainer(
|
||||
EntityUid holderId,
|
||||
[NotNullWhen(true)] out IContainer? container,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
return _containerSystem.TryGetContainer(holderId, ActionContainerId, out container, containerManager);
|
||||
}
|
||||
|
||||
public void SetCooldown(EntityUid? actionId, TimeSpan start, TimeSpan end)
|
||||
{
|
||||
if (actionId == null)
|
||||
return;
|
||||
|
||||
var action = GetActionData(actionId);
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
action.Cooldown = (start, end);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
#region ComponentStateManagement
|
||||
public virtual void Dirty(EntityUid? actionId)
|
||||
{
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
return;
|
||||
|
||||
Dirty(actionId.Value, action);
|
||||
|
||||
if (action.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
@@ -51,39 +190,63 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty(comp);
|
||||
Dirty(action.AttachedEntity.Value, comp);
|
||||
}
|
||||
|
||||
public void SetToggled(ActionType action, bool toggled)
|
||||
public void SetToggled(EntityUid? actionId, bool toggled)
|
||||
{
|
||||
if (action.Toggled == toggled)
|
||||
if (!TryGetActionData(actionId, out var action) ||
|
||||
action.Toggled == toggled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.Toggled = toggled;
|
||||
Dirty(action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
public void SetEnabled(ActionType action, bool enabled)
|
||||
public void SetEnabled(EntityUid? actionId, bool enabled)
|
||||
{
|
||||
if (action.Enabled == enabled)
|
||||
if (!TryGetActionData(actionId, out var action) ||
|
||||
action.Enabled == enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.Enabled = enabled;
|
||||
Dirty(action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
public void SetCharges(ActionType action, int? charges)
|
||||
public void SetCharges(EntityUid? actionId, int? charges)
|
||||
{
|
||||
if (action.Charges == charges)
|
||||
if (!TryGetActionData(actionId, out var action) ||
|
||||
action.Charges == charges)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.Charges = charges;
|
||||
Dirty(action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
private void GetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
|
||||
private void OnActionsMapInit(EntityUid uid, ActionsComponent component, MapInitEvent args)
|
||||
{
|
||||
args.State = new ActionsComponentState(component.Actions.ToList());
|
||||
EnsureContainer(uid);
|
||||
}
|
||||
|
||||
private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var actions = new List<EntityUid>();
|
||||
if (TryGetContainer(uid, out var container))
|
||||
actions.AddRange(container.ContainedEntities);
|
||||
|
||||
args.State = new ActionsComponentState(actions);
|
||||
}
|
||||
|
||||
private void OnActionsShutdown(EntityUid uid, ActionsComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryGetContainer(uid, out var container))
|
||||
container.Shutdown(EntityManager);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -101,33 +264,36 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!TryComp(user, out ActionsComponent? component))
|
||||
return;
|
||||
|
||||
if (!TryComp(ev.Action, out MetaDataComponent? metaData))
|
||||
return;
|
||||
|
||||
var name = Name(ev.Action, metaData);
|
||||
|
||||
// Does the user actually have the requested action?
|
||||
if (!component.Actions.TryGetValue(ev.Action, out var act))
|
||||
if (!TryGetContainer(user, out var container) || !container.Contains(ev.Action))
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} attempted to perform an action that they do not have: {ev.Action.DisplayName}.");
|
||||
$"{ToPrettyString(user):user} attempted to perform an action that they do not have: {name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!act.Enabled)
|
||||
var action = GetActionData(ev.Action);
|
||||
if (action == null || !action.Enabled)
|
||||
return;
|
||||
|
||||
var curTime = GameTiming.CurTime;
|
||||
if (act.Cooldown.HasValue && act.Cooldown.Value.End > curTime)
|
||||
if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
|
||||
return;
|
||||
|
||||
BaseActionEvent? performEvent = null;
|
||||
|
||||
// Validate request by checking action blockers and the like:
|
||||
var name = Loc.GetString(act.DisplayName);
|
||||
|
||||
switch (act)
|
||||
switch (action)
|
||||
{
|
||||
case EntityTargetAction entityAction:
|
||||
|
||||
case EntityTargetActionComponent entityAction:
|
||||
if (ev.EntityTarget is not { Valid: true } entityTarget)
|
||||
{
|
||||
Log.Error($"Attempted to perform an entity-targeted action without a target! Action: {entityAction.DisplayName}");
|
||||
Log.Error($"Attempted to perform an entity-targeted action without a target! Action: {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,7 +303,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateEntityTarget(user, entityTarget, entityAction))
|
||||
return;
|
||||
|
||||
if (act.Provider == null)
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {ToPrettyString(entityTarget):target}.");
|
||||
@@ -145,22 +311,21 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(act.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}.");
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}.");
|
||||
}
|
||||
|
||||
if (entityAction.Event != null)
|
||||
{
|
||||
entityAction.Event.Target = entityTarget;
|
||||
Dirty(ev.Action, entityAction);
|
||||
performEvent = entityAction.Event;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WorldTargetAction worldAction:
|
||||
|
||||
case WorldTargetActionComponent worldAction:
|
||||
if (ev.EntityCoordinatesTarget is not { } entityCoordinatesTarget)
|
||||
{
|
||||
Log.Error($"Attempted to perform a world-targeted action without a target! Action: {worldAction.DisplayName}");
|
||||
Log.Error($"Attempted to perform a world-targeted action without a target! Action: {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +334,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateWorldTarget(user, entityCoordinatesTarget, worldAction))
|
||||
return;
|
||||
|
||||
if (act.Provider == null)
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {entityCoordinatesTarget:target}.");
|
||||
@@ -177,23 +342,22 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(act.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}.");
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}.");
|
||||
}
|
||||
|
||||
if (worldAction.Event != null)
|
||||
{
|
||||
worldAction.Event.Target = entityCoordinatesTarget;
|
||||
Dirty(ev.Action, worldAction);
|
||||
performEvent = worldAction.Event;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case InstantAction instantAction:
|
||||
|
||||
if (act.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
case InstantActionComponent instantAction:
|
||||
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
return;
|
||||
|
||||
if (act.Provider == null)
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action.");
|
||||
@@ -201,7 +365,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(act.Provider.Value):provider}.");
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Provider.Value):provider}.");
|
||||
}
|
||||
|
||||
performEvent = instantAction.Event;
|
||||
@@ -212,10 +376,10 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
performEvent.Performer = user;
|
||||
|
||||
// All checks passed. Perform the action!
|
||||
PerformAction(user, component, act, performEvent, curTime);
|
||||
PerformAction(user, component, ev.Action, action, performEvent, curTime);
|
||||
}
|
||||
|
||||
public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetAction action)
|
||||
public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetActionComponent action)
|
||||
{
|
||||
if (!target.IsValid() || Deleted(target))
|
||||
return false;
|
||||
@@ -254,7 +418,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return _interactionSystem.CanAccessViaStorage(user, target);
|
||||
}
|
||||
|
||||
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, WorldTargetAction action)
|
||||
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, WorldTargetActionComponent action)
|
||||
{
|
||||
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
return false;
|
||||
@@ -276,7 +440,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
|
||||
}
|
||||
|
||||
public void PerformAction(EntityUid performer, ActionsComponent? component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime, bool predicted = true)
|
||||
public void PerformAction(EntityUid performer, ActionsComponent? component, EntityUid actionId, BaseActionComponent action, BaseActionEvent? actionEvent, TimeSpan curTime, bool predicted = true)
|
||||
{
|
||||
var handled = false;
|
||||
|
||||
@@ -320,89 +484,180 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
action.Cooldown = (curTime, curTime + action.UseDelay.Value);
|
||||
}
|
||||
|
||||
Dirty(actionId, action);
|
||||
|
||||
if (dirty && component != null)
|
||||
Dirty(component);
|
||||
Dirty(performer, component);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AddRemoveActions
|
||||
/// <summary>
|
||||
/// Add an action to an action component. If the entity has no action component, this will give them one.
|
||||
/// Add an action to an action holder.
|
||||
/// If the holder has no actions component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to receive the actions</param>
|
||||
/// <param name="action">The action to add</param>
|
||||
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
||||
public virtual void AddAction(EntityUid uid, ActionType action, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
public BaseActionComponent? AddAction(EntityUid holderId, ref EntityUid? actionId, string? actionPrototypeId, EntityUid? provider = null, ActionsComponent? holderComp = null)
|
||||
{
|
||||
// Because action classes have state data, e.g. cooldowns and uses-remaining, people should not be adding prototypes directly
|
||||
if (action is IPrototype)
|
||||
if (Deleted(actionId))
|
||||
{
|
||||
Log.Error("Attempted to directly add a prototype action. You need to clone a prototype in order to use it.");
|
||||
if (_net.IsClient)
|
||||
return null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(actionPrototypeId))
|
||||
return null;
|
||||
|
||||
actionId = Spawn(actionPrototypeId);
|
||||
}
|
||||
|
||||
AddAction(holderId, actionId.Value, provider, holderComp);
|
||||
return GetActionData(actionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an action to an action holder.
|
||||
/// If the holder has no actions component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="holderId">Entity to receive the actions</param>
|
||||
/// <param name="actionId">Action entity to add</param>
|
||||
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
||||
/// <param name="holder">Component of <see cref="holderId"/></param>
|
||||
/// <param name="action">Component of <see cref="actionId"/></param>
|
||||
/// <param name="actionContainer">Action container of <see cref="holderId"/></param>
|
||||
public virtual void AddAction(EntityUid holderId, EntityUid actionId, EntityUid? provider, ActionsComponent? holder = null, BaseActionComponent? action = null, bool dirty = true, IContainer? actionContainer = null)
|
||||
{
|
||||
action ??= GetActionData(actionId);
|
||||
// TODO remove when action subscriptions are split up
|
||||
if (action == null)
|
||||
{
|
||||
Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}");
|
||||
return;
|
||||
}
|
||||
|
||||
comp ??= EnsureComp<ActionsComponent>(uid);
|
||||
holder ??= EnsureComp<ActionsComponent>(holderId);
|
||||
action.Provider = provider;
|
||||
action.AttachedEntity = uid;
|
||||
AddActionInternal(comp, action);
|
||||
action.AttachedEntity = holderId;
|
||||
Dirty(actionId, action);
|
||||
|
||||
actionContainer ??= EnsureContainer(holderId);
|
||||
AddActionInternal(actionId, actionContainer);
|
||||
|
||||
if (dirty)
|
||||
Dirty(comp);
|
||||
Dirty(holderId, holder);
|
||||
}
|
||||
|
||||
protected virtual void AddActionInternal(ActionsComponent comp, ActionType action)
|
||||
protected virtual void AddActionInternal(EntityUid actionId, IContainer container)
|
||||
{
|
||||
comp.Actions.Add(action);
|
||||
container.Insert(actionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add actions to an action component. If the entity has no action component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to receive the actions</param>
|
||||
/// <param name="holderId">Entity to receive the actions</param>
|
||||
/// <param name="actions">The actions to add</param>
|
||||
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
||||
public void AddActions(EntityUid uid, IEnumerable<ActionType> actions, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
public void AddActions(EntityUid holderId, IEnumerable<EntityUid> actions, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
{
|
||||
comp ??= EnsureComp<ActionsComponent>(uid);
|
||||
comp ??= EnsureComp<ActionsComponent>(holderId);
|
||||
|
||||
var allClientExclusive = true;
|
||||
var container = EnsureContainer(holderId);
|
||||
|
||||
foreach (var action in actions)
|
||||
foreach (var actionId in actions)
|
||||
{
|
||||
AddAction(uid, action, provider, comp, false);
|
||||
var action = GetActionData(actionId);
|
||||
if (action == null)
|
||||
continue;
|
||||
|
||||
AddAction(holderId, actionId, provider, comp, action, false, container);
|
||||
allClientExclusive = allClientExclusive && action.ClientExclusive;
|
||||
}
|
||||
|
||||
if (dirty && !allClientExclusive)
|
||||
Dirty(comp);
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetActions(EntityUid holderId, IContainer? container = null)
|
||||
{
|
||||
if (container == null &&
|
||||
!TryGetContainer(holderId, out container))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var actionId in container.ContainedEntities)
|
||||
{
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
continue;
|
||||
|
||||
yield return (actionId, action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove any actions that were enabled by some other entity. Useful when unequiping items that grant actions.
|
||||
/// </summary>
|
||||
public void RemoveProvidedActions(EntityUid uid, EntityUid provider, ActionsComponent? comp = null)
|
||||
public void RemoveProvidedActions(EntityUid holderId, EntityUid provider, ActionsComponent? comp = null, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
if (!Resolve(holderId, ref comp, ref actionContainer, false))
|
||||
return;
|
||||
|
||||
foreach (var act in comp.Actions.ToArray())
|
||||
if (!TryGetContainer(holderId, out var container, actionContainer))
|
||||
return;
|
||||
|
||||
foreach (var actionId in container.ContainedEntities.ToArray())
|
||||
{
|
||||
if (act.Provider == provider)
|
||||
RemoveAction(uid, act, comp, dirty: false);
|
||||
var action = GetActionData(actionId);
|
||||
if (action?.Provider == provider)
|
||||
RemoveAction(holderId, actionId, comp, dirty: false, actionContainer: actionContainer);
|
||||
}
|
||||
Dirty(comp);
|
||||
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
public virtual void RemoveAction(EntityUid uid, ActionType action, ActionsComponent? comp = null, bool dirty = true)
|
||||
public virtual void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
if (actionId == null ||
|
||||
!Resolve(holderId, ref comp, ref actionContainer, false) ||
|
||||
!TryGetContainer(holderId, out var container, actionContainer) ||
|
||||
!container.Contains(actionId.Value) ||
|
||||
TerminatingOrDeleted(actionId.Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
comp.Actions.Remove(action);
|
||||
action.AttachedEntity = null;
|
||||
action ??= GetActionData(actionId);
|
||||
container.Remove(actionId.Value);
|
||||
|
||||
if (action != null)
|
||||
{
|
||||
action.AttachedEntity = null;
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(comp);
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all actions with the given prototype id.
|
||||
/// </summary>
|
||||
public void RemoveAction(EntityUid holderId, string actionPrototypeId, ActionsComponent? holderComp = null, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(holderId, ref holderComp, ref actionContainer, false))
|
||||
return;
|
||||
|
||||
var actions = new List<(EntityUid Id, BaseActionComponent Comp)>();
|
||||
foreach (var (id, comp) in GetActions(holderId))
|
||||
{
|
||||
if (Prototype(id)?.ID == actionPrototypeId)
|
||||
actions.Add((id, comp));
|
||||
}
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
RemoveAction(holderId, action.Id, holderComp, action.Comp, actionContainer: actionContainer);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -410,7 +665,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
#region EquipHandlers
|
||||
private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
|
||||
{
|
||||
var ev = new GetItemActionsEvent(args.Equipee, args.SlotFlags);
|
||||
var ev = new GetItemActionsEvent(EntityManager, _net, args.Equipee, args.SlotFlags);
|
||||
RaiseLocalEvent(args.Equipment, ev);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
@@ -421,7 +676,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
|
||||
private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
|
||||
{
|
||||
var ev = new GetItemActionsEvent(args.User);
|
||||
var ev = new GetItemActionsEvent(EntityManager, _net, args.User);
|
||||
RaiseLocalEvent(args.Equipped, ev);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
|
||||
25
Content.Shared/Actions/WorldTargetActionComponent.cs
Normal file
25
Content.Shared/Actions/WorldTargetActionComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
[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) : base(component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,25 @@
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Bed.Sleep
|
||||
{
|
||||
public abstract class SharedSleepingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] private const string WakeActionId = "ActionWake";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SleepingComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SleepingComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
|
||||
@@ -22,7 +29,7 @@ namespace Content.Server.Bed.Sleep
|
||||
private void OnSleepUnpaused(EntityUid uid, SleepingComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.CoolDownEnd += args.PausedTime;
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, SleepingComponent component, ComponentStartup args)
|
||||
@@ -32,8 +39,17 @@ namespace Content.Server.Bed.Sleep
|
||||
_blindableSystem.UpdateIsBlind(uid);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args)
|
||||
{
|
||||
component.WakeAction = Spawn(WakeActionId);
|
||||
_actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(15));
|
||||
_actionsSystem.AddAction(uid, component.WakeAction.Value, null);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(uid, component.WakeAction);
|
||||
|
||||
var ev = new SleepStateChangedEvent(false);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
_blindableSystem.UpdateIsBlind(uid);
|
||||
|
||||
@@ -25,4 +25,6 @@ public sealed partial class SleepingComponent : Component
|
||||
|
||||
[DataField("cooldownEnd", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan CoolDownEnd;
|
||||
|
||||
[DataField("wakeAction")] public EntityUid? WakeAction;
|
||||
}
|
||||
|
||||
8
Content.Shared/Bible/SummonActionEvent.cs
Normal file
8
Content.Shared/Bible/SummonActionEvent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared.Bible;
|
||||
|
||||
public sealed partial class SummonActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
@@ -80,11 +78,7 @@ public sealed partial class BlockingSystem : EntitySystem
|
||||
|
||||
private void OnGetActions(EntityUid uid, BlockingComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
if (component.BlockingToggleAction == null && _proto.TryIndex(component.BlockingToggleActionId, out InstantActionPrototype? act))
|
||||
component.BlockingToggleAction = new(act);
|
||||
|
||||
if (component.BlockingToggleAction != null)
|
||||
args.Actions.Add(component.BlockingToggleAction);
|
||||
args.AddAction(ref component.BlockingToggleActionEntity, component.BlockingToggleAction);
|
||||
}
|
||||
|
||||
private void OnToggleAction(EntityUid uid, BlockingComponent component, ToggleActionEvent args)
|
||||
@@ -191,7 +185,7 @@ public sealed partial class BlockingSystem : EntitySystem
|
||||
CantBlockError(user);
|
||||
return false;
|
||||
}
|
||||
_actionsSystem.SetToggled(component.BlockingToggleAction, true);
|
||||
_actionsSystem.SetToggled(component.BlockingToggleActionEntity, true);
|
||||
_popupSystem.PopupEntity(msgUser, user, user);
|
||||
_popupSystem.PopupEntity(msgOther, user, Filter.PvsExcept(user), true);
|
||||
}
|
||||
@@ -252,7 +246,7 @@ public sealed partial class BlockingSystem : EntitySystem
|
||||
if (xform.Anchored)
|
||||
_transformSystem.Unanchor(user, xform);
|
||||
|
||||
_actionsSystem.SetToggled(component.BlockingToggleAction, false);
|
||||
_actionsSystem.SetToggled(component.BlockingToggleActionEntity, false);
|
||||
_fixtureSystem.DestroyFixture(user, BlockingComponent.BlockFixtureID, body: physicsComponent);
|
||||
_physics.SetBodyType(user, blockingUserComponent.OriginalBodyType, body: physicsComponent);
|
||||
_popupSystem.PopupEntity(msgUser, user, user);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Blocking;
|
||||
@@ -47,11 +47,11 @@ public sealed partial class BlockingComponent : Component
|
||||
[DataField("activeBlockModifier", required: true)]
|
||||
public DamageModifierSet ActiveBlockDamageModifier = default!;
|
||||
|
||||
[DataField("blockingToggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string BlockingToggleActionId = "ToggleBlock";
|
||||
[DataField("blockingToggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string BlockingToggleAction = "ActionToggleBlock";
|
||||
|
||||
[DataField("blockingToggleAction")]
|
||||
public InstantAction? BlockingToggleAction;
|
||||
[DataField("blockingToggleActionEntity")]
|
||||
public EntityUid? BlockingToggleActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The sound to be played when you get hit while actively blocking
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Content.Shared.CartridgeLoader;
|
||||
|
||||
@@ -42,6 +41,6 @@ public sealed partial class CartridgeLoaderComponent : Component
|
||||
[DataField("diskSpace")]
|
||||
public int DiskSpace = 5;
|
||||
|
||||
[DataField("uiKey", required: true, customTypeSerializer: typeof(EnumSerializer))]
|
||||
[DataField("uiKey", required: true)]
|
||||
public Enum UiKey = default!;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared.Clothing;
|
||||
|
||||
/// <summary>
|
||||
@@ -55,3 +57,5 @@ public sealed class EquipmentVisualsUpdatedEvent : EntityEventArgs
|
||||
RevealedLayers = revealedLayers;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class ToggleMaskEvent : InstantActionEvent { }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Clothing.Components;
|
||||
|
||||
@@ -23,14 +24,13 @@ public sealed partial class StealthClothingComponent : Component
|
||||
[DataField("visibility"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public float Visibility;
|
||||
|
||||
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ToggleAction = "ActionTogglePhaseCloak";
|
||||
|
||||
/// <summary>
|
||||
/// The action for enabling and disabling stealth.
|
||||
/// </summary>
|
||||
[DataField("toggleAction")]
|
||||
public InstantAction ToggleAction = new()
|
||||
{
|
||||
Event = new ToggleStealthEvent()
|
||||
};
|
||||
[DataField("toggleActionEntity")] public EntityUid? ToggleActionEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -20,9 +19,11 @@ public sealed partial class ToggleableClothingComponent : Component
|
||||
/// <summary>
|
||||
/// Action used to toggle the clothing on or off.
|
||||
/// </summary>
|
||||
[DataField("actionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ActionId = "ToggleSuitPiece";
|
||||
public InstantAction? ToggleAction = null;
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Action = "ActionToggleSuitPiece";
|
||||
|
||||
[DataField("actionEntity")]
|
||||
public EntityUid? ActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Default clothing entity prototype to spawn into the clothing container.
|
||||
@@ -66,7 +67,7 @@ public sealed partial class ToggleableClothingComponent : Component
|
||||
public TimeSpan? StripDelay = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>
|
||||
/// Text shown in the toggle-clothing verb. Defaults to using the name of the <see cref="ToggleAction"/> action.
|
||||
/// Text shown in the toggle-clothing verb. Defaults to using the name of the <see cref="ActionEntity"/> action.
|
||||
/// </summary>
|
||||
[DataField("verbText")]
|
||||
public string? VerbText;
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Stealth;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
@@ -59,7 +58,7 @@ public sealed class StealthClothingSystem : EntitySystem
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
args.Actions.Add(comp.ToggleAction);
|
||||
args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -10,7 +9,6 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Strip;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -60,7 +58,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
if (!args.CanAccess || !args.CanInteract || component.ClothingUid == null || component.Container == null)
|
||||
return;
|
||||
|
||||
var text = component.VerbText ?? component.ToggleAction?.DisplayName;
|
||||
var text = component.VerbText ?? (component.ActionEntity == null ? null : Name(component.ActionEntity.Value));
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
@@ -179,8 +177,11 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
// automatically be deleted.
|
||||
|
||||
// remove action.
|
||||
if (component.ToggleAction?.AttachedEntity != null)
|
||||
_actionsSystem.RemoveAction(component.ToggleAction.AttachedEntity.Value, component.ToggleAction);
|
||||
if (_actionsSystem.TryGetActionData(component.ActionEntity, out var action) &&
|
||||
action.AttachedEntity != null)
|
||||
{
|
||||
_actionsSystem.RemoveAction(action.AttachedEntity.Value, component.ActionEntity);
|
||||
}
|
||||
|
||||
if (component.ClothingUid != null)
|
||||
QueueDel(component.ClothingUid.Value);
|
||||
@@ -200,8 +201,11 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// remove action.
|
||||
if (toggleComp.ToggleAction?.AttachedEntity != null)
|
||||
_actionsSystem.RemoveAction(toggleComp.ToggleAction.AttachedEntity.Value, toggleComp.ToggleAction);
|
||||
if (_actionsSystem.TryGetActionData(toggleComp.ActionEntity, out var action) &&
|
||||
action.AttachedEntity != null)
|
||||
{
|
||||
_actionsSystem.RemoveAction(action.AttachedEntity.Value, toggleComp.ActionEntity);
|
||||
}
|
||||
|
||||
RemComp(component.AttachedUid, toggleComp);
|
||||
}
|
||||
@@ -259,8 +263,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
if (component.ClothingUid == null || (args.SlotFlags & component.RequiredFlags) != component.RequiredFlags)
|
||||
return;
|
||||
|
||||
if (component.ToggleAction != null)
|
||||
args.Actions.Add(component.ToggleAction);
|
||||
args.AddAction(ref component.ActionEntity, component.Action);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, ToggleableClothingComponent component, ComponentInit args)
|
||||
@@ -280,13 +283,9 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.ToggleAction == null
|
||||
&& _proto.TryIndex(component.ActionId, out InstantActionPrototype? act))
|
||||
{
|
||||
component.ToggleAction = new(act);
|
||||
}
|
||||
component.ActionEntity ??= Spawn(component.Action);
|
||||
|
||||
if (component.ClothingUid != null && component.ToggleAction != null)
|
||||
if (component.ClothingUid != null && component.ActionEntity != null)
|
||||
{
|
||||
DebugTools.Assert(Exists(component.ClothingUid), "Toggleable clothing is missing expected entity.");
|
||||
DebugTools.Assert(TryComp(component.ClothingUid, out AttachedClothingComponent? comp), "Toggleable clothing is missing an attached component");
|
||||
@@ -300,10 +299,10 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
||||
component.Container.Insert(component.ClothingUid.Value, EntityManager, ownerTransform: xform);
|
||||
}
|
||||
|
||||
if (component.ToggleAction != null)
|
||||
if (_actionsSystem.TryGetActionData(component.ActionEntity, out var action))
|
||||
{
|
||||
component.ToggleAction.EntityIcon = component.ClothingUid;
|
||||
_actionsSystem.Dirty(component.ToggleAction);
|
||||
action.EntityIcon = component.ClothingUid;
|
||||
_actionsSystem.Dirty(component.ActionEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Clothing;
|
||||
|
||||
@@ -7,8 +8,11 @@ namespace Content.Shared.Clothing;
|
||||
[Access(typeof(SharedMagbootsSystem))]
|
||||
public sealed partial class MagbootsComponent : Component
|
||||
{
|
||||
[DataField("toggleAction", required: true)]
|
||||
public InstantAction ToggleAction = new();
|
||||
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
|
||||
public string? ToggleAction;
|
||||
|
||||
[DataField("toggleActionEntity")]
|
||||
public EntityUid? ToggleActionEntity;
|
||||
|
||||
[DataField("on"), AutoNetworkedField]
|
||||
public bool On;
|
||||
|
||||
@@ -62,7 +62,7 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
|
||||
protected void OnChanged(EntityUid uid, MagbootsComponent component)
|
||||
{
|
||||
_sharedActions.SetToggled(component.ToggleAction, component.On);
|
||||
_sharedActions.SetToggled(component.ToggleActionEntity, component.On);
|
||||
_clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid, component.On);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
|
||||
private void OnGetActions(EntityUid uid, MagbootsComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.Actions.Add(component.ToggleAction);
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Targeting;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.CombatMode
|
||||
@@ -32,11 +32,11 @@ namespace Content.Shared.CombatMode
|
||||
|
||||
#endregion
|
||||
|
||||
[DataField("combatToggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string CombatToggleActionId = "CombatModeToggle";
|
||||
[DataField("combatToggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string CombatToggleAction = "ActionCombatModeToggle";
|
||||
|
||||
[DataField("combatToggleAction")]
|
||||
public InstantAction? CombatToggleAction;
|
||||
[DataField("combatToggleActionEntity")]
|
||||
public EntityUid? CombatToggleActionEntity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("isInCombatMode"), AutoNetworkedField]
|
||||
public bool IsInCombatMode;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Shared.CombatMode.Pacification;
|
||||
|
||||
@@ -33,10 +32,7 @@ public sealed class PacificationSystem : EntitySystem
|
||||
_combatSystem.SetCanDisarm(uid, false, combatMode);
|
||||
|
||||
_combatSystem.SetInCombatMode(uid, false, combatMode);
|
||||
|
||||
if (combatMode.CombatToggleAction != null)
|
||||
_actionsSystem.SetEnabled(combatMode.CombatToggleAction, false);
|
||||
|
||||
_actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, false);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.Pacified);
|
||||
}
|
||||
|
||||
@@ -48,9 +44,7 @@ public sealed class PacificationSystem : EntitySystem
|
||||
if (combatMode.CanDisarm != null)
|
||||
_combatSystem.SetCanDisarm(uid, true, combatMode);
|
||||
|
||||
if (combatMode.CombatToggleAction != null)
|
||||
_actionsSystem.SetEnabled(combatMode.CombatToggleAction, true);
|
||||
|
||||
_actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, true);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.Pacified);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Targeting;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.CombatMode;
|
||||
@@ -13,7 +10,6 @@ public abstract class SharedCombatModeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
@@ -21,27 +17,19 @@ public abstract class SharedCombatModeSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CombatModeComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<CombatModeComponent, MapInitEvent>(OnStartup);
|
||||
SubscribeLocalEvent<CombatModeComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<CombatModeComponent, ToggleCombatActionEvent>(OnActionPerform);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, CombatModeComponent component, ComponentStartup args)
|
||||
private void OnStartup(EntityUid uid, CombatModeComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.CombatToggleAction == null
|
||||
&& _protoMan.TryIndex(component.CombatToggleActionId, out InstantActionPrototype? toggleProto))
|
||||
{
|
||||
component.CombatToggleAction = new(toggleProto);
|
||||
}
|
||||
|
||||
if (component.CombatToggleAction != null)
|
||||
_actionsSystem.AddAction(uid, component.CombatToggleAction, null);
|
||||
_actionsSystem.AddAction(uid, ref component.CombatToggleActionEntity, component.CombatToggleAction);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, CombatModeComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.CombatToggleAction != null)
|
||||
_actionsSystem.RemoveAction(uid, component.CombatToggleAction);
|
||||
_actionsSystem.RemoveAction(uid, component.CombatToggleActionEntity);
|
||||
}
|
||||
|
||||
private void OnActionPerform(EntityUid uid, CombatModeComponent component, ToggleCombatActionEvent args)
|
||||
@@ -86,8 +74,8 @@ public abstract class SharedCombatModeSystem : EntitySystem
|
||||
component.IsInCombatMode = value;
|
||||
Dirty(entity, component);
|
||||
|
||||
if (component.CombatToggleAction != null)
|
||||
_actionsSystem.SetToggled(component.CombatToggleAction, component.IsInCombatMode);
|
||||
if (component.CombatToggleActionEntity != null)
|
||||
_actionsSystem.SetToggled(component.CombatToggleActionEntity, component.IsInCombatMode);
|
||||
}
|
||||
|
||||
public virtual void SetActiveZone(EntityUid entity, TargetingZone zone,
|
||||
|
||||
@@ -141,7 +141,8 @@ namespace Content.Shared.Cuffs
|
||||
|
||||
private void OnCuffsRemovedFromContainer(EntityUid uid, CuffableComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.Container.ID)
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
if (args.Container.ID != component.Container?.ID)
|
||||
return;
|
||||
|
||||
_handVirtualItem.DeleteInHandsMatching(uid, args.Entity);
|
||||
|
||||
25
Content.Shared/Decals/PlaceDecalActionEvent.cs
Normal file
25
Content.Shared/Decals/PlaceDecalActionEvent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Decals;
|
||||
|
||||
public sealed partial class PlaceDecalActionEvent : WorldTargetActionEvent
|
||||
{
|
||||
[DataField("decalId", customTypeSerializer:typeof(PrototypeIdSerializer<DecalPrototype>), required:true)]
|
||||
public string DecalId = string.Empty;
|
||||
|
||||
[DataField("color")]
|
||||
public Color Color;
|
||||
|
||||
[DataField("rotation")]
|
||||
public double Rotation;
|
||||
|
||||
[DataField("snap")]
|
||||
public bool Snap;
|
||||
|
||||
[DataField("zIndex")]
|
||||
public int ZIndex;
|
||||
|
||||
[DataField("cleanable")]
|
||||
public bool Cleanable;
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Devour;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Devour.Components;
|
||||
namespace Content.Shared.Devour.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedDevourSystem))]
|
||||
public sealed partial class DevourerComponent : Component
|
||||
{
|
||||
[DataField("devourAction")]
|
||||
public EntityTargetAction? DevourAction;
|
||||
[DataField("devourAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? DevourAction = "ActionDevour";
|
||||
|
||||
[DataField("devourActionEntity")]
|
||||
public EntityUid? DevourActionEntity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundDevour")]
|
||||
public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user