using System.Linq;
using Content.Shared._DV.Waypointer;
using Content.Shared._DV.Waypointer.Components;
using Content.Shared.Whitelist;
using JetBrains.Annotations;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.Waypointer;
///
/// This handles the PVSOverrides for the Waypointer System.
///
public sealed class WaypointerSystem : SharedWaypointerSystem
{
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnAddition);
SubscribeLocalEvent(OnRemoval);
SubscribeLocalEvent(OnTrackableInit);
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
SubscribeLocalEvent(OnMapChanged);
}
private void OnAddition(Entity player, ref ComponentInit args)
{
_actions.AddAction(player, ref player.Comp.ActionEntity, player.Comp.ActionProtoId);
}
private void OnRemoval(Entity player, ref ComponentRemove args)
{
_actions.RemoveAction(player.Owner, player.Comp.ActionEntity);
}
private void OnTrackableInit(Entity trackable, ref ComponentInit args)
{
// This might be a bit confusing, but I think this is the cheapest way to refresh overrides for new trackables.
// I'll explain:
// This gets all possible waypointers in the game.
var waypointers = _prototype.GetInstances();
// This will hold all waypointers that need their overrides to be refreshed because this trackable spawned.
var waypointersToOverride = new HashSet>();
// Now, we iterate through each waypointer
foreach (var waypointer in waypointers.Values)
{
// And then we iterate through each component that the waypointer tracks
foreach (var trackedComponent in waypointer.TrackedComponents.Values)
{
// And then check if the trackable has that tracked component
if (!EntityManager.HasComponent(trackable, trackedComponent.Component.GetType())
// And of course, check if it passes the whitelist & blacklist.
|| !_whitelist.CheckBoth(trackable, blacklist: waypointer.Blacklist, whitelist: waypointer.Whitelist))
continue;
// THEN we add that WAYPOINTER to the list above.
waypointersToOverride.Add(new ProtoId(waypointer.ID));
// If it didn't have that component, then we don't need to refresh the overrides for that one.
}
}
// We get this for a check later.
var trackXform = Transform(trackable);
// Now we get every entity that has an active waypointer
var waypointerQuery = AllEntityQuery();
// We iterate through them
while (waypointerQuery.MoveNext(out var player, out var waypointerComp, out var actorComp))
{
// No need to override if they don't have any waypointers.
if (waypointerComp.WaypointerProtoIds == null)
continue;
// Then we iterate through every waypointer they have access to
foreach (var waypointer in waypointerComp.WaypointerProtoIds.Keys)
{
// We check if they have any waypointer that can track the new trackable entity.
if (!waypointersToOverride.Contains(waypointer))
continue;
var playerXform = Transform(player);
// Now we check if that player is on the same map as the tracked entity.
if (trackXform.MapID != playerXform.MapID)
continue;
// Then we finally override that entity for said player.
_pvsOverride.AddSessionOverride(trackable, actorComp.PlayerSession);
break; // No need to check other waypointers, so we break here to check for the next player.
}
}
}
private void OnPlayerAttached(Entity player, ref PlayerAttachedEvent args)
{
if (player.Comp.WaypointerProtoIds == null)
return;
AddOverrides(player, player.Comp.WaypointerProtoIds.Keys.ToHashSet());
}
private void OnPlayerDetached(Entity player, ref PlayerDetachedEvent args)
{
if (player.Comp.WaypointerProtoIds == null)
return;
RemoveOverrides(player, player.Comp.WaypointerProtoIds.Keys.ToHashSet());
}
private void OnMapChanged(Entity player, ref MapUidChangedEvent args)
{
// Since we only override PVS on entities on the same map, if the person switches maps, they'll need new overrides.
RefreshOverrides(player);
}
///
/// Refreshes the Waypointer PVS Overiddes for an entity if they are controlled by a player.
///
/// The entity to have their overrides refreshed.
[PublicAPI]
public void RefreshOverrides(Entity player)
{
if (player.Comp.WaypointerProtoIds == null)
return;
RemoveOverrides(player, player.Comp.WaypointerProtoIds.Keys.ToHashSet());
AddOverrides(player, player.Comp.WaypointerProtoIds.Keys.ToHashSet());
}
protected override void AddOverrides(EntityUid player, HashSet> waypointers)
{
if (!_player.TryGetSessionByEntity(player, out var session))
return;
var playerXform = Transform(player);
foreach (var waypointerProtoId in waypointers)
{
if (!_prototype.Resolve(waypointerProtoId, out var prototype))
continue;
var waypointQuery = _entity.CompRegistryQueryEnumerator(prototype.TrackedComponents);
while (waypointQuery.MoveNext(out var target))
{
// Grids somehow already work, so we exclude them. No idea why. But I fear messing with them.
if (HasComp(target)
// If it doesn't have the trackable component either, it doesn't work.
|| HasComp(target)
// Check if the target fails/passes the whitelist/blacklist.
|| _whitelist.CheckBoth(target, whitelist: prototype.Whitelist, blacklist: prototype.Blacklist))
continue;
var targetXform = Transform(target);
// Check if they're in the same Map. If not, don't override.
if (targetXform.MapID == playerXform.MapID)
_pvsOverride.AddSessionOverride(target, session);
}
}
}
protected override void RemoveOverrides(EntityUid player, HashSet> waypointers)
{
if (!_player.TryGetSessionByEntity(player, out var session))
return;
foreach (var waypointerProtoId in waypointers)
{
if (!_prototype.Resolve(waypointerProtoId, out var prototype))
continue;
var waypointQuery = _entity.CompRegistryQueryEnumerator(prototype.TrackedComponents);
while (waypointQuery.MoveNext(out var target))
{
// Grids somehow already work, so we exclude them. No idea why. But I fear messing with them.
if (HasComp(target))
continue;
_pvsOverride.RemoveSessionOverride(target, session);
}
}
}
}