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, }