using System.Linq;
using Content.Shared._DV.Waypointer.Components;
using Content.Shared._DV.Waypointer.Events;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Clothing;
using Content.Shared.Inventory;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Waypointer;
///
/// This solely handles giving the Waypoint component to equipees. This cannot be done on client, or else it would.
///
public abstract class SharedWaypointerSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
public override void Initialize()
{
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnActionPressed);
SubscribeLocalEvent(OnWaypointersToggled);
SubscribeLocalEvent(OnWaypointersStatusChanged);
SubscribeLocalEvent(OnEquip);
SubscribeLocalEvent(OnUnequip);
SubscribeLocalEvent(OnWaypointerChanged);
SubscribeLocalEvent>(OnWaypointerChanged);
}
private void OnMapInit(Entity player, ref MapInitEvent args)
{
SetWaypointerComponent(player);
}
private void OnActionPressed(Entity player, ref ActionManageWaypointersEvent args)
{
if (args.Handled)
return;
// To avoid adding UserInterfaceComponent on the BaseMob, we open the interface on the action entity, not the player.
_ui.OpenUi(args.Action.Owner, WaypointerUiKey.Key, player.Owner);
args.Handled = true;
}
protected virtual void OnWaypointersToggled(Entity action, ref WaypointersToggledMessage args)
{
// Messages are sent to the action entity - So we need to get the player from the component.
if (!TryComp(action.Comp.Container, out var waypointer)
|| waypointer.WaypointerProtoIds == null)
return;
waypointer.Active = args.IsActive;
_actions.SetToggled(action.AsNullable(), args.IsActive);
Dirty(action.Comp.Container.Value, waypointer);
}
private void OnWaypointersStatusChanged(Entity action, ref WaypointerStatusChangedMessage args)
{
// Messages are sent to the action entity - So we need to get the player from the component.
if (!TryComp(action.Comp.Container, out var waypointer)
|| waypointer.WaypointerProtoIds == null)
return;
waypointer.WaypointerProtoIds[args.ToggledWaypointerProtoId] = !waypointer.WaypointerProtoIds[args.ToggledWaypointerProtoId];
Dirty(action.Comp.Container.Value, waypointer);
}
private void OnEquip(Entity clothing, ref ClothingGotEquippedEvent args)
{
SetWaypointerComponent(args.Wearer);
}
private void OnUnequip(Entity clothing, ref ClothingGotUnequippedEvent args)
{
SetWaypointerComponent(args.Wearer);
}
private void OnWaypointerChanged(Entity clothing, ref WaypointerChangedEvent args)
{
args.Waypointers.UnionWith(clothing.Comp.WaypointerProtoIds);
}
private void OnWaypointerChanged(Entity clothing, ref InventoryRelayedEvent args)
{
args.Args.Waypointers.UnionWith(clothing.Comp.WaypointerProtoIds);
}
private void SetWaypointerComponent(EntityUid player)
{
if (_timing.ApplyingState)
return;
var comp = EnsureComp(player);
// The following is much easier to do with HashSets.
HashSet>? previousTable = null;
HashSet>? overridesToRemove = null;
if (comp.WaypointerProtoIds != null)
{
// Store the hashset for comparison later, if not null
previousTable = comp.WaypointerProtoIds.Keys.ToHashSet();
// The same as above. As an example, these have the values A and B. { A, B }
overridesToRemove = comp.WaypointerProtoIds.Keys.ToHashSet();
}
// We raise this on the entity to check for anything that could give the entity a waypointer.
var ev = new WaypointerChangedEvent();
RaiseLocalEvent(player, ref ev); // For example purposes, this will return { B, C }
if (overridesToRemove != null)
{
// Now we remove all the waypointers that the event gathered from the previous hashset.
// Removing { B, C } will leave us with { A }, as we don't have the waypointer A anymore.
overridesToRemove.ExceptWith(ev.Waypointers);
// We remove the overrides for the waypointer A.
RemoveOverrides(player, overridesToRemove);
}
if (ev.Waypointers.Count == 0) // Self-Explanatory - If there are no waypointers left, remove the component.
{
RemComp(player);
return;
}
// We need to turn it into a dictionary so we can keep track of every waypointer status, not important for overrides.
var newDict = ev.Waypointers.ToDictionary(key => key, _ => true);
if (comp.WaypointerProtoIds != null)
{
foreach (var pair in comp.WaypointerProtoIds.Where(pair => newDict.ContainsKey(pair.Key)))
{
// We set the status of every waypointer that persisted to their original value, not important for overrides.
newDict[pair.Key] = pair.Value;
}
}
// Then replace the old dictionary with the new one
comp.WaypointerProtoIds = newDict;
// Here we now remove every waypointer that doesn't need new overrides.
// The waypointer B was already overriden, since it's in the earlier hashset { A, B }
// So, by removing { A, b} from { B, C }, we get { C }, which is the only new waypointer - thus needing overrides.
if (previousTable != null)
ev.Waypointers.ExceptWith(previousTable);
AddOverrides(player, ev.Waypointers);
Dirty(player, comp);
}
protected virtual void AddOverrides(EntityUid player, HashSet> waypointers)
{
}
protected virtual void RemoveOverrides(EntityUid player, HashSet> waypointers)
{
}
}
[Serializable, NetSerializable]
public enum WaypointerUiKey : byte
{
Key,
}