using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Robust.Server.ViewVariables.Traits; using Robust.Shared.GameObjects; using Robust.Shared.Log; using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Server.ViewVariables { internal sealed class ViewVariablesSession : IViewVariablesSession { private readonly List _traits = new(); private readonly ISawmill _logger; public IServerViewVariablesInternal Host { get; } public IRobustSerializer RobustSerializer { get; } public IEntityManager EntityManager { get; } public NetUserId PlayerUser { get; } public object Object { get; } public uint SessionId { get; } public Type ObjectType { get; } public Action? ObjectChangeDelegate { get; } /// The session ID of the player who opened this session. /// The object we represent. /// /// The session ID for this session. This is what the server and client use to talk about this session. /// /// The view variables host owning this session. public ViewVariablesSession(NetUserId playerUser, object o, Action? objectChangeDelegate, uint sessionId, IServerViewVariablesInternal host, IRobustSerializer robustSerializer, IEntityManager entMan, ISawmill logger) { PlayerUser = playerUser; Object = o; SessionId = sessionId; ObjectType = o.GetType(); ObjectChangeDelegate = objectChangeDelegate; Host = host; RobustSerializer = robustSerializer; EntityManager = entMan; _logger = logger; var traitIds = Host.TraitIdsFor(ObjectType); if (traitIds.Contains(ViewVariablesTraits.Members)) { var trait = new ViewVariablesTraitMembers(this, _logger); _traits.Add(trait); } if (traitIds.Contains(ViewVariablesTraits.Enumerable)) { var trait = new ViewVariablesTraitEnumerable(this); _traits.Add(trait); } if (traitIds.Contains(ViewVariablesTraits.Entity)) { var trait = new ViewVariablesTraitEntity(this); _traits.Add(trait); } } public ViewVariablesBlob? DataRequest(ViewVariablesRequest messageRequestMeta) { if (messageRequestMeta is ViewVariablesRequestMetadata) { return new ViewVariablesBlobMetadata { ObjectType = ObjectType.AssemblyQualifiedName, ObjectTypePretty = TypeAbbreviation.Abbreviate(ObjectType), Stringified = Object.ToString(), Traits = new List(Host.TraitIdsFor(ObjectType)) }; } foreach (var trait in _traits) { var blob = trait.DataRequest(messageRequestMeta); if (blob != null) { return blob; } } return null; } public void Modify(object[] propertyIndex, object value) { foreach (var trait in _traits) { if (trait.TryModifyProperty(propertyIndex, value)) { break; } } // Auto-dirty component. Only works when modifying a field that is directly on a component, // Does not work for nested objects. if (Object is Component { NetSyncEnabled: true } comp) EntityManager.Dirty(comp.Owner, comp); } public bool TryGetRelativeObject(object[] propertyIndex, out object? value) { // First property chain entry is for the trait, rest is value. value = default; foreach (var trait in _traits) { if (trait.TryGetRelativeObject(propertyIndex[0], out value)) { goto found; } } return false; // Yes I just used goto. // The fuck you gonna do about it? found: for (var i = 1; i < propertyIndex.Length; i++) { var selector = propertyIndex[i]; switch (selector) { case ViewVariablesSelectorKeyValuePair kvPair: if (value == null || !value.GetType().IsGenericType || value.GetType().GetGenericTypeDefinition() != typeof(KeyValuePair<,>)) return false; dynamic kv = value; value = kvPair.Key ? kv.Key : kv.Value; break; case ViewVariablesTupleIndexSelector indexSelector when value is ITuple tuple: value = indexSelector.Index <= tuple.Length - 1 ? tuple[indexSelector.Index] : null; break; } } return true; } } }