using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Threading.Tasks; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.ViewVariables.Editors; using Robust.Client.ViewVariables.Instances; using Robust.Shared.Audio; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; using static Robust.Client.ViewVariables.Editors.VVPropEditorNumeric; namespace Robust.Client.ViewVariables { internal sealed partial class ClientViewVariablesManager : ViewVariablesManager, IClientViewVariablesManagerInternal { [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IClientNetManager _netManager = default!; [Dependency] private readonly IRobustSerializer _robustSerializer = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IResourceManager _resManager = default!; private uint _nextReqId = 1; private readonly Vector2i _defaultWindowSize = (640, 420); private readonly Dictionary _windows = new(); private readonly Dictionary _sessions = new(); private readonly Dictionary> _requestedSessions = new(); private readonly Dictionary> _requestedData = new(); public override void Initialize() { base.Initialize(); InitializeDomains(); _netManager.RegisterNetMessage(_netMessageOpenSession); _netManager.RegisterNetMessage(_netMessageRemoteData); _netManager.RegisterNetMessage(_netMessageCloseSession); _netManager.RegisterNetMessage(_netMessageDenySession); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); } public VVPropEditor PropertyFor(Type? type) { // TODO: make this more flexible. if (type == null) { return new VVPropEditorDummy(); } if (type == typeof(sbyte)) { return new VVPropEditorNumeric(NumberType.SByte); } if (type == typeof(byte)) { return new VVPropEditorNumeric(NumberType.Byte); } if (type == typeof(ushort)) { return new VVPropEditorNumeric(NumberType.UShort); } if (type == typeof(short)) { return new VVPropEditorNumeric(NumberType.Short); } if (type == typeof(uint)) { return new VVPropEditorNumeric(NumberType.UInt); } if (type == typeof(int)) { return new VVPropEditorNumeric(NumberType.Int); } if (type == typeof(ulong)) { return new VVPropEditorNumeric(NumberType.ULong); } if (type == typeof(long)) { return new VVPropEditorNumeric(NumberType.Long); } if (type == typeof(float)) { return new VVPropEditorNumeric(NumberType.Float); } if (type == typeof(double)) { return new VVPropEditorNumeric(NumberType.Double); } if (type == typeof(decimal)) { return new VVPropEditorNumeric(NumberType.Decimal); } if (type == typeof(string)) { return new VVPropEditorString(); } if (type == typeof(EntProtoId?)) { return new VVPropEditorNullableEntProtoId(); } if (type == typeof(EntProtoId)) { return new VVPropEditorEntProtoId(); } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>)) { var editor = (VVPropEditor)Activator.CreateInstance( typeof(VVPropEditorProtoId<>).MakeGenericType(type.GenericTypeArguments[0]))!; IoCManager.InjectDependencies(editor); return editor; } if (typeof(IPrototype).IsAssignableFrom(type) || typeof(ViewVariablesBlobMembers.PrototypeReferenceToken).IsAssignableFrom(type)) { return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorIPrototype<>).MakeGenericType(type))!; } if (typeof(ISelfSerialize).IsAssignableFrom(type)) { return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorISelfSerializable<>).MakeGenericType(type))!; } if (type.IsEnum) { return new VVPropEditorEnum(); } if (type == typeof(Vector2)) { return new VVPropEditorVector2(intVec: false); } if (type == typeof(Vector2i)) { return new VVPropEditorVector2(intVec: true); } if (type == typeof(bool)) { return new VVPropEditorBoolean(); } if (type == typeof(Angle)) { return new VVPropEditorAngle(); } if (type == typeof(Box2)) { return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2); } if (type == typeof(Box2i)) { return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2i); } if (type == typeof(UIBox2)) { return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2); } if (type == typeof(UIBox2i)) { return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2i); } if (type == typeof(EntityCoordinates)) { return new VVPropEditorEntityCoordinates(); } if (type == typeof(EntityUid)) { return new VVPropEditorEntityUid(); } if (type == typeof(NetEntity)) { return new VVPropEditorNetEntity(); } if (type == typeof(Color)) { return new VVPropEditorColor(); } if (type == typeof(TimeSpan)) { return new VVPropEditorTimeSpan(); } if (typeof(SoundSpecifier).IsAssignableFrom(type)) { var control = new VVPropEditorSoundSpecifier(_protoManager, _resManager); return control; } if (type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { return new VVPropEditorKeyValuePair(); } if (type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken) && !type.IsValueType) { return new VVPropEditorReference(); } return new VVPropEditorDummy(); } public void OpenVV(object obj) { // TODO: more flexibility in allowing custom instances here. ViewVariablesInstance instance; if (obj is NetEntity netEntity && _entityManager.GetEntity(netEntity).IsValid()) { instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer, Sawmill); } else { instance = new ViewVariablesInstanceObject(this, _robustSerializer); } var window = new DefaultWindow {Title = Loc.GetString("view-variables")}; instance.Initialize(window, obj); window.OnClose += () => _closeInstance(instance, false); _windows.Add(instance, window); window.SetSize = _defaultWindowSize; window.Open(); } public void OpenVV(string path) { if (ReadPath(path) is {} obj) OpenVV(obj); } public async void OpenVV(ViewVariablesObjectSelector selector) { var window = new DefaultWindow { Title = Loc.GetString("view-variables"), SetSize = _defaultWindowSize }; var loadingLabel = new Label {Text = "Retrieving remote object data from server..."}; window.Contents.AddChild(loadingLabel); // We need to request the data, THEN create an instance. // Because we don't know what instance to make until we asked the server about the object data. ViewVariablesRemoteSession session; try { session = await RequestSession(selector); } catch (SessionDenyException e) { loadingLabel.Text = $"Server denied session request: {e.Reason}"; return; } var blob = await RequestData(session, new ViewVariablesRequestMetadata()); var type = Type.GetType(blob.ObjectType); // TODO: more flexibility in allowing custom instances here. ViewVariablesInstance instance; if (type != null && typeof(NetEntity).IsAssignableFrom(type)) { instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer, Sawmill); } else { instance = new ViewVariablesInstanceObject(this, _robustSerializer); } loadingLabel.Dispose(); instance.Initialize(window, blob, session); window.OnClose += () => _closeInstance(instance, false); _windows.Add(instance, window); window.Size = _defaultWindowSize; window.Open(); } public Task RequestSession(ViewVariablesObjectSelector selector) { var msg = new MsgViewVariablesReqSession(); msg.Selector = selector; msg.RequestId = _nextReqId++; _netManager.ClientSendMessage(msg); var tcs = new TaskCompletionSource(); _requestedSessions.Add(msg.RequestId, tcs); return tcs.Task; } public Task RequestData(ViewVariablesRemoteSession session, ViewVariablesRequest meta) { if (session.Closed) { throw new ArgumentException("Session is closed", nameof(session)); } var msg = new MsgViewVariablesReqData(); var reqId = msg.RequestId = _nextReqId++; msg.RequestMeta = meta; msg.SessionId = session.SessionId; _netManager.ClientSendMessage(msg); var tcs = new TaskCompletionSource(); _requestedData.Add(reqId, tcs); return tcs.Task; } public async Task RequestData(ViewVariablesRemoteSession session, ViewVariablesRequest meta) where T : ViewVariablesBlob { return (T) await RequestData(session, meta); } public void CloseSession(ViewVariablesRemoteSession session) { if (!_sessions.ContainsKey(session.SessionId)) { throw new ArgumentException(); } var closeMsg = new MsgViewVariablesCloseSession(); closeMsg.SessionId = session.SessionId; _netManager.ClientSendMessage(closeMsg); } public bool TryGetSession(uint sessionId, [NotNullWhen(true)] out ViewVariablesRemoteSession? session) { return _sessions.TryGetValue(sessionId, out session); } public void ModifyRemote(ViewVariablesRemoteSession session, object[] propertyIndex, object? value, bool reinterpretValue = false) { if (!_sessions.ContainsKey(session.SessionId)) { throw new ArgumentException(); } var msg = new MsgViewVariablesModifyRemote(); msg.SessionId = session.SessionId; msg.ReinterpretValue = reinterpretValue; msg.PropertyIndex = propertyIndex; msg.Value = value; _netManager.ClientSendMessage(msg); } private void _closeInstance(ViewVariablesInstance instance, bool closeWindow) { if (!_windows.TryGetValue(instance, out var window)) { throw new ArgumentException(); } if (closeWindow) { window.Dispose(); } _windows.Remove(instance); instance.Close(); } private void _netMessageOpenSession(MsgViewVariablesOpenSession msg) { if (!_requestedSessions.TryGetValue(msg.RequestId, out var tcs)) { Sawmill.Error("Server sent us new session {0}/{1} which we didn't request.", msg.RequestId, msg.SessionId); return; } _requestedSessions.Remove(msg.RequestId); var session = new ViewVariablesRemoteSession(msg.SessionId); _sessions.Add(msg.SessionId, session); tcs.SetResult(session); } private void _netMessageCloseSession(MsgViewVariablesCloseSession message) { if (!_sessions.TryGetValue(message.SessionId, out var session)) { Sawmill.Warning("Got a close session message for an unknown session: {0}", message.SessionId); return; } session.Closed = true; _sessions.Remove(message.SessionId); } private void _netMessageRemoteData(MsgViewVariablesRemoteData message) { if (!_requestedData.TryGetValue(message.RequestId, out var tcs)) { Sawmill.Warning("Server sent us data we didn't request: {0}.", message.RequestId); return; } _requestedData.Remove(message.RequestId); tcs.SetResult(message.Blob); } private void _netMessageDenySession(MsgViewVariablesDenySession message) { if (!_requestedSessions.TryGetValue(message.RequestId, out var tcs)) { Sawmill.Warning("Server sent us a deny session {0} which we didn't request.", message.RequestId); return; } _requestedSessions.Remove(message.RequestId); tcs.SetException(new SessionDenyException(message.Reason)); } protected override bool CheckPermissions(INetChannel channel, string command) { // Acquiesce, client!! Do what the server tells you. return true; } protected override bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommonSession? session) { session = null; return false; } } [Virtual] public class SessionDenyException : Exception { public SessionDenyException(ViewVariablesResponseCode reason) { Reason = reason; } public ViewVariablesResponseCode Reason { get; } } }