using System; using System.Collections.Generic; using Robust.Shared.GameStates; using Robust.Shared.Player; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.ViewVariables; namespace Robust.Shared.GameObjects { [RegisterComponent, NetworkedComponent, Access(typeof(SharedUserInterfaceSystem))] public sealed partial class UserInterfaceComponent : Component, IComponentDelta { /// public GameTick LastUnclassifiedDirty { get; set; } /// public GameTick[] LastModifiedFields { get; set; } = []; /// /// The currently open interfaces. Used clientside to store the UI. /// [ViewVariables, Access(Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.ReadWriteExecute)] public readonly Dictionary ClientOpenInterfaces = new(); [DataField, AlwaysPushInheritance] internal Dictionary Interfaces = new(); /// /// Actors that currently have interfaces open. /// [DataField] public Dictionary> Actors = new(); /// /// Legacy data, new BUIs should be using comp states. /// public Dictionary States = new(); } [Serializable, NetSerializable] internal sealed class UserInterfaceComponentState( Dictionary> actors, Dictionary states, Dictionary data) : IComponentState { public Dictionary> Actors = actors; public Dictionary States = states; public Dictionary Data = data; } [Serializable, NetSerializable] internal sealed class UserInterfaceActorsDeltaState : IComponentDeltaState { public Dictionary> Actors = new(); public void ApplyToFullState(UserInterfaceComponentState fullState) { fullState.Actors.Clear(); foreach (var (key, value) in Actors) { fullState.Actors.Add(key, value); } } public UserInterfaceComponentState CreateNewFullState(UserInterfaceComponentState fullState) { var newState = new UserInterfaceComponentState( new Dictionary>(Actors), new(fullState.States), new(fullState.Data)); return newState; } } [Serializable, NetSerializable] internal sealed class UserInterfaceStatesDeltaState : IComponentDeltaState { public Dictionary States = new(); public void ApplyToFullState(UserInterfaceComponentState fullState) { fullState.States.Clear(); foreach (var (key, value) in States) { fullState.States.Add(key, value); } } public UserInterfaceComponentState CreateNewFullState(UserInterfaceComponentState fullState) { var newState = new UserInterfaceComponentState( new Dictionary>(fullState.Actors), new(States), new(fullState.Data)); return newState; } } [DataDefinition, Serializable, NetSerializable] public sealed partial class InterfaceData { [DataField("type", required: true)] public string ClientType { get; private set; } = default!; /// /// Maximum range before a BUI auto-closes. A non-positive number means there is no limit. /// [DataField] public float InteractionRange = 2f; // TODO BUI move to content? // I've tried to keep the name general, but really this is a bool for: can ghosts/stunned/dead people press buttons on this UI? /// /// Determines whether the server should verify that a client is capable of performing generic UI interactions when receiving UI messages. /// /// /// Avoids requiring each system to individually validate client inputs. However, perhaps some BUIs are supposed to be bypass accessibility checks /// [DataField] public bool RequireInputValidation = true; public InterfaceData(string clientType, float interactionRange = 2f, bool requireInputValidation = true) { ClientType = clientType; InteractionRange = interactionRange; RequireInputValidation = requireInputValidation; } public InterfaceData(InterfaceData data) { ClientType = data.ClientType; InteractionRange = data.InteractionRange; RequireInputValidation = data.RequireInputValidation; } } /// /// Raised whenever the server receives a BUI message from a client relating to a UI that requires input /// validation. /// public sealed class BoundUserInterfaceMessageAttempt( EntityUid actor, EntityUid target, Enum uiKey, BoundUserInterfaceMessage message) : CancellableEntityEventArgs { public readonly EntityUid Actor = actor; public readonly EntityUid Target = target; public readonly Enum UiKey = uiKey; public readonly BoundUserInterfaceMessage Message = message; } /// /// Raised whenever the server receives a BUI wrap message from a client for a valid interface. /// [ByRefEvent] public readonly record struct BoundUserInterfaceMessageReceivedEvent( EntityUid Actor, EntityUid Target, Enum UiKey); [NetSerializable, Serializable] public abstract class BoundUserInterfaceState { } /// /// Abstract class for local BUI events. /// public abstract class BaseLocalBoundUserInterfaceEvent : BaseBoundUserInterfaceEvent { /// /// The Entity receiving the message. /// Only set when the message is raised as a directed event. /// public EntityUid Entity = EntityUid.Invalid; } /// /// Abstract class for all BUI events. /// [Serializable, NetSerializable] public abstract class BaseBoundUserInterfaceEvent : EntityEventArgs { /// /// The UI of this message. /// Only set when the message is raised as a directed event. /// public Enum UiKey = default!; /// /// The session sending or receiving this message. /// Only set when the message is raised as a directed event. /// [NonSerialized] public EntityUid Actor = default!; } /// /// Abstract class for networked BUI events. /// [NetSerializable, Serializable] public abstract class BoundUserInterfaceMessage : BaseBoundUserInterfaceEvent { /// /// The Entity receiving the message. /// Only set when the message is raised as a directed event. /// public NetEntity Entity { get; set; } = NetEntity.Invalid; } [NetSerializable, Serializable] public sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage { } [NetSerializable, Serializable] public sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage { } [Serializable, NetSerializable] internal abstract class BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : EntityEventArgs { public readonly NetEntity Entity = entity; public readonly BoundUserInterfaceMessage Message = message; public readonly Enum UiKey = uiKey; } /// /// Helper message raised from client to server. /// [Serializable, NetSerializable] internal sealed class BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : BaseBoundUIWrapMessage(entity, message, uiKey); public sealed class BoundUIOpenedEvent : BaseLocalBoundUserInterfaceEvent { public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, EntityUid actor) { UiKey = uiKey; Entity = uid; Actor = actor; } } public sealed class BoundUIClosedEvent : BaseLocalBoundUserInterfaceEvent { public BoundUIClosedEvent(Enum uiKey, EntityUid uid, EntityUid actor) { UiKey = uiKey; Entity = uid; Actor = actor; } } }