using Content.Shared.Chemistry; using Content.Shared.Damage; using Content.Shared.Electrocution; using Content.Shared.Explosion; using Content.Shared.Eye.Blinding.Systems; using Content.Shared.IdentityManagement.Components; using Content.Shared.Inventory.Events; using Content.Shared.Movement.Systems; using Content.Shared.Overlays; using Content.Shared.Radio; using Content.Shared.Slippery; using Content.Shared.Strip.Components; using Content.Shared.Temperature; using Content.Shared.Verbs; using Robust.Shared.Containers; namespace Content.Shared.Inventory; public partial class InventorySystem { public void InitializeRelay() { SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); // by-ref events SubscribeLocalEvent(RefRelayInventoryEvent); // Eye/vision events SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); // ComponentActivatedClientSystems SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(OnGetEquipmentVerbs); } protected void RefRelayInventoryEvent(EntityUid uid, InventoryComponent component, ref T args) where T : IInventoryRelayEvent { RelayEvent((uid, component), args); } protected void RelayInventoryEvent(EntityUid uid, InventoryComponent component, T args) where T : IInventoryRelayEvent { RelayEvent((uid, component), args); } public void RelayEvent(Entity inventory, ref T args) where T : IInventoryRelayEvent { var containerEnumerator = new ContainerSlotEnumerator(inventory, inventory.Comp.TemplateId, _prototypeManager, this, args.TargetSlots); // this copies the by-ref event if it is a struct var ev = new InventoryRelayedEvent(args); while (containerEnumerator.MoveNext(out var container)) { if (!container.ContainedEntity.HasValue) continue; RaiseLocalEvent(container.ContainedEntity.Value, ev); } // and now we copy it back args = ev.Args; } public void RelayEvent(Entity inventory, T args) where T : IInventoryRelayEvent { if (args.TargetSlots == SlotFlags.NONE) return; var containerEnumerator = new ContainerSlotEnumerator(inventory, inventory.Comp.TemplateId, _prototypeManager, this, args.TargetSlots); var ev = new InventoryRelayedEvent(args); while (containerEnumerator.MoveNext(out var container)) { if (!container.ContainedEntity.HasValue) continue; RaiseLocalEvent(container.ContainedEntity.Value, ev); } } private void OnGetEquipmentVerbs(EntityUid uid, InventoryComponent component, GetVerbsEvent args) { // Automatically relay stripping related verbs to all equipped clothing. if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? proto)) return; if (!TryComp(uid, out ContainerManagerComponent? containers)) return; var ev = new InventoryRelayedEvent>(args); foreach (var slotDef in proto.Slots) { if (slotDef.StripHidden && args.User != uid) continue; if (!containers.TryGetContainer(slotDef.Name, out var container)) continue; if (container is not ContainerSlot slot || slot.ContainedEntity is not { } ent) continue; RaiseLocalEvent(ent, ev); } } } /// /// Event wrapper for relayed events. /// /// /// This avoids nested inventory relays, and makes it easy to have certain events only handled by the initial /// target entity. E.g. health based movement speed modifiers should not be handled by a hat, even if that hat /// happens to be a dead mouse. Clothing that wishes to modify movement speed must subscribe to /// InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> /// public sealed class InventoryRelayedEvent : EntityEventArgs { public TEvent Args; public InventoryRelayedEvent(TEvent args) { Args = args; } } /// /// Events that should be relayed to inventory slots should implement this interface. /// public interface IInventoryRelayEvent { /// /// What inventory slots should this event be relayed to, if any? /// /// /// In general you may want to exclude , given that those items are not truly /// "equipped" by the user. /// public SlotFlags TargetSlots { get; } }