using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Robust.Server.ViewVariables.Traits; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network.Messages; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Server.ViewVariables { /// /// Traits define what behavior an object can have that VV cares about. /// So like, is it enumerable, does it have VV accessible members. That kinda deal. /// These are the "modular" way of extending VV. /// Server traits are bound to one , AKA one object. /// internal abstract class ViewVariablesTrait { internal readonly IViewVariablesSession Session; protected ViewVariablesTrait(IViewVariablesSession session) { Session = session; } /// /// Invoked when the client requests a data blob from the session this trait is bound to, /// Using . /// /// /// The request meta object, equivalent to the object. /// /// /// If this trait doesn't care about this request, a meaningful blob otherwise. /// No, not the game mode, the other kind of blob. /// public virtual ViewVariablesBlob? DataRequest(ViewVariablesRequest viewVariablesRequest) { return null; } /// /// Tries to get an object relative to the object handled by this trait. /// This is for doing the whole "this guy wants to open a VV window on a sub object" deal. /// /// /// The first element of a property index list as described by /// /// "Why is this the first only?" Well my idea was that it should get the first level of the index, /// and then if it were like a tuple the session would handle the rest of the indices. /// Whether that will work out in practice when the next person comes along to implement that is probably uncertain, /// so if you wanna change that future person go ahead. /// /// /// /// The to-be value of the object if this trait managed to retrieve it. /// True if we retrieved a value, false otherwise. public virtual bool TryGetRelativeObject(object property, out object? value) { value = default; return false; } /// /// TRY to modify a property on this trait. /// For example tries to modify the object's members here. /// /// /// A list of objects that your trait might understand to figure out what to modify. /// See for an explanation. /// /// /// The new value of the object. /// /// True if this trait can and did modify the property, false otherwise. public virtual bool TryModifyProperty(object[] property, object value) { return false; } /// /// Swaps values like references over to reference tokens to prevent issues. /// protected object? MakeValueNetSafe(object? value) { if (value == null) { return null; } if (value is EntityUid uid) return IoCManager.Resolve().GetComponentOrNull(uid)?.NetEntity ?? NetEntity.Invalid; if (value is SoundSpecifier) return value; var valType = value.GetType(); if (!valType.IsValueType) { // TODO: More flexibility in which types can be sent here. // We don't blindly send any prototypes, we ONLY send prototypes for valid, registered variants. if (typeof(IPrototype).IsAssignableFrom(valType) && IoCManager.Resolve().TryGetKindFrom(valType, out var variant)) { return new ViewVariablesBlobMembers.PrototypeReferenceToken() { Stringified = PrettyPrint.PrintUserFacing(value), ID = ((IPrototype) value).ID, Variant = variant }; } if (valType != typeof(string)) { return new ViewVariablesBlobMembers.ReferenceToken { Stringified = PrettyPrint.PrintUserFacing(value) }; } } else if (!Session.RobustSerializer.CanSerialize(valType)) { // Handle KeyValuePair<,> if (valType.IsGenericType && valType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { dynamic kv = value; var key = kv.Key; var val = kv.Value; return new ViewVariablesBlobMembers.ServerKeyValuePairToken { Key = MakeValueNetSafe(key), Value = MakeValueNetSafe(val) }; } // Handle ValueTuples if (typeof(ITuple).IsAssignableFrom(valType)) { var tuple = (ITuple)value; var items = new object?[tuple.Length]; for (var i = 0; i < tuple.Length; i++) { items[i] = MakeValueNetSafe(tuple[i]); } return new ViewVariablesBlobMembers.ServerTupleToken { Items = items }; } // Can't send this value type over the wire. return new ViewVariablesBlobMembers.ServerValueTypeToken { Stringified = value.ToString() }; } return value; } /// /// Swaps null values for net safe equivalents depending on type. /// Will return null for types where null is considered net safe. /// protected object? MakeNullValueNetSafe(Type type) { if (typeof(IPrototype).IsAssignableFrom(type)) { var protoMan = IoCManager.Resolve(); if (protoMan.TryGetKindFrom(type, out var variant)) return new ViewVariablesBlobMembers.PrototypeReferenceToken() { ID = null, Variant = variant, Stringified = PrettyPrint.PrintUserFacing(null), }; } return null; } } }