6
2026-01-24 12:49:55 +03:00

288 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// Handles <see cref="MechGrabberComponent"/> and all related UI logic
/// </summary>
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
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentUiMessageRelayEvent<MechGrabberEjectMessage>>(OnGrabberMessage);
SubscribeLocalEvent<MechGrabberComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentUiStateReadyEvent>(OnUiStateReady);
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentRemovedEvent>(OnEquipmentRemoved);
SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
SubscribeLocalEvent<MechGrabberComponent, UserActivateInWorldEvent>(OnInteract);
SubscribeLocalEvent<MechGrabberComponent, GrabberDoAfterEvent>(OnMechGrab);
SubscribeLocalEvent<MechGrabberComponent, EntityTerminatingEvent>(OnTerminating); // Horizon Mech
SubscribeLocalEvent<MechGrabberComponent, EntRemovedFromContainerMessage>(OnEntityRemovedFromContainer); // Horizon Mech
}
private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent<MechGrabberEjectMessage> args)
{
if (!TryComp<MechEquipmentComponent>(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);
}
/// <summary>
/// Removes an item from the grabber's container
/// </summary>
/// <param name="uid">The mech grabber</param>
/// <param name="mech">The mech it belongs to</param>
/// <param name="toRemove">The item being removed</param>
/// <param name="component"></param>
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<MechEquipmentComponent>(uid, out var equipmentComponent) ||
equipmentComponent.EquipmentOwner == null)
return;
var mech = equipmentComponent.EquipmentOwner.Value;
var allItems = new List<EntityUid>(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<Container>(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<PhysicsComponent>(target, out var physics) && physics.BodyType == BodyType.Static)
return;
if (HasComp<WallMountComponent>(target))
return;
// Если grabMobs = true, разрешить только мобов. Если grabMobs = false, запретить мобов.
if (component.GrabMobs)
{
if (!HasComp<MobStateComponent>(target))
return; // grabMobs = true, но цель не моб - запретить
}
else
{
if (HasComp<MobStateComponent>(target))
return; // grabMobs = false, но цель моб - запретить
}
if (HasComp<MechComponent>(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<MechComponent>(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<MechEquipmentComponent>(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<StrapComponent>(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<ContainerManagerComponent>(args.Args.Target, out var containerManager))
{
EntityCoordinates? coords = null;
if (TryComp(equipmentComponent.EquipmentOwner, out TransformComponent? xform))
coords = xform.Coordinates;
List<EntityUid> toRemove = new();
foreach (var container in containerManager.Containers)
{
toRemove.Clear();
foreach (var contained in container.Value.ContainedEntities)
{
if (HasComp<GhostRoleComponent>(contained)
|| TryComp<MindContainerComponent>(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
}