using System.Numerics; using Content.Shared._RMC14.Attachable.Components; using Content.Shared._RMC14.Attachable.Events; // using Content.Shared._RMC14.Slow; using Content.Shared._RMC14.Weapons.Ranged; using Content.Shared.Actions; using Content.Shared.Actions.Components; using Content.Shared.Actions.Events; using Content.Shared.DoAfter; using Content.Shared.Fluids; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Light; using Content.Shared.Movement.Events; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Timing; using Content.Shared.Toggleable; using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Whitelist; using Content.Shared.Wieldable.Components; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Timing; namespace Content.Shared._RMC14.Attachable.Systems; public sealed class AttachableToggleableSystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly ActionContainerSystem _actionContainerSystem = default!; [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; [Dependency] private readonly EntityWhitelistSystem _entityWhitelistSystem = default!; [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly AttachableHolderSystem _attachableHolderSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly UseDelaySystem _useDelaySystem = default!; // [Dependency] private readonly RMCSlowSystem _slow = default!; private const string attachableToggleUseDelayID = "RMCAttachableToggle"; private const int bracingInvalidCollisionGroup = (int)CollisionGroup.ThrownItem; private const int bracingRequiredCollisionGroup = (int)CollisionGroup.MidImpassable; public override void Initialize() { SubscribeLocalEvent(OnActivateInWorld, after: new[] { typeof(CMGunSystem) }); SubscribeLocalEvent(OnAttachableAltered, after: new[] { typeof(AttachableModifiersSystem) }); SubscribeLocalEvent(OnAttachableToggleableInterrupt); SubscribeLocalEvent(OnAttachableToggleAction); SubscribeLocalEvent(OnAttachableToggleDoAfter); SubscribeLocalEvent(OnAttachableToggleStarted); SubscribeLocalEvent(OnAttemptShoot); SubscribeLocalEvent>(OnGunShot); SubscribeLocalEvent(OnGunShot); SubscribeLocalEvent(OnToggleAction, before: new[] { typeof(SharedHandheldLightSystem) }); //SubscribeLocalEvent(OnUniqueAction); SubscribeLocalEvent(OnGrantAttachableActions); SubscribeLocalEvent(OnRemoveAttachableActions); SubscribeLocalEvent>(OnHandDeselected); SubscribeLocalEvent>(OnGotEquippedHand); SubscribeLocalEvent>(OnGotUnequippedHand); SubscribeLocalEvent(OnSprayAttempt); SubscribeLocalEvent(OnDropped); SubscribeLocalEvent>(OnDropped); SubscribeLocalEvent(OnAttachableMovementLockedMoveInput); SubscribeLocalEvent(OnAttachableAltered, after: new[] { typeof(AttachableModifiersSystem) }); SubscribeLocalEvent(OnAttachableAltered, after: new[] { typeof(AttachableModifiersSystem) }); SubscribeLocalEvent(OnAttemptShoot); } #region AttachableAlteredEvent handling private void OnAttachableAltered(Entity attachable, ref AttachableAlteredEvent args) { switch (args.Alteration) { case AttachableAlteredType.Detached: if (attachable.Comp.SupercedeHolder && TryComp(args.Holder, out AttachableHolderComponent? holderComponent) && holderComponent.SupercedingAttachable == attachable.Owner) { _attachableHolderSystem.SetSupercedingAttachable((args.Holder, holderComponent), null); } if (attachable.Comp.Active) { var ev = args with { Alteration = AttachableAlteredType.DetachedDeactivated }; RaiseLocalEvent(attachable.Owner, ref ev); } attachable.Comp.Attached = false; attachable.Comp.Active = false; Dirty(attachable); break; case AttachableAlteredType.Attached: attachable.Comp.Attached = true; break; case AttachableAlteredType.Unwielded: if (!attachable.Comp.WieldedOnly || !attachable.Comp.Active) break; Toggle(attachable, args.User, attachable.Comp.DoInterrupt); break; } if (attachable.Comp.Action == null || !TryComp(attachable.Comp.Action, out ActionComponent? actionComponent)) { return; } _actionsSystem.SetToggled(attachable.Comp.Action, attachable.Comp.Active); _actionsSystem.SetEnabled((attachable.Comp.Action.Value, actionComponent), attachable.Comp.Attached); } private void OnAttachableAltered(Entity attachable, ref AttachableAlteredEvent args) { if (args.User == null) return; switch (args.Alteration) { case AttachableAlteredType.Activated: RaiseLocalEvent(attachable.Owner, new ActivateInWorldEvent(args.User.Value, args.Holder, true)); break; case AttachableAlteredType.Deactivated: RaiseLocalEvent(attachable.Owner, new ActivateInWorldEvent(args.User.Value, args.Holder, true)); break; case AttachableAlteredType.DetachedDeactivated: RaiseLocalEvent(attachable.Owner, new ActivateInWorldEvent(args.User.Value, args.Holder, true)); break; } } private void OnAttachableAltered(Entity attachable, ref AttachableAlteredEvent args) { if (!TryComp(attachable.Owner, out AttachableToggleableComponent? toggleableComponent)) return; EnsureComp(args.Holder, out AttachableGunPreventShootComponent preventShootComponent); switch (args.Alteration) { case AttachableAlteredType.Attached: preventShootComponent.Message = attachable.Comp.Message; preventShootComponent.PreventShoot = attachable.Comp.ShootWhenActive && !toggleableComponent.Active || !attachable.Comp.ShootWhenActive && toggleableComponent.Active; break; case AttachableAlteredType.Detached: preventShootComponent.Message = ""; preventShootComponent.PreventShoot = false; break; case AttachableAlteredType.Activated: preventShootComponent.PreventShoot = !attachable.Comp.ShootWhenActive; break; case AttachableAlteredType.Deactivated: preventShootComponent.PreventShoot = attachable.Comp.ShootWhenActive; break; case AttachableAlteredType.DetachedDeactivated: preventShootComponent.PreventShoot = false; break; } Dirty(args.Holder, preventShootComponent); } #endregion private void OnGotEquippedHand(Entity attachable, ref AttachableRelayedEvent args) { if (!attachable.Comp.Attached) return; args.Args.Handled = true; var addEv = new GrantAttachableActionsEvent(args.Args.User); RaiseLocalEvent(attachable, ref addEv); } #region Lockouts and interrupts private void OnActivateInWorld(Entity attachable, ref ActivateInWorldEvent args) { if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) args.Handled = true; } private void OnAttemptShoot(Entity attachable, ref AttemptShootEvent args) { if (args.Cancelled) return; if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) { args.Cancelled = true; return; } if (attachable.Comp.WieldedUseOnly && (!_attachableHolderSystem.TryGetHolder(attachable.Owner, out EntityUid? holderUid) || !TryComp(holderUid, out WieldableComponent? wieldableComponent) || !wieldableComponent.Wielded)) { args.Cancelled = true; if (holderUid == null) return; _popupSystem.PopupClient( Loc.GetString("rmc-attachable-shoot-fail-not-wielded", ("holder", holderUid), ("attachable", attachable)), args.User, args.User); return; } } private void OnGunShot(Entity attachable, ref AttachableRelayedEvent args) { CheckUserBreakOnRotate(args.Args.User); CheckUserBreakOnFullRotate(args.Args.User, args.Args.FromCoordinates, args.Args.ToCoordinates); } private void OnGunShot(Entity attachable, ref GunShotEvent args) { CheckUserBreakOnRotate(args.User); CheckUserBreakOnFullRotate(args.User, args.FromCoordinates, args.ToCoordinates); } private void OnAttemptShoot(Entity gun, ref AttemptShootEvent args) { if (args.Cancelled || !gun.Comp.PreventShoot) return; args.Cancelled = true; _popupSystem.PopupClient(gun.Comp.Message, args.User, args.User); } /* private void OnUniqueAction(Entity attachable, ref UniqueActionEvent args) { if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) args.Handled = true; }*/ private void OnHandDeselected(Entity attachable, ref AttachableRelayedEvent args) { if (!attachable.Comp.Attached) return; args.Args.Handled = true; if (!attachable.Comp.NeedHand || !attachable.Comp.Active) return; Toggle(attachable, args.Args.User, attachable.Comp.DoInterrupt); } private void OnAttachableToggleableInterrupt(Entity attachable, ref AttachableToggleableInterruptEvent args) { if (!attachable.Comp.Active) return; Toggle(attachable, args.User, attachable.Comp.DoInterrupt); } private void OnGotUnequippedHand(Entity attachable, ref AttachableRelayedEvent args) { if (!attachable.Comp.Attached) return; args.Args.Handled = true; if ((attachable.Comp.NeedHand || attachable.Comp.BreakOnDrop) && attachable.Comp.Active) { Toggle(attachable, args.Args.User, attachable.Comp.DoInterrupt); } var removeEv = new RemoveAttachableActionsEvent(args.Args.User); RaiseLocalEvent(attachable, ref removeEv); } private void OnSprayAttempt(Entity attachable, ref SprayAttemptEvent args) { if (args.Cancelled) return; if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) args.Cancelled = true; } private void OnDropped(Entity attachable, ref DroppedEvent args) { if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) return; if (!attachable.Comp.BreakOnDrop || !attachable.Comp.Active) return; Toggle(attachable, args.User); } private void OnDropped(Entity attachable, ref AttachableRelayedEvent args) { if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) return; if (!attachable.Comp.BreakOnDrop || !attachable.Comp.Active) return; Toggle(attachable, args.Args.User); } private void OnAttachableMovementLockedMoveInput(Entity user, ref MoveInputEvent args) { if (!args.HasDirectionalMovement) return; for (var i = user.Comp.AttachableList.Count - 1; i >= 0; i--) { var attachableUid = user.Comp.AttachableList[i]; if (!TryComp(attachableUid, out AttachableToggleableComponent? toggleableComponent) || !toggleableComponent.Active || !toggleableComponent.BreakOnMove) { continue; } Toggle((attachableUid, toggleableComponent), user.Owner, toggleableComponent.DoInterrupt); } } private void CheckUserBreakOnRotate(Entity user) { if (user.Comp == null) { if (!TryComp(user.Owner, out AttachableDirectionLockedComponent? lockedComponent)) return; user.Comp = lockedComponent; } if (Transform(user.Owner).LocalRotation.GetCardinalDir() == user.Comp.LockedDirection) return; for (var i = user.Comp.AttachableList.Count - 1; i >= 0; i--) { var attachableUid = user.Comp.AttachableList[i]; if (!TryComp(attachableUid, out AttachableToggleableComponent? toggleableComponent) || !toggleableComponent.Active || !toggleableComponent.BreakOnRotate) { continue; } Toggle((attachableUid, toggleableComponent), user.Owner, toggleableComponent.DoInterrupt); } } private void CheckUserBreakOnFullRotate(Entity user, EntityCoordinates playerPos, EntityCoordinates targetPos) { if (user.Comp == null) { if (!TryComp(user.Owner, out AttachableSideLockedComponent? lockedComponent)) return; user.Comp = lockedComponent; } if (user.Comp.LockedDirection == null) return; var initialAngle = user.Comp.LockedDirection.Value.ToAngle(); var playerMapPos = _transformSystem.ToMapCoordinates(playerPos); var targetMapPos = _transformSystem.ToMapCoordinates(targetPos); var currentAngle = (targetMapPos.Position - playerMapPos.Position).ToWorldAngle(); var differenceFromLockedAngle = (currentAngle.Degrees - initialAngle.Degrees + 180 + 360) % 360 - 180; if (differenceFromLockedAngle > -90 && differenceFromLockedAngle < 90) return; for (var i = user.Comp.AttachableList.Count - 1; i >= 0; i--) { var attachableUid = user.Comp.AttachableList[i]; if (!TryComp(attachableUid, out AttachableToggleableComponent? toggleableComponent) || !toggleableComponent.Active || !toggleableComponent.BreakOnFullRotate) { continue; } Toggle((attachableUid, toggleableComponent), user.Owner, toggleableComponent.DoInterrupt); } } #endregion #region Toggling private void OnAttachableToggleStarted(Entity attachable, ref AttachableToggleStartedEvent args) { if (!CanStartToggleDoAfter(attachable, ref args)) return; var popupText = Loc.GetString(attachable.Comp.Active ? attachable.Comp.DeactivatePopupText : attachable.Comp.ActivatePopupText, ("attachable", attachable.Owner)); _doAfterSystem.TryStartDoAfter(new DoAfterArgs( EntityManager, args.User, GetToggleDoAfter(attachable, args.Holder, args.User, ref popupText), new AttachableToggleDoAfterEvent(args.SlotId, popupText), attachable, target: attachable.Owner, used: args.Holder) { NeedHand = attachable.Comp.DoAfterNeedHand, BreakOnMove = attachable.Comp.DoAfterBreakOnMove }); Dirty(attachable); } private bool CanStartToggleDoAfter(Entity attachable, ref AttachableToggleStartedEvent args, bool silent = false) { if (TryComp(attachable.Owner, out UseDelayComponent? useDelayComponent) && _useDelaySystem.IsDelayed((attachable.Owner, useDelayComponent), attachableToggleUseDelayID)) { return false; } _attachableHolderSystem.TryGetUser(attachable.Owner, out var userUid); if (attachable.Comp.HeldOnlyActivate && !attachable.Comp.Active && (userUid == null || !_handsSystem.IsHolding(userUid.Value, args.Holder, out _))) { if (!silent) _popupSystem.PopupClient( Loc.GetString("rmc-attachable-activation-fail-not-held", ("holder", args.Holder), ("attachable", attachable)), args.User, args.User); return false; } if (attachable.Comp.UserOnly && userUid != args.User) { if (!silent) _popupSystem.PopupClient( Loc.GetString("rmc-attachable-activation-fail-not-owned", ("holder", args.Holder), ("attachable", attachable)), args.User, args.User); return false; } if (!attachable.Comp.Active && attachable.Comp.WieldedOnly && (!TryComp(args.Holder, out WieldableComponent? wieldableComponent) || !wieldableComponent.Wielded)) { if (!silent) _popupSystem.PopupClient( Loc.GetString("rmc-attachable-activation-fail-not-wielded", ("holder", args.Holder), ("attachable", attachable)), args.User, args.User); return false; } return true; } private TimeSpan GetToggleDoAfter(Entity attachable, EntityUid holderUid, EntityUid userUid, ref string popupText) { if (!TryComp(holderUid, out TransformComponent? transformComponent) || !transformComponent.ParentUid.Valid) return TimeSpan.FromSeconds(0f); var extraDoAfter = transformComponent.ParentUid == userUid ? 0f : 0.5f; switch (attachable.Comp.InstantToggle) { case AttachableInstantToggleConditions.Brace: if (attachable.Comp.Active || transformComponent.ParentUid != userUid || !TryComp(userUid, out TransformComponent? userTransform)) break; TimeSpan? doAfter; var coords = userTransform.Coordinates; Func comparer = (EntityCoordinates userCoords, EntityCoordinates entCoords) => { return false; }; var coordsShift = new Vector2(0f, 0f); Func, EntityUid?> GetBracingSurface = (HashSet ents) => { foreach (var entity in ents) { if (!TryComp(entity, out FixturesComponent? fixturesComponent) || !Transform(entity).Anchored) continue; foreach (var fixture in fixturesComponent.Fixtures.Values) { if ((fixture.CollisionLayer & bracingInvalidCollisionGroup) != 0 || (fixture.CollisionLayer & bracingRequiredCollisionGroup) == 0) continue; if (!comparer(coords, Transform(entity).Coordinates)) continue; return entity; } } return null; }; switch (userTransform.LocalRotation.GetCardinalDir()) { case Direction.South: comparer = (EntityCoordinates userCoords, EntityCoordinates entCoords) => { return entCoords.Y < userCoords.Y; }; coordsShift = new Vector2(0f, -0.7f); break; case Direction.North: comparer = (EntityCoordinates userCoords, EntityCoordinates entCoords) => { return entCoords.Y > userCoords.Y; }; coordsShift = new Vector2(0f, 0.7f); break; case Direction.East: comparer = (EntityCoordinates userCoords, EntityCoordinates entCoords) => { return entCoords.X > userCoords.X; }; coordsShift = new Vector2(0.7f, 0f); break; case Direction.West: comparer = (EntityCoordinates userCoords, EntityCoordinates entCoords) => { return entCoords.X < userCoords.X; }; coordsShift = new Vector2(-0.7f, 0f); break; default: break; } var surface = GetBracingSurface(_entityLookupSystem.GetEntitiesInRange(coords, 0.5f, LookupFlags.Dynamic | LookupFlags.Static)); if (surface != null) { popupText = Loc.GetString("attachable-popup-activate-deploy-on-generic", ("attachable", attachable.Owner), ("surface", surface)); return TimeSpan.FromSeconds(0f); } coords = new EntityCoordinates(coords.EntityId, coords.Position + coordsShift); surface = GetBracingSurface(_entityLookupSystem.GetEntitiesInRange(coords, 0.5f, LookupFlags.Dynamic | LookupFlags.Static)); if (surface != null) { popupText = Loc.GetString("attachable-popup-activate-deploy-on-generic", ("attachable", attachable.Owner), ("surface", surface)); return TimeSpan.FromSeconds(0f); } popupText = Loc.GetString("attachable-popup-activate-deploy-on-ground", ("attachable", attachable.Owner)); break; default: break; } return TimeSpan.FromSeconds(Math.Max( (attachable.Comp.DeactivateDoAfter != null && attachable.Comp.Active ? attachable.Comp.DeactivateDoAfter.Value : attachable.Comp.DoAfter ) + extraDoAfter, 0)); } private void OnAttachableToggleDoAfter(Entity attachable, ref AttachableToggleDoAfterEvent args) { if (args.Cancelled || args.Handled) return; if (args.Target is not { } target || args.Used is not { } used) return; if (!HasComp(target)) return; if (!TryComp(args.Used, out AttachableHolderComponent? holderComponent)) return; FinishToggle(attachable, (used, holderComponent), args.SlotId, args.User, args.PopupText); args.Handled = true; Dirty(attachable); } private void RemoveUnusedLocks(Entity attachable, EntityUid? userUid) { if (userUid == null) return; if (attachable.Comp.BreakOnMove && TryComp(userUid.Value, out var movementLockedComponent)) { movementLockedComponent.AttachableList.Remove(attachable.Owner); if (movementLockedComponent.AttachableList.Count == 0) RemCompDeferred(userUid.Value); } if (attachable.Comp.BreakOnRotate && TryComp(userUid.Value, out var directionLockedComponent)) { directionLockedComponent.AttachableList.Remove(attachable.Owner); if (directionLockedComponent.AttachableList.Count == 0) RemCompDeferred(userUid.Value); } if (attachable.Comp.BreakOnFullRotate && TryComp(userUid.Value, out var sideLockedComponent)) { sideLockedComponent.AttachableList.Remove(attachable.Owner); if (sideLockedComponent.AttachableList.Count == 0) RemCompDeferred(userUid.Value); } } private void FinishToggle( Entity attachable, Entity holder, string slotId, EntityUid? userUid, string popupText, bool interrupted = false) { attachable.Comp.Active = !attachable.Comp.Active; var mode = attachable.Comp.Active ? AttachableAlteredType.Activated : interrupted ? AttachableAlteredType.Interrupted : AttachableAlteredType.Deactivated; var ev = new AttachableAlteredEvent(holder.Owner, mode, userUid); RaiseLocalEvent(attachable.Owner, ref ev); var holderEv = new AttachableHolderAttachablesAlteredEvent(attachable.Owner, slotId, mode); RaiseLocalEvent(holder.Owner, ref holderEv); _useDelaySystem.SetLength(attachable.Owner, attachable.Comp.UseDelay, attachableToggleUseDelayID); _useDelaySystem.TryResetDelay(attachable.Owner, id: attachableToggleUseDelayID); _actionsSystem.StartUseDelay(attachable.Comp.Action); if (attachable.Comp.ShowTogglePopup && userUid != null) _popupSystem.PopupClient(popupText, userUid.Value, userUid.Value); _audioSystem.PlayPredicted( attachable.Comp.Active ? attachable.Comp.ActivateSound : attachable.Comp.DeactivateSound, attachable, userUid); if (!attachable.Comp.Active) { if (attachable.Comp.SupercedeHolder && holder.Comp.SupercedingAttachable == attachable.Owner) _attachableHolderSystem.SetSupercedingAttachable(holder, null); RemoveUnusedLocks(attachable, userUid); return; } if (attachable.Comp.BreakOnMove && userUid != null) { var movementLockedComponent = EnsureComp(userUid.Value); movementLockedComponent.AttachableList.Add(attachable.Owner); } if (attachable.Comp.BreakOnRotate && userUid != null) { var directionLockedComponent = EnsureComp(userUid.Value); directionLockedComponent.AttachableList.Add(attachable.Owner); if (directionLockedComponent.LockedDirection == null) directionLockedComponent.LockedDirection = Transform(userUid.Value).LocalRotation.GetCardinalDir(); } if (attachable.Comp.BreakOnFullRotate && userUid != null) { var sideLockedComponent = EnsureComp(userUid.Value); sideLockedComponent.AttachableList.Add(attachable.Owner); if (sideLockedComponent.LockedDirection == null) sideLockedComponent.LockedDirection = Transform(userUid.Value).LocalRotation.GetCardinalDir(); } if (!attachable.Comp.SupercedeHolder) return; if (holder.Comp.SupercedingAttachable != null && TryComp(holder.Comp.SupercedingAttachable, out AttachableToggleableComponent? toggleableComponent)) { toggleableComponent.Active = false; ev = new AttachableAlteredEvent(holder.Owner, AttachableAlteredType.Deactivated); RaiseLocalEvent(holder.Comp.SupercedingAttachable.Value, ref ev); if (_attachableHolderSystem.TryGetSlotId(holder.Owner, attachable.Owner, out var deactivatedSlot)) { holderEv = new AttachableHolderAttachablesAlteredEvent(holder.Comp.SupercedingAttachable.Value, deactivatedSlot, AttachableAlteredType.Deactivated); RaiseLocalEvent(holder.Owner, ref holderEv); } } _attachableHolderSystem.SetSupercedingAttachable(holder, attachable.Owner); } private void Toggle(Entity attachable, EntityUid? user, bool interrupted = false) { if (!_attachableHolderSystem.TryGetHolder(attachable.Owner, out var holderUid) || !TryComp(holderUid, out AttachableHolderComponent? holderComponent) || !_attachableHolderSystem.TryGetSlotId(holderUid.Value, attachable.Owner, out var slotId)) { return; } FinishToggle( attachable, (holderUid.Value, holderComponent), slotId, user, Loc.GetString(attachable.Comp.Active ? attachable.Comp.DeactivatePopupText : attachable.Comp.ActivatePopupText, ("attachable", attachable.Owner)), interrupted); Dirty(attachable); } #endregion #region Actions private void OnGrantAttachableActions(Entity ent, ref GrantAttachableActionsEvent args) { GrantAttachableActions(ent, args.User); RelayAttachableActions(ent, args.User); } private void GrantAttachableActions(Entity ent, EntityUid user, bool doSecondTry = true) { // This is to prevent ActionContainerSystem from shitting itself if the attachment has actions other than its attachment toggle. if (!TryComp(ent.Owner, out ActionsContainerComponent? actionsContainerComponent) || actionsContainerComponent.Container == null) { EnsureComp(ent.Owner); if (doSecondTry) GrantAttachableActions(ent, user, false); return; } var exists = ent.Comp.Action != null; _actionContainerSystem.EnsureAction(ent, ref ent.Comp.Action, ent.Comp.ActionId, actionsContainerComponent); if (ent.Comp.Action is not { } actionId) return; _actionsSystem.GrantContainedAction(user, ent.Owner, actionId); if (exists) return; _metaDataSystem.SetEntityName(actionId, ent.Comp.ActionName); _metaDataSystem.SetEntityDescription(actionId, ent.Comp.ActionDesc); if (_actionsSystem.GetAction(actionId) is { } action) { var actionEnt = action.AsNullable(); _actionsSystem.SetIcon(actionEnt, ent.Comp.Icon); _actionsSystem.SetIconOn(actionEnt, ent.Comp.IconActive); _actionsSystem.SetEnabled(actionEnt, ent.Comp.Attached); _actionsSystem.SetUseDelay(actionEnt, ent.Comp.UseDelay); } Dirty(ent); } private void RelayAttachableActions(Entity attachable, EntityUid user) { if (attachable.Comp.ActionsToRelayWhitelist == null || !TryComp(attachable.Owner, out ActionsContainerComponent? actionsContainerComponent)) return; foreach (var actionUid in actionsContainerComponent.Container.ContainedEntities) { if (!_entityWhitelistSystem.IsWhitelistPass(attachable.Comp.ActionsToRelayWhitelist, actionUid)) continue; _actionsSystem.GrantContainedAction(user, (attachable.Owner, actionsContainerComponent), actionUid); } } private void OnRemoveAttachableActions(Entity ent, ref RemoveAttachableActionsEvent args) { RemoveAttachableActions(ent, args.User); RemoveRelayedActions(ent, args.User); } private void RemoveAttachableActions(Entity ent, EntityUid user) { if (ent.Comp.Action is not { } action) return; if (!HasComp(action) || !TryComp(action, out ActionComponent? actionComponent) || actionComponent.AttachedEntity != user) { return; } _actionsSystem.RemoveProvidedAction(user, ent, action); } private void RemoveRelayedActions(Entity attachable, EntityUid user) { if (attachable.Comp.ActionsToRelayWhitelist == null || !TryComp(attachable.Owner, out ActionsContainerComponent? actionsContainerComponent)) return; foreach (var actionUid in actionsContainerComponent.Container.ContainedEntities) { if (!_entityWhitelistSystem.IsWhitelistPass(attachable.Comp.ActionsToRelayWhitelist, actionUid)) continue; _actionsSystem.RemoveProvidedAction(user, attachable.Owner, actionUid); } } private void OnAttachableToggleAction(Entity attachable, ref AttachableToggleActionEvent args) { args.Handled = true; if (!attachable.Comp.Attached) return; if (!_attachableHolderSystem.TryGetHolder(attachable.Owner, out var holderUid) || !TryComp(holderUid, out AttachableHolderComponent? holderComponent) || !_attachableHolderSystem.TryGetSlotId(holderUid.Value, attachable.Owner, out var slotId)) { return; } var ev = new AttachableToggleStartedEvent(holderUid.Value, args.Performer, slotId); RaiseLocalEvent(attachable.Owner, ref ev); } private void OnToggleAction(Entity attachable, ref ToggleActionEvent args) { if (attachable.Comp.AttachedOnly && !attachable.Comp.Attached) args.Handled = true; } #endregion }