using System.Linq; using Content.Server.Interaction; using Content.Server.Mech.Equipment.Components; using Content.Server.Mech.Systems; using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Mech; using Content.Shared.Mech.Components; using Content.Shared.Mech.Equipment.Components; using Content.Shared.Mobs.Components; using Content.Shared.Wall; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Content.Shared.Whitelist; // Frontier using Content.Shared.Buckle.Components; // Frontier using Content.Shared.Buckle; // Frontier using Content.Server.Body.Systems; using Content.Shared.Mind.Components; // Frontier using Content.Server.Ghost.Roles.Components; // Frontier namespace Content.Server.Mech.Equipment.EntitySystems; /// /// Handles and all related UI logic /// public sealed class MechGrabberSystem : EntitySystem { [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly MechSystem _mech = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; // Frontier [Dependency] private readonly SharedBuckleSystem _buckle = default!; // Frontier /// public override void Initialize() { SubscribeLocalEvent>(OnGrabberMessage); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnUiStateReady); SubscribeLocalEvent(OnEquipmentRemoved); SubscribeLocalEvent(OnAttemptRemove); SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnMechGrab); SubscribeLocalEvent(OnTerminating); // Horizon Mech SubscribeLocalEvent(OnEntityRemovedFromContainer); // Horizon Mech } private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args) { if (!TryComp(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null) return; var mech = equipmentComponent.EquipmentOwner.Value; var targetCoords = new EntityCoordinates(mech, component.DepositOffset); if (!_interaction.InRangeUnobstructed(mech, targetCoords)) return; var item = GetEntity(args.Message.Item); if (!component.ItemContainer.Contains(item)) return; RemoveItem(uid, mech, item, component); } /// /// Removes an item from the grabber's container /// /// The mech grabber /// The mech it belongs to /// The item being removed /// public void RemoveItem(EntityUid uid, EntityUid mech, EntityUid toRemove, MechGrabberComponent? component = null) { if (!Resolve(uid, ref component)) return; _container.Remove(toRemove, component.ItemContainer); // Примечание: Удаление эффекта удушения теперь происходит в OnEntityRemovedFromContainer // чтобы обработать все случаи удаления из контейнера (включая побег) var mechxform = Transform(mech); var xform = Transform(toRemove); _transform.AttachToGridOrMap(toRemove, xform); var (mechPos, mechRot) = _transform.GetWorldPositionRotation(mechxform); var offset = mechPos + mechRot.RotateVec(component.DepositOffset); _transform.SetWorldPositionRotation(toRemove, offset, Angle.Zero); _mech.UpdateUserInterface(mech); } private void OnEquipmentRemoved(EntityUid uid, MechGrabberComponent component, ref MechEquipmentRemovedEvent args) { if (!TryComp(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null) return; var mech = equipmentComponent.EquipmentOwner.Value; var allItems = new List(component.ItemContainer.ContainedEntities); foreach (var item in allItems) { RemoveItem(uid, mech, item, component); } } private void OnAttemptRemove(EntityUid uid, MechGrabberComponent component, ref AttemptRemoveMechEquipmentEvent args) { args.Cancelled = component.ItemContainer.ContainedEntities.Any(); } private void OnStartup(EntityUid uid, MechGrabberComponent component, ComponentStartup args) { component.ItemContainer = _container.EnsureContainer(uid, "item-container"); } private void OnUiStateReady(EntityUid uid, MechGrabberComponent component, MechEquipmentUiStateReadyEvent args) { args.State = new MechGrabberUiState { Contents = GetNetEntityList(component.ItemContainer.ContainedEntities.ToList()), MaxContents = component.MaxContents }; } private void OnInteract(EntityUid uid, MechGrabberComponent component, UserActivateInWorldEvent args) { if (args.Handled) return; var target = args.Target; if (args.Target == args.User || component.DoAfter != null) return; // Horizon Mech start if (TryComp(target, out var physics) && physics.BodyType == BodyType.Static) return; if (HasComp(target)) return; // Если grabMobs = true, разрешить только мобов. Если grabMobs = false, запретить мобов. if (component.GrabMobs) { if (!HasComp(target)) return; // grabMobs = true, но цель не моб - запретить } else { if (HasComp(target)) return; // grabMobs = false, но цель моб - запретить } if (HasComp(target)) return; // Horizon Mech end if (_whitelist.IsBlacklistPass(component.Blacklist, target)) // Frontier: Blacklist return; if (Transform(target).Anchored) return; if (component.ItemContainer.ContainedEntities.Count >= component.MaxContents) return; if (!TryComp(args.User, out var mech) || mech.PilotSlot.ContainedEntity == target) return; if (mech.Energy + component.GrabEnergyDelta < 0) return; if (!_interaction.InRangeUnobstructed(args.User, target)) return; args.Handled = true; component.AudioStream = _audio.PlayPvs(component.GrabSound, uid)?.Entity; var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.GrabDelay, new GrabberDoAfterEvent(), uid, target: target, used: uid) { BreakOnMove = true }; _doAfter.TryStartDoAfter(doAfterArgs, out component.DoAfter); } private void OnMechGrab(EntityUid uid, MechGrabberComponent component, DoAfterEvent args) { component.DoAfter = null; if (args.Cancelled) { component.AudioStream = _audio.Stop(component.AudioStream); return; } if (args.Handled || args.Args.Target == null) return; if (!TryComp(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null) return; if (!_mech.TryChangeEnergy(equipmentComponent.EquipmentOwner.Value, component.GrabEnergyDelta)) return; // Frontier: Remove people from chairs and containers if (TryComp(args.Args.Target, out var strapComp) && strapComp.BuckledEntities != null) { foreach (var buckleUid in strapComp.BuckledEntities) { _buckle.Unbuckle(buckleUid, args.Args.User); } } // Remove contained humanoids // TODO: revise condition for "generic player entities" if (TryComp(args.Args.Target, out var containerManager)) { EntityCoordinates? coords = null; if (TryComp(equipmentComponent.EquipmentOwner, out TransformComponent? xform)) coords = xform.Coordinates; List toRemove = new(); foreach (var container in containerManager.Containers) { toRemove.Clear(); foreach (var contained in container.Value.ContainedEntities) { if (HasComp(contained) || TryComp(contained, out var mindContainer) && mindContainer.HasMind) { toRemove.Add(contained); } } foreach (var removeUid in toRemove) { _container.Remove(removeUid, container.Value, destination: coords); } } } // End Frontier: Remove people from chairs and containers _container.Insert(args.Args.Target.Value, component.ItemContainer); // Horizon Mech start if (component.SlowMetabolism && args.Target.HasValue) { var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.Target.Value, 0.4f, false); RaiseLocalEvent(args.Target.Value, ref metabolicEvent); } // Horizon Mech end _mech.UpdateUserInterface(equipmentComponent.EquipmentOwner.Value); args.Handled = true; } // Horizon Mech start private void OnTerminating(EntityUid uid, MechGrabberComponent comp, ref EntityTerminatingEvent args) // Horizon Mech { _container.EmptyContainer(comp.ItemContainer, true); } private void OnEntityRemovedFromContainer(EntityUid uid, MechGrabberComponent component, EntRemovedFromContainerMessage args) { // Проверяем, что это удаление из нашего контейнера ItemContainer if (args.Container != component.ItemContainer) return; // Если у слипера включён SlowMetabolism, убираем эффект удушения при выходе существа // Это обрабатывает все случаи удаления из контейнера: // - ручное удаление через UI (RemoveItem -> _container.Remove) // - побег существа через TryRemoveFromContainer (EscapeMechSystem) // - очистка контейнера при уничтожении оборудования if (component.SlowMetabolism) { var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.Entity, 0.4f, true); RaiseLocalEvent(args.Entity, ref metabolicEvent); } } // Horizon Mech end }