using Content.Shared.DoAfter;
using Content.Shared.Eye;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.RatKing;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.NodeCrawl;
///
/// Manages entry & exit of node crawlers into node networks
///
public abstract class SharedNodeCrawlSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedMoverController _mover = default!;
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] private readonly NodeCrawlerMovementSystem _nodeCrawler = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
private const string MoverContainer = "mover-container";
private static readonly EntProtoId MoverProto = "DVNodeCrawlMover";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent>(OnGetVerbs);
SubscribeLocalEvent(OnNodeCrawlEntryDoAfter);
SubscribeLocalEvent(OnArrivedAtNode);
SubscribeLocalEvent(OnGetVisMask);
SubscribeLocalEvent(OnCrawlableShutdown);
SubscribeLocalEvent(OnMovementShutdown);
SubscribeLocalEvent(OnCrawlerShutdown);
SubscribeLocalEvent(OnCrawlableAnchorChanged);
SubscribeLocalEvent(OnRatKingStartedCrawling);
}
private void OnGetVerbs(Entity ent, ref GetVerbsEvent args)
{
var user = args.User;
var target = args.Target;
if (!TryComp(user, out var nodeCrawler))
return;
if (!_entityWhitelist.IsWhitelistPass(nodeCrawler.ExitNodes, target))
return;
if (!args.CanAccess)
return;
args.Verbs.Add(new AlternativeVerb
{
Act = () => StartEntryDoAfter((user, nodeCrawler), target),
Text = Loc.GetString("node-crawl-enter", ("target", target)),
});
}
private void StartEntryDoAfter(Entity ent, EntityUid target)
{
var doAfterArgs = new DoAfterArgs(EntityManager, ent.Owner, ent.Comp.EnterDelay, new NodeCrawlEnterDoAfterEvent(), ent.Owner, target);
_doAfter.TryStartDoAfter(doAfterArgs);
}
private void OnNodeCrawlEntryDoAfter(Entity ent, ref NodeCrawlEnterDoAfterEvent args)
{
if (args.Cancelled || args.Target is not { } target)
return;
NodeCrawl(ent, target);
}
protected virtual void SetupAir(Entity movement)
{
}
protected virtual void EjectAir(Entity movement)
{
}
private void NodeCrawl(Entity ent, EntityUid target)
{
if (!_net.IsServer)
return;
var mover = Spawn(MoverProto, Transform(target).Coordinates);
var crawler = Comp(mover);
var container = _container.GetContainer(mover, MoverContainer);
_container.Insert(ent.Owner, container);
ent.Comp.Mover = mover;
Dirty(ent);
var evt = new NodeCrawlerStartedCrawlingEvent((mover, crawler));
RaiseLocalEvent(ent, ref evt);
_nodeCrawler.SetNode((mover, crawler), target);
_nodeCrawler.SetHeldCrawler((mover, crawler), ent);
SetupAir((mover, crawler));
_mover.SetRelay(ent, mover);
_physics.SetCanCollide(ent.Owner, false);
_physics.SetCanCollide(mover, false);
_eye.RefreshVisibilityMask(ent.Owner);
}
///
/// Causes this node crawler to exit its node crawl.
///
/// The crawler to exit node-crawl from.
public void ExitNodeCrawl(Entity ent)
{
if (ent.Comp.Mover is not { } mover)
return;
ent.Comp.Mover = null;
Dirty(ent);
var container = _container.GetContainer(mover, MoverContainer);
_container.Remove(ent.Owner, container);
foreach (var other in _container.EmptyContainer(container))
{
if (!TryComp(other, out var otherCrawler))
continue;
otherCrawler.Mover = null;
Dirty(other, otherCrawler);
}
RemComp(ent);
if (_net.IsServer && !TerminatingOrDeleted(mover))
{
if (TryComp(mover, out var movement))
EjectAir((mover, movement));
QueueDel(mover); // deletion isn't predicted because client queued deletion doesn't interact well with container stuff
}
_physics.SetCanCollide(ent.Owner, true);
_eye.RefreshVisibilityMask(ent.Owner);
}
private void OnArrivedAtNode(Entity ent, ref NodeCrawlerArrivedAtNodeEvent args)
{
if (!_entityWhitelist.IsWhitelistPass(ent.Comp.ExitNodes, args.Node))
return;
ExitNodeCrawl(ent);
}
private void OnGetVisMask(Entity ent, ref GetVisMaskEvent args)
{
if (ent.Comp.Mover is null)
return;
args.VisibilityMask |= (int)VisibilityFlags.Subfloor;
}
private void OnCrawlableShutdown(Entity ent, ref ComponentShutdown args)
{
foreach (var crawler in ent.Comp.Crawlers)
{
var movement = Comp(crawler);
if (movement.HeldCrawler is not { } held)
continue;
_nodeCrawler.SetNode((crawler, movement), null);
ExitNodeCrawl((held, Comp(held)));
}
}
private void OnMovementShutdown(Entity ent, ref ComponentShutdown args)
{
if (ent.Comp.Node is { } node && node.IsValid())
{
var nodeComp = Comp(node);
nodeComp.Crawlers.Remove(ent);
Dirty(node, nodeComp);
}
if (ent.Comp.HeldCrawler is { } crawler && crawler.IsValid() && !TerminatingOrDeleted(crawler) && TryComp(crawler, out var nodeCrawler))
{
ExitNodeCrawl((crawler, nodeCrawler));
}
}
private void OnCrawlerShutdown(Entity ent, ref ComponentShutdown args)
{
ExitNodeCrawl(ent);
}
private void OnCrawlableAnchorChanged(Entity ent, ref AnchorStateChangedEvent args)
{
if (args.Anchored)
return;
foreach (var crawler in ent.Comp.Crawlers)
{
var movement = Comp(crawler);
if (movement.HeldCrawler is not { } held)
continue;
ExitNodeCrawl((held, Comp(held)));
}
}
private void OnRatKingStartedCrawling(Entity ent, ref NodeCrawlerStartedCrawlingEvent args)
{
var entities = new HashSet>();
_entityLookup.GetEntitiesInRange(Transform(ent).Coordinates,
ent.Comp.VentCrawlRecruitRadius,
entities);
var container = _container.GetContainer(args.Mover, MoverContainer);
foreach (var servant in entities)
{
if (servant.Comp.King != ent)
continue;
_container.Insert(servant.Owner, container);
}
}
}