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;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
- public event Action<EntityUid>? ActionAdded;
- public event Action<EntityUid>? ActionRemoved;
+ public event Action<EntityUid>? OnActionAdded;
+ public event Action<EntityUid>? OnActionRemoved;
public event OnActionReplaced? ActionReplaced;
public event Action? ActionsUpdated;
public event Action<ActionsComponent>? LinkActions;
public event Action? ClearAssignments;
public event Action<List<SlotAssignment>>? AssignSlot;
- /// <summary>
- /// Queue of entities with <see cref="ActionsComponent"/> that needs to be updated after
- /// handling a state.
- /// </summary>
- private readonly Queue<EntityUid> _actionHoldersQueue = new();
+ private readonly List<EntityUid> _removed = new();
+ private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
public override void Initialize()
{
SubscribeLocalEvent<ActionsComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ActionsComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
+
+ SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
+ SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
+ SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
}
- public override void Dirty(EntityUid? actionId)
+ private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
{
- var action = GetActionData(actionId);
- if (_playerManager.LocalPlayer?.ControlledEntity != action?.AttachedEntity)
+ if (args.Current is not InstantActionComponentState state)
return;
- base.Dirty(actionId);
- ActionsUpdated?.Invoke();
+ BaseHandleState<InstantActionComponent>(uid, component, state);
}
- private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
+ private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
{
- if (args.Current is not ActionsComponentState state)
+ if (args.Current is not EntityTargetActionComponentState state)
return;
- component.Actions.Clear();
- component.Actions.UnionWith(EnsureEntitySet<ActionsComponent>(state.Actions, uid));
+ component.Whitelist = state.Whitelist;
+ component.CanTargetSelf = state.CanTargetSelf;
+ BaseHandleState<EntityTargetActionComponent>(uid, component, state);
+ }
- _actionHoldersQueue.Enqueue(uid);
+ private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not WorldTargetActionComponentState state)
+ return;
+
+ BaseHandleState<WorldTargetActionComponent>(uid, component, state);
}
- protected override void AddActionInternal(EntityUid holderId, EntityUid actionId, BaseContainer container, ActionsComponent holder)
+ private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{
- // 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 (container.Contains(actionId))
- {
- ActionReplaced?.Invoke(actionId);
- }
- else
- {
- base.AddActionInternal(holderId, actionId, container, holder);
- }
+ 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.Container = EnsureEntity<T>(state.Container, uid);
+ component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
+ component.CheckCanInteract = state.CheckCanInteract;
+ component.ClientExclusive = state.ClientExclusive;
+ component.Priority = state.Priority;
+ component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
+ component.AutoPopulate = state.AutoPopulate;
+ component.Temporary = state.Temporary;
+ component.ItemIconStyle = state.ItemIconStyle;
+ component.Sound = state.Sound;
+
+ if (_playerManager.LocalPlayer?.ControlledEntity == component.AttachedEntity)
+ ActionsUpdated?.Invoke();
}
- public override void AddAction(EntityUid holderId, EntityUid actionId, EntityUid? provider, ActionsComponent? holder = null, BaseActionComponent? action = null, bool dirty = true, BaseContainer? actionContainer = null)
+ protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
- if (!Resolve(holderId, ref holder, false))
+ if (!ResolveActionData(actionId, ref action))
return;
- action ??= GetActionData(actionId);
- if (action == null)
- {
- Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}");
+ base.UpdateAction(actionId, action);
+ if (_playerManager.LocalPlayer?.ControlledEntity != action.AttachedEntity)
return;
- }
-
- dirty &= !action.ClientExclusive;
- base.AddAction(holderId, actionId, provider, holder, action, dirty, actionContainer);
- if (holderId == _playerManager.LocalPlayer?.ControlledEntity)
- ActionAdded?.Invoke(actionId);
+ ActionsUpdated?.Invoke();
}
- public override void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true)
+ private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
{
- if (GameTiming.ApplyingState)
+ if (args.Current is not ActionsComponentState state)
return;
- if (!Resolve(holderId, ref comp, false))
- return;
+ _added.Clear();
+ _removed.Clear();
+ var stateEnts = EnsureEntitySet<ActionsComponent>(state.Actions, uid);
+ foreach (var act in component.Actions)
+ {
+ if (!stateEnts.Contains(act) && !IsClientSide(act))
+ _removed.Add(act);
+ }
+ component.Actions.ExceptWith(_removed);
+
+ foreach (var actionId in stateEnts)
+ {
+ if (!actionId.IsValid())
+ continue;
- if (actionId == null)
+ if (!component.Actions.Add(actionId))
+ continue;
+
+ TryGetActionData(actionId, out var action);
+ _added.Add((actionId, action));
+ }
+
+ if (_playerManager.LocalPlayer?.ControlledEntity != uid)
return;
- action ??= GetActionData(actionId);
+ foreach (var action in _removed)
+ {
+ OnActionRemoved?.Invoke(action);
+ }
+
+ _added.Sort(ActionComparer);
+
+ foreach (var action in _added)
+ {
+ OnActionAdded?.Invoke(action.Item1);
+ }
+
+ ActionsUpdated?.Invoke();
+ }
+
+ public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b)
+ {
+ var priorityA = a.Item2?.Priority ?? 0;
+ var priorityB = b.Item2?.Priority ?? 0;
+ if (priorityA != priorityB)
+ return priorityA - priorityB;
+
+ priorityA = a.Item2?.Container?.Id ?? 0;
+ priorityB = b.Item2?.Container?.Id ?? 0;
+ return priorityA - priorityB;
+ }
- if (action is { ClientExclusive: false })
+ protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp,
+ BaseActionComponent action)
+ {
+ if (_playerManager.LocalPlayer?.ControlledEntity != performer)
return;
- dirty &= !action?.ClientExclusive ?? true;
- base.RemoveAction(holderId, actionId, comp, action, dirty);
+ OnActionAdded?.Invoke(actionId);
+ }
- if (_playerManager.LocalPlayer?.ControlledEntity != holderId)
+ protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
+ {
+ if (_playerManager.LocalPlayer?.ControlledEntity != performer)
return;
- if (action == null || action.AutoRemove)
- ActionRemoved?.Invoke(actionId.Value);
+ OnActionRemoved?.Invoke(actionId);
}
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
return;
}
- if (action.Provider != null && Deleted(action.Provider))
- return;
-
if (action is not InstantActionComponent instantAction)
return;
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
var actionId = Spawn(null);
- AddComp<Component>(actionId, action);
- AddAction(user, actionId, null);
+ AddComp(actionId, action);
+ AddActionDirect(user, actionId);
if (map.TryGet<ValueDataNode>("name", out var nameNode))
_metaData.SetEntityName(actionId, nameNode.Value);
AssignSlot?.Invoke(assignments);
}
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- if (_actionHoldersQueue.Count == 0)
- return;
-
- var removed = new List<EntityUid>();
- var added = new List<(EntityUid Id, BaseActionComponent Comp)>();
- 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 (!holder.Actions.Contains(act))
- {
- holder.OldClientActions.Remove(act);
- if (data.AutoRemove)
- removed.Add(act);
- }
- }
-
- // Anything that remains is a new action
- foreach (var newAct in holder.Actions)
- {
- if (!TryGetActionData(newAct, out var serverData))
- continue;
-
- if (!holder.OldClientActions.ContainsKey(newAct))
- added.Add((newAct, serverData));
-
- holder.OldClientActions[newAct] = new ActionMetaData(serverData.ClientExclusive, serverData.AutoRemove);
- }
-
- if (_playerManager.LocalPlayer?.ControlledEntity != holderId)
- return;
-
- foreach (var action in removed)
- {
- ActionRemoved?.Invoke(action);
- }
-
- added.Sort(static (a, b) =>
- {
- if (a.Comp.Priority != b.Comp.Priority)
- return a.Comp.Priority - b.Comp.Priority;
-
- if (a.Comp.Provider != b.Comp.Provider)
- {
- if (a.Comp.Provider == null)
- return -1;
-
- if (b.Comp.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) a.Comp.Provider - (int) b.Comp.Provider;
- }
-
- return 0;
- });
-
- foreach (var action in added)
- {
- ActionAdded?.Invoke(action.Item1);
- }
-
- ActionsUpdated?.Invoke();
- }
- }
-
public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);
}
}
{
base.Initialize();
- SubscribeLocalEvent<GhostComponent, ComponentInit>(OnGhostInit);
+ SubscribeLocalEvent<GhostComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<GhostComponent, ComponentRemove>(OnGhostRemove);
SubscribeLocalEvent<GhostComponent, AfterAutoHandleStateEvent>(OnGhostState);
SubscribeLocalEvent<GhostComponent, ToggleGhostsActionEvent>(OnToggleGhosts);
}
- private void OnGhostInit(EntityUid uid, GhostComponent component, ComponentInit args)
+ private void OnStartup(EntityUid uid, GhostComponent component, ComponentStartup args)
{
if (TryComp(uid, out SpriteComponent? sprite))
- {
sprite.Visible = GhostVisibility;
- }
-
- _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)
[Dependency] private readonly IRobustRandom _random = default!;
private readonly DeviceListSystem _deviceListSystem;
- private Dictionary<EntityUid, Color> _colors = new();
+ public Dictionary<EntityUid, Color> Colors = new();
+ public EntityUid? Action;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
_deviceListSystem = _entityManager.System<DeviceListSystem>();
}
- public void ClearEntity(EntityUid uid)
- {
- _colors.Remove(uid);
- }
-
protected override void Draw(in OverlayDrawArgs args)
{
foreach (var tracker in _entityManager.EntityQuery<NetworkConfiguratorActiveLinkOverlayComponent>())
continue;
}
- if (!_colors.TryGetValue(tracker.Owner, out var color))
+ if (!Colors.TryGetValue(tracker.Owner, out var color))
{
color = new Color(
_random.Next(0, 255),
_random.Next(0, 255),
_random.Next(0, 255));
- _colors.Add(tracker.Owner, color);
+ Colors.Add(tracker.Owner, color);
}
var sourceTransform = _entityManager.GetComponent<TransformComponent>(tracker.Owner);
continue;
}
- args.WorldHandle.DrawLine(sourceTransform.WorldPosition, linkTransform.WorldPosition, _colors[tracker.Owner]);
+ args.WorldHandle.DrawLine(sourceTransform.WorldPosition, linkTransform.WorldPosition, Colors[tracker.Owner]);
}
}
}
using Content.Client.Items;
using Content.Client.Message;
using Content.Client.Stylesheets;
+using Content.Shared.Actions;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Input;
if (!toggle)
{
- if (_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
- {
- _overlay.GetOverlay<NetworkConfiguratorLinkOverlay>().ClearEntity(component.ActiveDeviceList.Value);
- }
-
RemComp<NetworkConfiguratorActiveLinkOverlayComponent>(component.ActiveDeviceList.Value);
- if (!EntityQuery<NetworkConfiguratorActiveLinkOverlayComponent>().Any())
- {
- _overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
- _actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, Action);
- }
+ if (!_overlay.TryGetOverlay(out NetworkConfiguratorLinkOverlay? overlay))
+ return;
+ overlay.Colors.Remove(component.ActiveDeviceList.Value);
+ if (overlay.Colors.Count > 0)
+ return;
+ _actions.RemoveAction(overlay.Action);
+ _overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
return;
}
if (!_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
{
- _overlay.AddOverlay(new NetworkConfiguratorLinkOverlay());
- _actions.AddAction(_playerManager.LocalPlayer.ControlledEntity.Value, Spawn(Action), null);
+ var overlay = new NetworkConfiguratorLinkOverlay();
+ _overlay.AddOverlay(overlay);
+ var player = _playerManager.LocalPlayer.ControlledEntity.Value;
+ overlay.Action = Spawn(Action);
+ _actions.AddActionDirect(player, overlay.Action.Value);
}
EnsureComp<NetworkConfiguratorActiveLinkOverlayComponent>(component.ActiveDeviceList.Value);
public void ClearAllOverlays()
{
- if (!_overlay.HasOverlay<NetworkConfiguratorLinkOverlay>())
+ if (!_overlay.TryGetOverlay(out NetworkConfiguratorLinkOverlay? overlay))
{
return;
}
RemCompDeferred<NetworkConfiguratorActiveLinkOverlayComponent>(tracker.Owner);
}
- _overlay.RemoveOverlay<NetworkConfiguratorLinkOverlay>();
-
- if (_playerManager.LocalPlayer?.ControlledEntity != null)
- {
- _actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, Action);
- }
+ _actions.RemoveAction(overlay.Action);
+ _overlay.RemoveOverlay(overlay);
}
// hacky solution related to mapping
{
if (_actionsSystem != null)
{
- _actionsSystem.ActionAdded += OnActionAdded;
- _actionsSystem.ActionRemoved += OnActionRemoved;
+ _actionsSystem.OnActionAdded += OnActionAdded;
+ _actionsSystem.OnActionRemoved += OnActionRemoved;
_actionsSystem.ActionReplaced += OnActionReplaced;
_actionsSystem.ActionsUpdated += OnActionsUpdated;
}
{
if (_actionsSystem != null)
{
- _actionsSystem.ActionAdded -= OnActionAdded;
- _actionsSystem.ActionRemoved -= OnActionRemoved;
+ _actionsSystem.OnActionAdded -= OnActionAdded;
+ _actionsSystem.OnActionRemoved -= OnActionRemoved;
_actionsSystem.ActionReplaced -= OnActionReplaced;
_actionsSystem.ActionsUpdated -= OnActionsUpdated;
}
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.Item => action.Container != null && action.Container != _playerManager.LocalPlayer?.ControlledEntity,
+ Filters.Innate => action.Container == null || action.Container == _playerManager.LocalPlayer?.ControlledEntity,
Filters.Instant => action is InstantActionComponent,
Filters.Targeted => action is BaseTargetActionComponent,
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
if (_window is not { Disposed: false } || _actionsSystem == null)
return;
+ if (_playerManager.LocalPlayer?.ControlledEntity is not { } player)
+ return;
+
var search = _window.SearchBar.Text;
var filters = _window.FilterButton.SelectedKeys;
var actions = _actionsSystem.GetClientActions();
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
return true;
- if (action.Comp.Provider == null || action.Comp.Provider == _playerManager.LocalPlayer?.ControlledEntity)
+ if (action.Comp.Container == null || action.Comp.Container == player)
return false;
- var providerName = EntityManager.GetComponent<MetaDataComponent>(action.Comp.Provider.Value).EntityName;
+ var providerName = EntityManager.GetComponent<MetaDataComponent>(action.Comp.Container.Value).EntityName;
return providerName.Contains(search, StringComparison.OrdinalIgnoreCase);
});
{
if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action))
{
- var entIcon = action.EntityIcon;
-
- if (entIcon != null)
+ if (action.EntityIcon is {} entIcon)
{
- _dragShadow.Texture = EntityManager.GetComponent<SpriteComponent>(entIcon.Value).Icon?
+ _dragShadow.Texture = EntityManager.GetComponent<SpriteComponent>(entIcon).Icon?
.GetFrame(RsiDirection.South, 0);
}
else if (action.Icon != null)
return;
var actions = _actionsSystem.GetClientActions().Where(action => action.Comp.AutoPopulate).ToList();
+ actions.Sort(ActionComparer);
var offset = 0;
var totalPages = _pages.Count;
action.Toggled = true;
// override "held-item" overlay
- var provider = action.Provider;
+ var provider = action.Container;
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
{
- if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Provider != null)
+ if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Container != null)
{
handOverlay.EntityOverride = provider;
}
public ActionButton this[int index]
{
get => (ActionButton) GetChild(index);
- set
- {
- AddChild(value);
- value.SetPositionInParent(index);
- value.ActionPressed += ActionPressed;
- value.ActionUnpressed += ActionUnpressed;
- value.ActionFocusExited += ActionFocusExited;
- }
}
public void SetActionData(params EntityUid?[] actionTypes)
button.ActionFocusExited += ActionFocusExited;
}
+ protected override void ChildRemoved(Control newChild)
+ {
+ if (newChild is not ActionButton button)
+ return;
+
+ button.ActionPressed -= ActionPressed;
+ button.ActionUnpressed -= ActionUnpressed;
+ button.ActionFocusExited -= ActionFocusExited;
+ }
+
public bool TryGetButtonIndex(ActionButton button, out int position)
{
if (button.Parent != this)
[RegisterComponent]
public sealed partial class ActionOnInteractComponent : Component
{
- [DataField("actions", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
- public List<string>? Actions;
+ [DataField(required:true)]
+ public List<EntProtoId>? Actions;
- [DataField("actionEntities")] public List<EntityUid>? ActionEntities;
+ [DataField] public List<EntityUid>? ActionEntities;
}
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
SubscribeLocalEvent<ActionOnInteractComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<ActionOnInteractComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<ActionOnInteractComponent, MapInitEvent>(OnMapInit);
+ }
+
+ private void OnMapInit(EntityUid uid, ActionOnInteractComponent component, MapInitEvent args)
+ {
+ if (component.Actions == null)
+ return;
+
+ var comp = EnsureComp<ActionsContainerComponent>(uid);
+ foreach (var id in component.Actions)
+ {
+ _actionContainer.AddAction(uid, id, comp);
+ }
}
private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args)
if (act.Event != null)
act.Event.Performer = args.User;
- act.Provider = uid;
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
args.Handled = true;
}
entAct.Event.Target = args.Target.Value;
}
- entAct.Provider = uid;
_actions.PerformAction(args.User, null, entActId, entAct, entAct.Event, _timing.CurTime, false);
args.Handled = true;
return;
act.Event.Target = args.ClickLocation;
}
- act.Provider = uid;
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
args.Handled = true;
}
[DataField("accumulatedFrametime")]
public float AccumulatedFrametime;
+
+ [DataField] public EntityUid? Action;
}
{
base.Initialize();
- SubscribeLocalEvent<EggLayerComponent, ComponentInit>(OnComponentInit);
+ SubscribeLocalEvent<EggLayerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<EggLayerComponent, EggLayInstantActionEvent>(OnEggLayAction);
}
}
}
- private void OnComponentInit(EntityUid uid, EggLayerComponent component, ComponentInit args)
+ private void OnMapInit(EntityUid uid, EggLayerComponent component, MapInitEvent args)
{
- if (string.IsNullOrWhiteSpace(component.EggLayAction))
- return;
-
- _actions.AddAction(uid, Spawn(component.EggLayAction), uid);
+ _actions.AddAction(uid, ref component.Action, component.EggLayAction);
component.CurrentEggLayCooldown = _random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax);
}
{
AddComp<HealOnBuckleHealingComponent>(uid);
component.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(component.HealTime);
- component.SleepAction = Spawn(SleepingSystem.SleepActionId);
- _actionsSystem.AddAction(args.BuckledEntity, component.SleepAction.Value, null);
+ _actionsSystem.AddAction(args.BuckledEntity, ref component.SleepAction, SleepingSystem.SleepActionId, uid);
return;
}
- if (component.SleepAction != null)
- _actionsSystem.RemoveAction(args.BuckledEntity, component.SleepAction.Value);
-
+ _actionsSystem.RemoveAction(args.BuckledEntity, component.SleepAction);
_sleepingSystem.TryWaking(args.BuckledEntity);
RemComp<HealOnBuckleHealingComponent>(uid);
}
public sealed class SleepingSystem : SharedSleepingSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
- [Dependency] private readonly ActionsSystem _actionsSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
var tryingToSleepEvent = new TryingToSleepEvent(uid);
RaiseLocalEvent(uid, ref tryingToSleepEvent);
- if (tryingToSleepEvent.Cancelled) return false;
-
- _actionsSystem.RemoveAction(uid, SleepActionId);
+ if (tryingToSleepEvent.Cancelled)
+ return false;
EnsureComp<SleepingComponent>(uid);
return true;
{
base.Initialize();
- SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
+ SubscribeLocalEvent<DragonComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
_audioSystem.Play(component.SoundRoar, Filter.Pvs(component.Owner, 4f, EntityManager), component.Owner, true, component.SoundRoar.Params);
}
- private void OnStartup(EntityUid uid, DragonComponent component, ComponentStartup args)
+ private void OnInit(EntityUid uid, DragonComponent component, MapInitEvent args)
{
Roar(component);
_actionsSystem.AddAction(uid, ref component.SpawnRiftActionEntity, component.SpawnRiftAction);
public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
- SubscribeLocalEvent<ZombifyOnDeathComponent, ZombifySelfActionEvent>(OnZombifySelf);
+ SubscribeLocalEvent<PendingZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
}
private void OnRoundEndText(RoundEndTextAppendEvent ev)
InfectInitialPlayers(component);
}
- private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
+ private void OnZombifySelf(EntityUid uid, PendingZombieComponent component, ZombifySelfActionEvent args)
{
_zombie.ZombifyEntity(uid);
- _action.RemoveAction(uid, ZombieRuleComponent.ZombifySelfActionPrototype);
+ if (component.Action != null)
+ Del(component.Action.Value);
}
private float GetInfectedFraction(bool includeOffStation = true, bool includeDead = false)
EnsureComp<ZombifyOnDeathComponent>(ownedEntity);
EnsureComp<IncurableZombieComponent>(ownedEntity);
var inCharacterName = MetaData(ownedEntity).EntityName;
- var action = Spawn(ZombieRuleComponent.ZombifySelfActionPrototype);
- _action.AddAction(mind.OwnedEntity.Value, action, null);
+ _action.AddAction(ownedEntity, ref pending.Action, ZombieRuleComponent.ZombifySelfActionPrototype, ownedEntity);
var message = Loc.GetString("zombie-patientzero-role-greeting");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
base.Initialize();
SubscribeLocalEvent<GhostComponent, ComponentStartup>(OnGhostStartup);
+ SubscribeLocalEvent<GhostComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<GhostComponent, ComponentShutdown>(OnGhostShutdown);
SubscribeLocalEvent<GhostComponent, ExaminedEvent>(OnGhostExamine);
var time = _gameTiming.CurTime;
component.TimeOfDeath = time;
-
- // TODO ghost: remove once ghosts are persistent and aren't deleted when returning to body
- 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 OnGhostShutdown(EntityUid uid, GhostComponent component, ComponentShutdown args)
// Entity can't see ghosts anymore.
SetCanSeeGhosts(uid, false);
-
- _actions.RemoveAction(uid, component.ActionEntity);
+ _actions.RemoveAction(uid, component.BooActionEntity);
}
private void SetCanSeeGhosts(EntityUid uid, bool canSee, EyeComponent? eyeComponent = null)
_eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask & ~(int) VisibilityFlags.Ghost, eyeComponent);
}
+ private void OnMapInit(EntityUid uid, GhostComponent component, MapInitEvent args)
+ {
+ if (_actions.AddAction(uid, ref component.BooActionEntity, out var act, component.BooAction)
+ && act.UseDelay != null)
+ {
+ var start = _gameTiming.CurTime;
+ var end = start + act.UseDelay.Value;
+ _actions.SetCooldown(component.BooActionEntity.Value, start, end);
+ }
+
+ _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);
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ActionsSystem _actions = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private void OnMapInit(EntityUid uid, HandheldLightComponent component, MapInitEvent args)
{
- _actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
+ _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
}
private void OnShutdown(EntityUid uid, HandheldLightComponent component, ComponentShutdown args)
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<SpellbookComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<SpellbookComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<SpellbookComponent, SpellbookDoAfterEvent>(OnDoAfter);
if (args.Handled || args.Cancelled)
return;
- _actionsSystem.AddActions(args.Args.User, component.Spells, component.LearnPermanently ? null : uid);
args.Handled = true;
+ if (!component.LearnPermanently)
+ {
+ _actionsSystem.GrantActions(args.Args.User, component.Spells, uid);
+ return;
+ }
+
+ foreach (var (id, charges) in component.SpellActions)
+ {
+ EntityUid? actionId = null;
+ if (_actionsSystem.AddAction(uid, ref actionId, id))
+ _actionsSystem.SetCharges(actionId, charges < 0 ? null : charges);
+ }
+
+ component.SpellActions.Clear();
}
- private void OnInit(EntityUid uid, SpellbookComponent component, ComponentInit args)
+ private void OnInit(EntityUid uid, SpellbookComponent component, MapInitEvent args)
{
- //Negative charges means the spell can be used without it running out.
+ if (!component.LearnPermanently)
+ return;
+
foreach (var (id, charges) in component.SpellActions)
{
- var spell = Spawn(id);
+ var spell = _actionContainer.AddAction(uid, id);
+ if (spell == null)
+ continue;
+
_actionsSystem.SetCharges(spell, charges < 0 ? null : charges);
- component.Spells.Add(spell);
+ component.Spells.Add(spell.Value);
}
}
/// </summary>
[DataField("time")]
public float Time;
+
+ [DataField] public EntityUid? Action;
}
}
+using System.Linq;
using Content.Server.Actions;
using Content.Server.Humanoid;
using Content.Server.Inventory;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ActionsSystem _actions = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedBuckleSystem _buckle = default!;
[Dependency] private readonly ContainerSystem _container = default!;
SubscribeLocalEvent<PolymorphableComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<PolymorphableComponent, PolymorphActionEvent>(OnPolymorphActionEvent);
- SubscribeLocalEvent<PolymorphedEntityComponent, ComponentStartup>(OnStartup);
+ SubscribeLocalEvent<PolymorphedEntityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
SubscribeLocalEvent<PolymorphedEntityComponent, RevertPolymorphActionEvent>(OnRevertPolymorphActionEvent);
Revert(uid, component);
}
- public void OnStartup(EntityUid uid, PolymorphedEntityComponent component, ComponentStartup args)
+ private void OnMapInit(EntityUid uid, PolymorphedEntityComponent component, MapInitEvent args)
{
if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto))
{
if (proto.Forced)
return;
- var actionId = Spawn(RevertPolymorphId);
- if (_actions.TryGetActionData(actionId, out var action))
+ if (_actions.AddAction(uid, ref component.Action, out var action, RevertPolymorphId))
{
action.EntityIcon = component.Parent;
action.UseDelay = TimeSpan.FromSeconds(proto.Delay);
}
-
- _actions.AddAction(uid, actionId, null, null, action);
}
private void OnBeforeFullyEaten(EntityUid uid, PolymorphedEntityComponent comp, BeforeFullyEatenEvent args)
if (!TryComp<PolymorphableComponent>(target, out var polycomp))
return;
+ polycomp.PolymorphActions ??= new Dictionary<string, EntityUid>();
+ if (polycomp.PolymorphActions.ContainsKey(id))
+ return;
+
var entproto = _proto.Index<EntityPrototype>(polyproto.Entity);
- var actionId = Spawn(RevertPolymorphId);
- if (_actions.TryGetActionData(actionId, out var baseAction) &&
- baseAction is InstantActionComponent action)
- {
- 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 Dictionary<string, EntityUid>();
- polycomp.PolymorphActions.Add(id, actionId);
- _actions.AddAction(target, actionId, target);
- }
+ EntityUid? actionId = default!;
+ if (!_actions.AddAction(target, ref actionId, RevertPolymorphId, target))
+ return;
+
+ polycomp.PolymorphActions.Add(id, actionId.Value);
+ _metaData.SetEntityName(actionId.Value, Loc.GetString("polymorph-self-action-name", ("target", entproto.Name)));
+ _metaData.SetEntityDescription(actionId.Value, Loc.GetString("polymorph-self-action-description", ("target", entproto.Name)));
+
+ if (!_actions.TryGetActionData(actionId, out var baseAction))
+ return;
+
+ baseAction.Icon = new SpriteSpecifier.EntityPrototype(polyproto.Entity);
+ if (baseAction is InstantActionComponent action)
+ action.Event = new PolymorphActionEvent { Prototype = polyproto };
}
[PublicAPI]
private void OnMapInit(EntityUid uid, RevenantComponent component, MapInitEvent args)
{
- _action.AddAction(uid, Spawn(RevenantShopId), null);
+ _action.AddAction(uid, ref component.Action, RevenantShopId);
}
private void OnStatusAdded(EntityUid uid, RevenantComponent component, StatusEffectAddedEvent args)
{
var chassis = args.ChassisEnt;
- var action = _actions.AddAction(chassis, ref component.ModuleSwapActionEntity, component.ModuleSwapActionId, uid);
- if (action != null)
+ if (_actions.AddAction(chassis, ref component.ModuleSwapActionEntity, out var action, component.ModuleSwapActionId, uid))
{
action.EntityIcon = uid;
- Dirty(component.ModuleSwapActionEntity!.Value, action);
+ Dirty(component.ModuleSwapActionEntity.Value, action);
}
if (!TryComp(chassis, out BorgChassisComponent? chassisComp))
//give action
if (!string.IsNullOrWhiteSpace(listing.ProductAction))
{
- _actions.AddAction(buyer, Spawn(listing.ProductAction), null);
+ // I guess we just allow duplicate actions?
+ _actions.AddAction(buyer, listing.ProductAction);
}
//broadcast event
public override void Initialize()
{
- SubscribeLocalEvent<IntrinsicUIComponent, ComponentStartup>(OnGetActions);
+ SubscribeLocalEvent<IntrinsicUIComponent, MapInitEvent>(InitActions);
SubscribeLocalEvent<IntrinsicUIComponent, ToggleIntrinsicUIEvent>(OnActionToggle);
}
args.Handled = InteractUI(uid, args.Key, component);
}
- private void OnGetActions(EntityUid uid, IntrinsicUIComponent component, ComponentStartup args)
+ private void InitActions(EntityUid uid, IntrinsicUIComponent component, MapInitEvent args)
{
- if (!TryComp<ActionsComponent>(uid, out var actions))
- return;
-
foreach (var entry in component.UIs)
{
- _actionsSystem.AddAction(uid, ref entry.ToggleActionEntity, entry.ToggleAction, null, actions);
+ _actionsSystem.AddAction(uid, ref entry.ToggleActionEntity, entry.ToggleAction);
}
}
"zombie-infection-warning",
"zombie-infection-underway"
};
+
+ [DataField] public EntityUid? Action;
}
--- /dev/null
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Actions;
+
+/// <summary>
+/// This component indicates that this entity contains actions inside of some container.
+/// </summary>
+[NetworkedComponent, RegisterComponent]
+[Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
+public sealed partial class ActionsContainerComponent : Component
+{
+ public const string ContainerId = "actions";
+
+ [ViewVariables]
+ public Container Container = default!;
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Actions;
+
+/// <summary>
+/// Handles storing & spawning action entities in a container.
+/// </summary>
+public sealed class ActionContainerSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly INetManager _netMan = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ActionsContainerComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<ActionsContainerComponent, ComponentShutdown>(OnShutdown);
+ SubscribeLocalEvent<ActionsContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
+ SubscribeLocalEvent<ActionsContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
+ }
+
+ /// <summary>
+ /// Spawns a new action entity and adds it to the given container.
+ /// </summary>
+ public EntityUid? AddAction(EntityUid uid, string actionPrototypeId, ActionsContainerComponent? comp = null)
+ {
+ EntityUid? result = default;
+ EnsureAction(uid, ref result, actionPrototypeId, comp);
+ return result;
+ }
+
+ /// <summary>
+ /// Ensures that a given entityUid refers to a valid entity action contained by the given container.
+ /// If the entity does not exist, it will attempt to spawn a new action.
+ /// Returns false if the given entity exists, but is not in a valid state.
+ /// </summary>
+ public bool EnsureAction(EntityUid uid,
+ [NotNullWhen(true)] ref EntityUid? actionId,
+ string actionPrototypeId,
+ ActionsContainerComponent? comp = null)
+ {
+ return EnsureAction(uid, ref actionId, out _, actionPrototypeId, comp);
+ }
+
+ /// <inheritdoc cref="EnsureAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Content.Shared.Actions.ActionsContainerComponent?)"/>
+ public bool EnsureAction(EntityUid uid,
+ [NotNullWhen(true)] ref EntityUid? actionId,
+ [NotNullWhen(true)] out BaseActionComponent? action,
+ string? actionPrototypeId,
+ ActionsContainerComponent? comp = null)
+ {
+ action = null;
+
+ DebugTools.Assert(comp == null || comp.Owner == uid);
+ comp ??= EnsureComp<ActionsContainerComponent>(uid);
+
+ if (Exists(actionId))
+ {
+ if (!comp.Container.Contains(actionId.Value))
+ {
+ Log.Error($"Action {ToPrettyString(actionId.Value)} is not contained in the expected container {ToPrettyString(uid)}");
+ return false;
+ }
+
+ if (!_actions.TryGetActionData(actionId, out action))
+ return false;
+
+ DebugTools.Assert(Transform(actionId.Value).ParentUid == uid);
+ DebugTools.Assert(_container.IsEntityInContainer(actionId.Value));
+ DebugTools.Assert(action.Container == uid);
+ return true;
+ }
+
+ // Null prototypes are never valid entities, they mean that someone didn't provide a proper prototype.
+ if (actionPrototypeId == null)
+ return false;
+
+ // Client cannot predict entity spawning.
+ if (_netMan.IsClient && !IsClientSide(uid))
+ return false;
+
+ actionId = Spawn(actionPrototypeId);
+ if (AddAction(uid, actionId.Value, action, comp) && _actions.TryGetActionData(actionId, out action))
+ return true;
+
+ Del(actionId.Value);
+ actionId = null;
+ return false;
+ }
+
+ /// <summary>
+ /// Adds a pre-existing action to an action container.
+ /// </summary>
+ public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? action = null, ActionsContainerComponent? comp = null)
+ {
+ if (!_actions.ResolveActionData(actionId, ref action))
+ return false;
+
+ if (action.Container != null)
+ {
+ Log.Error($"Attempted to insert an action {ToPrettyString(actionId)} that was already in a container {ToPrettyString(action.Container.Value)}");
+ return false;
+ }
+
+ DebugTools.Assert(comp == null || comp.Owner == uid);
+ comp ??= EnsureComp<ActionsContainerComponent>(uid);
+ if (!comp.Container.Insert(actionId))
+ {
+ Log.Error($"Failed to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}");
+ return false;
+ }
+
+ // Container insert events should have updated the component's fields:
+ DebugTools.Assert(comp.Container.Contains(actionId));
+ DebugTools.Assert(action.Container == uid);
+
+ return true;
+ }
+
+ private void OnInit(EntityUid uid, ActionsContainerComponent component, ComponentInit args)
+ {
+ component.Container = _container.EnsureContainer<Container>(uid, ActionsContainerComponent.ContainerId);
+ }
+
+ private void OnShutdown(EntityUid uid, ActionsContainerComponent component, ComponentShutdown args)
+ {
+ component.Container.Shutdown();
+ }
+
+ private void OnEntityInserted(EntityUid uid, ActionsContainerComponent component, EntInsertedIntoContainerMessage args)
+ {
+ if (args.Container.ID != ActionsContainerComponent.ContainerId)
+ return;
+
+ if (!_actions.TryGetActionData(args.Entity, out var data))
+ return;
+
+ DebugTools.Assert(data.AttachedEntity == null || data.Container != EntityUid.Invalid);
+ DebugTools.Assert(data.Container == null || data.Container == uid);
+
+ data.Container = uid;
+ Dirty(uid, component);
+
+ var ev = new ActionAddedEvent(args.Entity, data);
+ RaiseLocalEvent(uid, ref ev);
+ }
+
+ private void OnEntityRemoved(EntityUid uid, ActionsContainerComponent component, EntRemovedFromContainerMessage args)
+ {
+ if (args.Container.ID != ActionsContainerComponent.ContainerId)
+ return;
+
+ // Actions should only be getting removed while terminating or moving outside of PVS range.
+ DebugTools.Assert(Terminating(args.Entity)
+ || _netMan.IsServer // I love gibbing code
+ || _timing.ApplyingState);
+
+ if (!_actions.TryGetActionData(args.Entity, out var data, false))
+ return;
+
+ // No event - the only entity that should care about this is the entity that the action was provided to.
+ if (data.AttachedEntity != null)
+ _actions.RemoveAction(data.AttachedEntity.Value, args.Entity, null, data);
+
+ var ev = new ActionRemovedEvent(args.Entity, data);
+ RaiseLocalEvent(uid, ref ev);
+
+ if (_netMan.IsServer)
+ {
+ // TODO Actions
+ // log an error or warning here once gibbing code is fixed.
+ QueueDel(uid);
+ }
+ }
+}
+
+/// <summary>
+/// Raised directed at an action container when a new action entity gets inserted.
+/// </summary>
+[ByRefEvent]
+public readonly struct ActionAddedEvent
+{
+ public readonly EntityUid Action;
+ public readonly BaseActionComponent Component;
+
+ public ActionAddedEvent(EntityUid action, BaseActionComponent component)
+ {
+ Action = action;
+ Component = component;
+ }
+}
+
+/// <summary>
+/// Raised directed at an action container when an action entity gets removed.
+/// </summary>
+[ByRefEvent]
+public readonly struct ActionRemovedEvent
+{
+ public readonly EntityUid Action;
+ public readonly BaseActionComponent Component;
+
+ public ActionRemovedEvent(EntityUid action, BaseActionComponent component)
+ {
+ Action = action;
+ Component = component;
+ }
+}
\ No newline at end of file
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;
/// </remarks>
public sealed class GetItemActionsEvent : EntityEventArgs
{
- private readonly IEntityManager _entities;
- private readonly INetManager _net;
+ private readonly ActionContainerSystem _system;
public readonly SortedSet<EntityUid> Actions = new();
/// <summary>
/// </summary>
public EntityUid User;
+ /// <summary>
+ /// The entity that is being asked to provide the actions. This is used as a default argument to <see cref="AddAction(ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string,Robust.Shared.GameObjects.EntityUid)"/>.
+ /// I.e., if a new action needs to be spawned, then it will be inserted into this entity unless otherwise specified.
+ /// </summary>
+ public EntityUid Provider;
+
/// <summary>
/// Slot flags for the inventory slot that this item got equipped to. Null if not in a slot (i.e., if equipped to hands).
/// </summary>
/// </summary>
public bool InHands => SlotFlags == null;
- public GetItemActionsEvent(IEntityManager entities, INetManager net, EntityUid user, SlotFlags? slotFlags = null)
+ public GetItemActionsEvent(ActionContainerSystem system, EntityUid user, EntityUid provider, SlotFlags? slotFlags = null)
{
- _entities = entities;
- _net = net;
+ _system = system;
User = user;
+ Provider = provider;
SlotFlags = slotFlags;
}
- public void AddAction(ref EntityUid? actionId, string? prototypeId)
+ /// <summary>
+ /// Grant the given action. If the EntityUid does not refer to a valid action entity, it will create a new action and
+ /// store it in <see cref="container"/>.
+ /// </summary>
+ public void AddAction(ref EntityUid? actionId, string prototypeId, EntityUid container)
{
- if (_entities.Deleted(actionId))
- {
- if (string.IsNullOrWhiteSpace(prototypeId) || _net.IsClient)
- return;
+ if (_system.EnsureAction(container, ref actionId, prototypeId))
+ Actions.Add(actionId.Value);
+ }
- actionId = _entities.Spawn(prototypeId);
- }
+ /// <summary>
+ /// Grant the given action. If the EntityUid does not refer to a valid action entity, it will create a new action and
+ /// store it in <see cref="Provider"/>.
+ /// </summary>
+ public void AddAction(ref EntityUid? actionId, string prototypeId)
+ {
+ AddAction(ref actionId, prototypeId, Provider);
+ }
- Actions.Add(actionId.Value);
+ public void AddAction(EntityUid actionId)
+ {
+ Actions.Add(actionId);
}
}
public sealed partial class ActionsComponent : Component
{
/// <summary>
- /// Handled on the client to track added and removed actions.
+ /// List of actions currently granted to this entity.
+ /// On the client, this may contain a mixture of client-side and networked entities.
/// </summary>
- [ViewVariables] public readonly Dictionary<EntityUid, ActionMetaData> OldClientActions = new();
-
- [ViewVariables] public readonly HashSet<EntityUid> Actions = new();
-
- public override bool SendOnlyToOwner => true;
+ [DataField] public HashSet<EntityUid> Actions = new();
}
[Serializable, NetSerializable]
}
}
-public readonly record struct ActionMetaData(bool ClientExclusive, bool AutoRemove);
+public readonly record struct ActionMetaData(bool ClientExclusive);
/// <summary>
/// Determines how the action icon appears in the hotbar for item actions.
namespace Content.Shared.Actions;
// TODO this should be an IncludeDataFields of each action component type, not use inheritance
+
+// TODO add access attribute. Need to figure out what to do with decal & mapping actions.
+// [Access(typeof(SharedActionsSystem))]
public abstract partial class BaseActionComponent : Component
{
public abstract BaseActionEvent? BaseEvent { get; }
/// The toggle can set directly via <see cref="SharedActionsSystem.SetToggled"/>, but it will also be
/// automatically toggled for targeted-actions while selecting a target.
/// </remarks>
+ [DataField]
public bool Toggled;
/// <summary>
/// The current cooldown on the action.
/// </summary>
+ // TODO serialization
public (TimeSpan Start, TimeSpan End)? Cooldown;
/// <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.
+ /// The entity that contains this action. If the action is innate, this may be the user themselves.
+ /// This should almost always be non-null.
/// </summary>
- public EntityUid? Provider;
+ [Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
+ [DataField]
+ public EntityUid? Container;
/// <summary>
- /// Entity to use for the action icon. Defaults to using <see cref="Provider"/>.
+ /// Entity to use for the action icon. If no entity is provided and the <see cref="Container"/> differs from
+ /// <see cref="AttachedEntity"/>, then it will default to using <see cref="Container"/>
/// </summary>
public EntityUid? EntityIcon
{
- get => _entityIcon ?? Provider;
- set => _entityIcon = value;
+ get
+ {
+ if (EntIcon != null)
+ return EntIcon;
+
+ if (AttachedEntity != Container)
+ return Container;
+
+ return null;
+ }
+ set => EntIcon = value;
}
- private EntityUid? _entityIcon;
+ [DataField]
+ public EntityUid? EntIcon;
/// <summary>
/// Whether the action system should block this action if the user cannot currently interact. Some spells or
[DataField("checkCanInteract")] public bool CheckCanInteract = true;
/// <summary>
- /// If true, will simply execute the action locally without sending to the server.
+ /// If true, this will cause the action to only execute locally without ever notifying the server.
/// </summary>
[DataField("clientExclusive")] public bool ClientExclusive = false;
/// </summary>
[DataField("autoPopulate")] public bool AutoPopulate = true;
-
/// <summary>
- /// Whether or not to automatically remove this action to the action bar when it becomes unavailable.
+ /// Temporary actions are deleted when they get removed a <see cref="ActionsComponent"/>.
/// </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.
public (TimeSpan Start, TimeSpan End)? Cooldown;
public TimeSpan? UseDelay;
public int? Charges;
- public NetEntity? Provider;
+ public NetEntity? Container;
public NetEntity? EntityIcon;
public bool CheckCanInteract;
public bool ClientExclusive;
public int Priority;
public NetEntity? AttachedEntity;
public bool AutoPopulate;
- public bool AutoRemove;
public bool Temporary;
public ItemActionIconStyle ItemIconStyle;
public SoundSpecifier? Sound;
protected BaseActionComponentState(BaseActionComponent component, IEntityManager entManager)
{
+ Container = entManager.GetNetEntity(component.Container);
+ EntityIcon = entManager.GetNetEntity(component.EntIcon);
+ AttachedEntity = entManager.GetNetEntity(component.AttachedEntity);
Icon = component.Icon;
IconOn = component.IconOn;
IconColor = component.IconColor;
Cooldown = component.Cooldown;
UseDelay = component.UseDelay;
Charges = component.Charges;
-
- // TODO ACTION REFACTOR fix bugs
- if (entManager.TryGetNetEntity(component.Provider, out var provider))
- Provider = provider;
- if (entManager.TryGetNetEntity(component.EntityIcon, out var icon))
- EntityIcon = icon;
- if (entManager.TryGetNetEntity(component.AttachedEntity, out var attached))
- AttachedEntity = attached;
-
CheckCanInteract = component.CheckCanInteract;
ClientExclusive = component.ClientExclusive;
Priority = component.Priority;
AutoPopulate = component.AutoPopulate;
- AutoRemove = component.AutoRemove;
Temporary = component.Temporary;
ItemIconStyle = component.ItemIconStyle;
Sound = component.Sound;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
-using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
public abstract class SharedActionsSystem : EntitySystem
{
- private const string ActionContainerId = "ActionContainer";
- private const string ProvidedActionContainerId = "ProvidedActionContainer";
-
[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!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
SubscribeLocalEvent<ActionsComponent, DidUnequipEvent>(OnDidUnequip);
SubscribeLocalEvent<ActionsComponent, DidUnequipHandEvent>(OnHandUnequipped);
- 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);
- SubscribeLocalEvent<InstantActionComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
- SubscribeLocalEvent<EntityTargetActionComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
- SubscribeLocalEvent<WorldTargetActionComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
-
SubscribeAllEvent<RequestPerformActionEvent>(OnActionRequest);
}
args.State = new WorldTargetActionComponentState(component, EntityManager);
}
- private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
- {
- 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 = EnsureEntity<T>(state.Provider, uid);
- component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
- component.CheckCanInteract = state.CheckCanInteract;
- component.ClientExclusive = state.ClientExclusive;
- component.Priority = state.Priority;
- component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
- 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<InstantActionComponent>(uid, component, state);
- }
-
- private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not EntityTargetActionComponentState state)
- return;
-
- BaseHandleState<EntityTargetActionComponent>(uid, 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<WorldTargetActionComponent>(uid, component, state);
- }
-
private void OnGetActionData<T>(EntityUid uid, T component, ref GetActionDataEvent args) where T : BaseActionComponent
{
args.Action = component;
}
- private void OnEntGotRemovedFromContainer<T>(EntityUid uid, T component, EntGotRemovedFromContainerMessage args) where T : BaseActionComponent
- {
- if (args.Container.ID != ProvidedActionContainerId)
- return;
-
- if (TryComp(component.AttachedEntity, out ActionsComponent? actions))
- {
- actions.Actions.Remove(uid);
- Dirty(component.AttachedEntity.Value, actions);
-
- if (TryGetActionData(uid, out var action))
- action.AttachedEntity = null;
- }
- }
-
- public BaseActionComponent? GetActionData(EntityUid? actionId)
+ public bool TryGetActionData(
+ [NotNullWhen(true)] EntityUid? uid,
+ [NotNullWhen(true)] out BaseActionComponent? result,
+ bool logError = true)
{
- if (actionId == null)
- return null;
+ result = null;
+ if (!Exists(uid))
+ return false;
- // 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;
- }
+ RaiseLocalEvent(uid.Value, ref ev);
+ result = ev.Action;
- public bool TryGetActionData(
- [NotNullWhen(true)] EntityUid? actionId,
- [NotNullWhen(true)] out BaseActionComponent? action)
- {
- action = null;
- return actionId != null && (action = GetActionData(actionId)) != null;
- }
+ if (result != null)
+ return true;
- protected Container EnsureContainer(EntityUid holderId, EntityUid? providerId)
- {
- return providerId == null
- ? _containerSystem.EnsureContainer<Container>(holderId, ActionContainerId)
- : _containerSystem.EnsureContainer<Container>(providerId.Value, ProvidedActionContainerId);
+ Log.Error($"Failed to get action from action entity: {ToPrettyString(uid.Value)}");
+ return false;
}
- protected bool TryGetContainer(
- EntityUid holderId,
- [NotNullWhen(true)] out BaseContainer? container,
- ContainerManagerComponent? containerManager = null)
+ public bool ResolveActionData(
+ [NotNullWhen(true)] EntityUid? uid,
+ [NotNullWhen(true)] ref BaseActionComponent? result,
+ bool logError = true)
{
- return _containerSystem.TryGetContainer(holderId, ActionContainerId, out container, containerManager);
- }
+ if (result != null)
+ {
+ DebugTools.Assert(result.Owner == uid);
+ return true;
+ }
- protected bool TryGetProvidedContainer(
- EntityUid providerId,
- [NotNullWhen(true)] out BaseContainer? container,
- ContainerManagerComponent? containerManager = null)
- {
- return _containerSystem.TryGetContainer(providerId, ProvidedActionContainerId, out container, containerManager);
+ return TryGetActionData(uid, out result, logError);
}
public void SetCooldown(EntityUid? actionId, TimeSpan start, TimeSpan end)
if (actionId == null)
return;
- var action = GetActionData(actionId);
- if (action == null)
+ if (!TryGetActionData(actionId, out var action))
return;
action.Cooldown = (start, end);
}
#region ComponentStateManagement
- public virtual void Dirty(EntityUid? actionId)
+ protected virtual void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
- if (!TryGetActionData(actionId, out var action))
- return;
-
- Dirty(actionId.Value, action);
-
- if (action.AttachedEntity == null)
- return;
-
- var ent = action.AttachedEntity;
-
- if (!TryComp(ent, out ActionsComponent? comp))
- {
- action.AttachedEntity = null;
- return;
- }
-
- Dirty(action.AttachedEntity.Value, comp);
+ // See client-side code.
}
public void SetToggled(EntityUid? actionId, bool toggled)
}
action.Toggled = toggled;
+ UpdateAction(actionId, action);
Dirty(actionId.Value, action);
}
}
action.Enabled = enabled;
+ UpdateAction(actionId, action);
Dirty(actionId.Value, action);
}
}
action.Charges = charges;
+ UpdateAction(actionId, action);
Dirty(actionId.Value, action);
}
- private void OnActionsMapInit(EntityUid uid, ActionsComponent component, MapInitEvent args)
- {
- EnsureContainer(uid, null);
- }
-
private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
{
args.State = new ActionsComponentState(GetNetEntitySet(component.Actions));
}
- private void OnActionsShutdown(EntityUid uid, ActionsComponent component, ComponentShutdown args)
- {
- if (TryGetContainer(uid, out var container))
- container.Shutdown(EntityManager);
- }
-
#endregion
#region Execution
return;
}
- var action = GetActionData(actionEnt);
- if (action == null || !action.Enabled)
+ if (!TryGetActionData(actionEnt, out var action))
+ return;
+
+ DebugTools.Assert(action.AttachedEntity == user);
+
+ if (!action.Enabled)
return;
var curTime = GameTiming.CurTime;
if (!ValidateEntityTarget(user, entityTarget, entityAction))
return;
- if (action.Provider == null)
- {
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action targeted at {ToPrettyString(entityTarget):target}.");
- }
- else
- {
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}.");
- }
+ _adminLogger.Add(LogType.Action,
+ $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {ToPrettyString(entityTarget):target}.");
if (entityAction.Event != null)
{
if (!ValidateWorldTarget(user, entityCoordinatesTarget, worldAction))
return;
- if (action.Provider == null)
- {
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action targeted at {entityCoordinatesTarget:target}.");
- }
- else
- {
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}.");
- }
+ _adminLogger.Add(LogType.Action,
+ $"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {entityCoordinatesTarget:target}.");
if (worldAction.Event != null)
{
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
return;
- if (action.Provider == null)
- {
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action.");
- }
- else
- {
- _adminLogger.Add(LogType.Action,
- $"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Provider.Value):provider}.");
- }
+ _adminLogger.Add(LogType.Action,
+ $"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Container ?? user):provider}.");
performEvent = instantAction.Event;
break;
var toggledBefore = action.Toggled;
+ // Note that attached entity is allowed to be null here.
+ if (action.AttachedEntity != null && action.AttachedEntity != performer)
+ {
+ Log.Error($"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(actionId)} that is attached to another entity {ToPrettyString(action.AttachedEntity.Value)}");
+ return;
+ }
+
if (actionEvent != null)
{
// This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
actionEvent.Handled = false;
- var provider = action.Provider;
-
- if (provider == null)
- RaiseLocalEvent(performer, (object) actionEvent, broadcast: true);
- else
- RaiseLocalEvent(provider.Value, (object) actionEvent, broadcast: true);
-
+ RaiseLocalEvent(action.Container ?? performer, (object) actionEvent, broadcast: true);
handled = actionEvent.Handled;
}
#endregion
#region AddRemoveActions
+
+ public EntityUid? AddAction(EntityUid performer,
+ string? actionPrototypeId,
+ EntityUid container = default,
+ ActionsComponent? component = null)
+ {
+ EntityUid? actionId = null;
+ AddAction(performer, ref actionId, out _, actionPrototypeId, container, component);
+ return actionId;
+ }
+
/// <summary>
- /// Add an action to an action holder.
+ /// Adds an action to an action holder. If the given entity does not exist, it will attempt to spawn one.
/// If the holder has no actions component, this will give them one.
/// </summary>
- public BaseActionComponent? AddAction(EntityUid holderId, ref EntityUid? actionId, string? actionPrototypeId, EntityUid? provider = null, ActionsComponent? holderComp = null)
- {
- if (Deleted(actionId))
- {
- if (_net.IsClient)
- return null;
-
- if (string.IsNullOrWhiteSpace(actionPrototypeId))
- return null;
-
- actionId = Spawn(actionPrototypeId);
- }
+ /// <param name="performer">Entity to receive the actions</param>
+ /// <param name="actionId">Action entity to add</param>
+ /// <param name="component">The <see cref="performer"/>'s action component of </param>
+ /// <param name="actionPrototypeId">The action entity prototype id to use if <see cref="actionId"/> is invalid.</param>
+ /// <param name="container">The entity that contains/enables this action (e.g., flashlight)..</param>
+ public bool AddAction(EntityUid performer,
+ [NotNullWhen(true)] ref EntityUid? actionId,
+ string? actionPrototypeId,
+ EntityUid container = default,
+ ActionsComponent? component = null)
+ {
+ return AddAction(performer, ref actionId, out _, actionPrototypeId, container, component);
+ }
+
+ /// <inheritdoc cref="AddAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Robust.Shared.GameObjects.EntityUid,Content.Shared.Actions.ActionsComponent?)"/>
+ public bool AddAction(EntityUid performer,
+ [NotNullWhen(true)] ref EntityUid? actionId,
+ [NotNullWhen(true)] out BaseActionComponent? action,
+ string? actionPrototypeId,
+ EntityUid container = default,
+ ActionsComponent? component = null)
+ {
+ if (!container.IsValid())
+ container = performer;
+
+ if (!_actionContainer.EnsureAction(container, ref actionId, out action, actionPrototypeId))
+ return false;
- AddAction(holderId, actionId.Value, provider, holderComp);
- return GetActionData(actionId);
+ return AddActionDirect(performer, actionId.Value, component, action);
}
/// <summary>
- /// Add an action to an action holder.
- /// If the holder has no actions component, this will give them one.
+ /// Adds a pre-existing action.
/// </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, BaseContainer? actionContainer = null)
- {
- action ??= GetActionData(actionId);
- // TODO remove when action subscriptions are split up
- if (action == null)
+ public bool AddAction(EntityUid performer,
+ EntityUid actionId,
+ EntityUid container,
+ ActionsComponent? comp = null,
+ BaseActionComponent? action = null,
+ ActionsContainerComponent? containerComp = null
+ )
+ {
+ if (!ResolveActionData(actionId, ref action))
+ return false;
+
+ if (action.Container != container
+ || !Resolve(container, ref containerComp)
+ || !containerComp.Container.Contains(actionId))
{
- Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}");
- return;
+ Log.Error($"Attempted to add an action with an invalid container: {ToPrettyString(actionId)}");
+ return false;
}
- holder ??= EnsureComp<ActionsComponent>(holderId);
- action.Provider = provider;
- action.AttachedEntity = holderId;
- Dirty(actionId, action);
+ return AddActionDirect(performer, actionId, comp, action);
+ }
- actionContainer ??= EnsureContainer(holderId, provider);
- AddActionInternal(holderId, actionId, actionContainer, holder);
+ /// <summary>
+ /// Adds a pre-existing action. This also bypasses the requirement that the given action must be stored in a
+ /// valid action container.
+ /// </summary>
+ public bool AddActionDirect(EntityUid performer,
+ EntityUid actionId,
+ ActionsComponent? comp = null,
+ BaseActionComponent? action = null)
+ {
+ if (!ResolveActionData(actionId, ref action))
+ return false;
+
+ DebugTools.Assert(action.Container == null ||
+ (TryComp(action.Container, out ActionsContainerComponent? containerComp)
+ && containerComp.Container.Contains(actionId)));
- if (dirty)
- Dirty(holderId, holder);
+ DebugTools.Assert(comp == null || comp.Owner == performer);
+ comp ??= EnsureComp<ActionsComponent>(performer);
+ action.AttachedEntity = performer;
+ comp.Actions.Add(actionId);
+ Dirty(actionId, action);
+ Dirty(performer, comp);
+ ActionAdded(performer, actionId, comp, action);
+ return true;
}
- protected virtual void AddActionInternal(EntityUid holderId, EntityUid actionId, BaseContainer container, ActionsComponent holder)
+ /// <summary>
+ /// This method gets called after a new action got added.
+ /// </summary>
+ protected virtual void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
{
- container.Insert(actionId);
- holder.Actions.Add(actionId);
- Dirty(holderId, holder);
+ // See client-side system for UI code.
}
/// <summary>
- /// Add actions to an action component. If the entity has no action component, this will give them one.
+ /// Grant pre-existing actions. If the entity has no action component, this will give them one.
/// </summary>
- /// <param name="holderId">Entity to receive the actions</param>
+ /// <param name="performer">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 holderId, IEnumerable<EntityUid> actions, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
+ /// <param name="container">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
+ public void GrantActions(EntityUid performer, IEnumerable<EntityUid> actions, EntityUid container, ActionsComponent? comp = null, ActionsContainerComponent? containerComp = null)
{
- comp ??= EnsureComp<ActionsComponent>(holderId);
+ if (!Resolve(container, ref containerComp))
+ return;
- var allClientExclusive = true;
- var container = EnsureContainer(holderId, provider);
+ DebugTools.Assert(comp == null || comp.Owner == performer);
+ comp ??= EnsureComp<ActionsComponent>(performer);
foreach (var actionId in actions)
{
- var action = GetActionData(actionId);
- if (action == null)
- continue;
-
- AddAction(holderId, actionId, provider, comp, action, false, container);
- allClientExclusive = allClientExclusive && action.ClientExclusive;
+ AddAction(performer, actionId, container, comp, containerComp: containerComp);
}
-
- if (dirty && !allClientExclusive)
- Dirty(holderId, comp);
}
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetActions(EntityUid holderId, ActionsComponent? actions = null)
/// <summary>
/// Remove any actions that were enabled by some other entity. Useful when unequiping items that grant actions.
/// </summary>
- public void RemoveProvidedActions(EntityUid holderId, EntityUid provider, ActionsComponent? comp = null)
+ public void RemoveProvidedActions(EntityUid performer, EntityUid container, ActionsComponent? comp = null)
{
- if (!Resolve(holderId, ref comp, false))
+ if (!Resolve(performer, ref comp, false))
return;
- if (!TryGetProvidedContainer(provider, out var container))
- return;
-
- foreach (var actionId in container.ContainedEntities.ToArray())
+ foreach (var actionId in comp.Actions.ToArray())
{
- var action = GetActionData(actionId);
- if (action?.Provider == provider)
- RemoveAction(holderId, actionId, comp, dirty: false);
- }
+ if (!TryGetActionData(actionId, out var action))
+ return;
- Dirty(holderId, comp);
+ if (action.Container == container)
+ RemoveAction(performer, actionId, comp);
+ }
}
- public virtual void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true)
+ public void RemoveAction(EntityUid? actionId)
{
- if (actionId == null ||
- !Resolve(holderId, ref comp, false) ||
- TerminatingOrDeleted(actionId.Value))
- {
+ if (actionId == null)
return;
- }
- action ??= GetActionData(actionId);
-
- if (TryGetContainer(holderId, out var container) && container.Contains(actionId.Value))
- QueueDel(actionId.Value);
-
- comp.Actions.Remove(actionId.Value);
-
- if (action != null)
- {
- action.AttachedEntity = null;
- Dirty(actionId.Value, action);
- }
+ if (!TryGetActionData(actionId, out var action))
+ return;
- if (dirty)
- Dirty(holderId, comp);
+ if (!TryComp(action.AttachedEntity, out ActionsComponent? comp))
+ return;
- DebugTools.Assert(Transform(actionId.Value).ParentUid.IsValid());
+ RemoveAction(action.AttachedEntity.Value, actionId, comp, action);
}
- /// <summary>
- /// Removes all actions with the given prototype id.
- /// </summary>
- public void RemoveAction(EntityUid holderId, string actionPrototypeId, ActionsComponent? holderComp = null)
+ public void RemoveAction(EntityUid performer, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null)
{
- if (!Resolve(holderId, ref holderComp, false))
+ if (actionId == null)
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));
- }
+ if (!ResolveActionData(actionId, ref action))
+ return;
- if (actions.Count == 0)
+ if (!Resolve(performer, ref comp, false))
+ {
+ DebugTools.AssertNull(action.AttachedEntity);
return;
+ }
- foreach (var action in actions)
+ if (action.AttachedEntity == null)
{
- RemoveAction(holderId, action.Id, holderComp, action.Comp);
+ // action was already removed?
+ DebugTools.Assert(!comp.Actions.Contains(actionId.Value) || GameTiming.ApplyingState);
+ return;
}
+
+ DebugTools.Assert(action.AttachedEntity == performer);
+ comp.Actions.Remove(actionId.Value);
+ action.AttachedEntity = null;
+ Dirty(actionId.Value, action);
+ Dirty(performer, comp);
+ ActionRemoved(performer, actionId.Value, comp, action);
+ if (action.Temporary)
+ QueueDel(actionId.Value);
+ }
+
+ /// <summary>
+ /// This method gets called after an action got removed.
+ /// </summary>
+ protected virtual void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
+ {
+ // See client-side system for UI code.
}
#endregion
#region EquipHandlers
private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
{
- var ev = new GetItemActionsEvent(EntityManager, _net, args.Equipee, args.SlotFlags);
+ if (GameTiming.ApplyingState)
+ return;
+
+ var ev = new GetItemActionsEvent(_actionContainer, args.Equipee, args.Equipment, args.SlotFlags);
RaiseLocalEvent(args.Equipment, ev);
if (ev.Actions.Count == 0)
return;
- AddActions(args.Equipee, ev.Actions, args.Equipment, component);
+ GrantActions(args.Equipee, ev.Actions, args.Equipment, component);
}
private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
{
- var ev = new GetItemActionsEvent(EntityManager, _net, args.User);
+ if (GameTiming.ApplyingState)
+ return;
+
+ var ev = new GetItemActionsEvent(_actionContainer, args.User, args.Equipped);
RaiseLocalEvent(args.Equipped, ev);
if (ev.Actions.Count == 0)
return;
- AddActions(args.User, ev.Actions, args.Equipped, component);
+ GrantActions(args.User, ev.Actions, args.Equipped, component);
}
private void OnDidUnequip(EntityUid uid, ActionsComponent component, DidUnequipEvent args)
{
+ if (GameTiming.ApplyingState)
+ return;
+
RemoveProvidedActions(uid, args.Equipment, component);
}
private void OnHandUnequipped(EntityUid uid, ActionsComponent component, DidUnequipHandEvent args)
{
+ if (GameTiming.ApplyingState)
+ return;
+
RemoveProvidedActions(uid, args.Unequipped, component);
}
#endregion
+
+ public void SetEntityIcon(EntityUid uid, EntityUid? icon, BaseActionComponent? action = null)
+ {
+ if (!Resolve(uid, ref action))
+ return;
+
+ action.EntityIcon = icon;
+ Dirty(uid, action);
+ }
}
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);
Dirty(uid, component);
}
- private void OnStartup(EntityUid uid, SleepingComponent component, ComponentStartup args)
+ private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args)
{
var ev = new SleepStateChangedEvent(true);
RaiseLocalEvent(uid, ev);
_blindableSystem.UpdateIsBlind(uid);
+ _actionsSystem.AddAction(uid, ref component.WakeAction, WakeActionId, uid);
- if (_net.IsClient)
- return;
-
- component.WakeAction = Spawn(WakeActionId);
+ // TODO remove hardcoded time.
_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);
{
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
private void OnGetActions(EntityUid uid, ToggleableClothingComponent component, GetItemActionsEvent args)
{
- if (component.ClothingUid == null || (args.SlotFlags & component.RequiredFlags) != component.RequiredFlags)
- return;
-
- args.AddAction(ref component.ActionEntity, component.Action);
+ if (component.ClothingUid != null
+ && component.ActionEntity != null
+ && (args.SlotFlags & component.RequiredFlags) == component.RequiredFlags)
+ {
+ args.AddAction(component.ActionEntity.Value);
+ }
}
private void OnInit(EntityUid uid, ToggleableClothingComponent component, ComponentInit args)
/// </summary>
private void OnMapInit(EntityUid uid, ToggleableClothingComponent component, MapInitEvent args)
{
- if (component.Container!.ContainedEntity is EntityUid ent)
+ if (component.Container!.ContainedEntity is {} ent)
{
DebugTools.Assert(component.ClothingUid == ent, "Unexpected entity present inside of a toggleable clothing container.");
return;
component.Container.Insert(component.ClothingUid.Value, EntityManager, ownerTransform: xform);
}
- if (_actionsSystem.TryGetActionData(component.ActionEntity, out var action))
- {
- action.EntityIcon = component.ClothingUid;
- _actionsSystem.Dirty(component.ActionEntity);
- }
+ if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action))
+ _actionsSystem.SetEntityIcon(component.ActionEntity.Value, component.ClothingUid, action);
}
}
[Access(typeof(SharedMagbootsSystem))]
public sealed partial class MagbootsComponent : Component
{
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
- public string? ToggleAction;
+ [DataField]
+ public EntProtoId ToggleAction = "ActionToggleMagboots";
- [DataField("toggleActionEntity")]
+ [DataField, AutoNetworkedField]
public EntityUid? ToggleActionEntity;
[DataField("on"), AutoNetworkedField]
[DataField("combatToggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string CombatToggleAction = "ActionCombatModeToggle";
- [DataField("combatToggleActionEntity")]
+ [DataField, AutoNetworkedField]
public EntityUid? CombatToggleActionEntity;
[ViewVariables(VVAccess.ReadWrite), DataField("isInCombatMode"), AutoNetworkedField]
{
base.Initialize();
- SubscribeLocalEvent<DevourerComponent, ComponentStartup>(OnStartup);
+ SubscribeLocalEvent<DevourerComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<DevourerComponent, DevourActionEvent>(OnDevourAction);
}
- protected void OnStartup(EntityUid uid, DevourerComponent component, ComponentStartup args)
+ protected void OnInit(EntityUid uid, DevourerComponent component, MapInitEvent args)
{
//Devourer doesn't actually chew, since he sends targets right into his stomach.
//I did it mom, I added ERP content into upstream. Legally!
[DataField("toggleLightingAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ToggleLightingAction = "ActionToggleLighting";
- [DataField("toggleLightingActionEntity")]
+ [DataField, AutoNetworkedField]
public EntityUid? ToggleLightingActionEntity;
[DataField("toggleFovAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ToggleFoVAction = "ActionToggleFov";
- [DataField("toggleFovActionEntity")]
+ [DataField, AutoNetworkedField]
public EntityUid? ToggleFoVActionEntity;
[DataField("toggleGhostsAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ToggleGhostsAction = "ActionToggleGhosts";
- [DataField("toggleGhostsActionEntity")]
+ [DataField, AutoNetworkedField]
public EntityUid? ToggleGhostsActionEntity;
[ViewVariables(VVAccess.ReadWrite), DataField("timeOfDeath", customTypeSerializer:typeof(TimeOffsetSerializer))]
[DataField("booMaxTargets")]
public int BooMaxTargets = 3;
- [DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string Action = "ActionGhostBoo";
+ [DataField]
+ public EntProtoId BooAction = "ActionGhostBoo";
- [DataField("actionEntity")] public EntityUid? ActionEntity;
+ [DataField, AutoNetworkedField]
+ public EntityUid? BooActionEntity;
// TODO: instead of this funny stuff just give it access and update in system dirtying when needed
[ViewVariables(VVAccess.ReadWrite)]
using Content.Shared.Actions;
-using Content.Shared.Radio;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Implants.Components;
/// The actions can be activated via an action, a passive ability (ie tracking), or a reactive ability (ie on death) or some sort of combination
/// They're added and removed with implanters
/// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class SubdermalImplantComponent : Component
{
/// <summary>
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("implantAction")]
- public string? ImplantAction;
+ public EntProtoId? ImplantAction;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? Action;
/// <summary>
/// The entity this implant is inside
/// </summary>
- [ViewVariables]
+ [ViewVariables, AutoNetworkedField]
public EntityUid? ImplantedEntity;
/// <summary>
/// Should this implant be removeable?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("permanent")]
+ [DataField("permanent"), AutoNetworkedField]
public bool Permanent = false;
}
if (!string.IsNullOrWhiteSpace(component.ImplantAction))
{
- var action = Spawn(component.ImplantAction);
- _actionsSystem.AddAction(component.ImplantedEntity.Value, action, uid);
+ _actionsSystem.AddAction(component.ImplantedEntity.Value, ref component.Action, component.ImplantAction, uid);
}
//replace micro bomb with macro bomb
using Content.Shared.Decals;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
/// This is simplified version of <see cref="HandheldLightComponent"/>.
/// It doesn't consume any power and can be toggle only by verb.
/// </summary>
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class UnpoweredFlashlightComponent : Component
{
[DataField("toggleFlashlightSound")]
public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
- [ViewVariables] public bool LightOn = false;
+ [DataField, AutoNetworkedField]
+ public bool LightOn = false;
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string? ToggleAction = "ActionToggleLight";
+ [DataField]
+ public EntProtoId ToggleAction = "ActionToggleLight";
- [DataField("toggleActionEntity")] public EntityUid? ToggleActionEntity;
+ [DataField, AutoNetworkedField]
+ public EntityUid? ToggleActionEntity;
/// <summary>
/// <see cref="ColorPalettePrototype"/> ID that determines the list
/// of colors to select from when we get emagged
/// </summary>
- [DataField("emaggedColorsPrototype")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string EmaggedColorsPrototype = "Emagged";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<ColorPalettePrototype> EmaggedColorsPrototype = "Emagged";
}
[DataField("brokenState")]
public string? BrokenState;
#endregion
+
+ [DataField] public EntityUid? MechCycleActionEntity;
+ [DataField] public EntityUid? MechUiActionEntity;
+ [DataField] public EntityUid? MechEjectActionEntity;
}
/// <summary>
if (_net.IsClient)
return;
- _actions.AddAction(pilot, Spawn(component.MechCycleAction), mech);
- _actions.AddAction(pilot, Spawn(component.MechUiAction),
- mech);
- _actions.AddAction(pilot, Spawn(component.MechEjectAction), mech);
+ _actions.AddAction(pilot, ref component.MechCycleActionEntity, component.MechCycleAction, mech);
+ _actions.AddAction(pilot, ref component.MechUiActionEntity, component.MechUiAction, mech);
+ _actions.AddAction(pilot, ref component.MechEjectActionEntity, component.MechEjectAction, mech);
}
private void RemoveUser(EntityUid mech, EntityUid pilot)
/// </example>
[DataField("actions")]
public Dictionary<MobState, List<string>> Actions = new();
+
+ [DataField] public List<EntityUid> GrantedActions = new();
}
using Content.Shared.Actions;
using Content.Shared.Mobs.Components;
-using Robust.Shared.Network;
namespace Content.Shared.Mobs.Systems;
/// </summary>
public sealed class MobStateActionsSystem : EntitySystem
{
- [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
/// <inheritdoc/>
private void OnMobStateChanged(EntityUid uid, MobStateActionsComponent component, MobStateChangedEvent args)
{
- if (_net.IsClient)
+ if (!TryComp<ActionsComponent>(uid, out var action))
return;
- if (!TryComp<ActionsComponent>(uid, out var action))
+ foreach (var act in component.GrantedActions)
+ {
+ Del(act);
+ }
+ component.GrantedActions.Clear();
+
+ if (!component.Actions.TryGetValue(args.NewMobState, out var toGrant))
return;
- foreach (var (state, acts) in component.Actions)
+ foreach (var id in toGrant)
{
- if (state != args.NewMobState && state != args.OldMobState)
- continue;
-
- foreach (var item in acts)
- {
- if (state == args.OldMobState)
- {
- // Don't remove actions that would be getting readded anyway
- if (component.Actions.TryGetValue(args.NewMobState, out var value)
- && value.Contains(item))
- continue;
-
- _actions.RemoveAction(uid, item, action);
- }
- else if (state == args.NewMobState)
- {
- _actions.AddAction(uid, Spawn(item), null, action);
- }
- }
+ EntityUid? act = null;
+ if (_actions.AddAction(uid, ref act, id, uid, action))
+ component.GrantedActions.Add(act.Value);
}
+
+ Dirty(uid, component);
}
}
[ViewVariables(VVAccess.ReadWrite), DataField("moleUsage")]
public float MoleUsage = 0.012f;
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string? ToggleAction = "ActionToggleJetpack";
+ [DataField] public EntProtoId ToggleAction = "ActionToggleJetpack";
[DataField("toggleActionEntity")] public EntityUid? ToggleActionEntity;
[DataField("harvestingState")]
public string HarvestingState = "harvesting";
#endregion
+
+ [DataField] public EntityUid? Action;
}
[AutoNetworkedField]
public int MaxBattlecryLength = 12;
- [DataField("configureAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string? ConfigureAction = "ActionConfigureMeleeSpeech";
+ [DataField] public EntProtoId ConfigureAction = "ActionConfigureMeleeSpeech";
/// <summary>
/// The action to open the battlecry UI
{
base.Initialize();
- SubscribeLocalEvent<SpiderComponent, ComponentStartup>(OnSpiderStartup);
+ SubscribeLocalEvent<SpiderComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<SpiderWebObjectComponent, ComponentStartup>(OnWebStartup);
}
- private void OnSpiderStartup(EntityUid uid, SpiderComponent component, ComponentStartup args)
+ private void OnInit(EntityUid uid, SpiderComponent component, MapInitEvent args)
{
- if (_net.IsClient)
- return;
-
- _action.AddAction(uid, Spawn(component.WebAction), null);
+ _action.AddAction(uid, ref component.Action, component.WebAction, uid);
}
private void OnWebStartup(EntityUid uid, SpiderWebObjectComponent component, ComponentStartup args)
{
+ // TODO dont use this. use some general random appearance system
_appearance.SetData(uid, SpiderWebVisuals.Variant, _robustRandom.Next(1, 3));
}
}
[ViewVariables(VVAccess.ReadWrite)]
[DataField("webAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string WebAction = "ActionSpiderWeb";
+
+ [DataField] public EntityUid? Action;
}
public sealed partial class SpiderWebActionEvent : InstantActionEvent { }
noSpawn: true
components:
- type: InstantAction
+ clientExclusive: true
+ checkCanInteract: false
+ temporary: true
icon: { sprite: Objects/Tools/multitool.rsi, state: icon }
event: !type:ClearAllOverlaysEvent
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots.rsi
- type: Magboots
- toggleAction: ActionToggleMagboots
- type: ClothingSpeedModifier
walkModifier: 0.85
sprintModifier: 0.8