using System; using System.Collections.Generic; using System.Linq; namespace RobloxFiles { /// /// Describes an object in Roblox's DataModel hierarchy. /// Instances can have sets of properties loaded from *.rbxl/*.rbxm files. /// public class Instance { /// The ClassName of this Instance. public string ClassName; /// A list of properties that are defined under this Instance. private Dictionary props = new Dictionary(); public IReadOnlyDictionary Properties => props; protected List Children = new List(); private Instance parent; /// The name of this Instance, if a Name property is defined. public override string ToString() => Name; /// A unique identifier for this instance when being serialized. public string Referent { get; internal set; } /// Indicates whether the parent of this object is locked. public bool ParentLocked { get; internal set; } /// Indicates whether this Instance is marked as a Service in the binary file format. public bool IsService { get; internal set; } /// If this instance is a service, this indicates whether the service should be loaded via GetService when Roblox loads the place file. public bool IsRootedService { get; internal set; } /// Indicates whether this object should be serialized. public bool Archivable = true; /// Returns true if this Instance is an ancestor to the provided Instance. /// The instance whose descendance will be tested against this Instance. public bool IsAncestorOf(Instance descendant) { while (descendant != null) { if (descendant == this) return true; descendant = descendant.Parent; } return false; } /// Returns true if this Instance is a descendant of the provided Instance. /// The instance whose ancestry will be tested against this Instance. public bool IsDescendantOf(Instance ancestor) { return ancestor.IsAncestorOf(this); } public string Name { get { Property propName = GetProperty("Name"); if (propName == null) SetProperty("Name", "Instance"); return propName.Value.ToString(); } set { SetProperty("Name", value); } } /// /// The parent of this Instance, or null if the instance is the root of a tree. /// Setting the value of this property will throw an exception if: /// - The value is set to itself. /// - The value is a descendant of the Instance. /// public Instance Parent { get { return parent; } set { if (ParentLocked) throw new Exception("The Parent property of this instance is locked."); if (IsAncestorOf(value)) throw new Exception("Parent would result in circular reference."); if (Parent == this) throw new Exception("Attempt to set parent to self."); if (parent != null) parent.Children.Remove(this); value.Children.Add(this); parent = value; } } /// /// Returns a snapshot of the Instances currently parented to this Instance, as an array. /// public Instance[] GetChildren() { return Children.ToArray(); } /// /// Returns a snapshot of the Instances that are descendants of this Instance, as an array. /// public Instance[] GetDescendants() { List results = new List(); foreach (Instance child in Children) { // Add this child to the results. results.Add(child); // Add its descendants to the results. Instance[] descendants = child.GetDescendants(); results.AddRange(descendants); } return results.ToArray(); } /// /// Returns the first child of this Instance whose Name is the provided string name. /// If the instance is not found, this returns null. /// /// The Name of the Instance to find. /// Indicates if we should search descendants as well. public Instance FindFirstChild(string name, bool recursive = false) { Instance result = null; var query = Children.Where((child) => name == child.Name); if (query.Count() > 0) { result = query.First(); } else if (recursive) { foreach (Instance child in Children) { Instance found = child.FindFirstChild(name, true); if (found != null) { result = found; break; } } } return result; } /// /// Returns the first ancestor of this Instance whose Name is the provided string name. /// If the instance is not found, this returns null. /// /// The Name of the Instance to find. public Instance FindFirstAncestor(string name) { Instance ancestor = Parent; while (ancestor != null) { if (ancestor.Name == name) break; ancestor = ancestor.Parent; } return ancestor; } /// /// Returns the first ancestor of this Instance whose ClassName is the provided string className. /// If the instance is not found, this returns null. /// /// The Name of the Instance to find. public Instance FindFirstAncestorOfClass(string className) { Instance ancestor = Parent; while (ancestor != null) { if (ancestor.ClassName == className) break; ancestor = ancestor.Parent; } return ancestor; } /// /// Returns the first Instance whose ClassName is the provided string className. /// If the instance is not found, this returns null. /// /// The ClassName of the Instance to find. public Instance FindFirstChildOfClass(string className, bool recursive = false) { Instance result = null; var query = Children.Where((child) => className == child.ClassName); if (query.Count() > 0) result = query.First(); return result; } /// /// Returns a string descrbing the index traversal of this Instance, starting from its root ancestor. /// public string GetFullName() { string fullName = Name; Instance at = Parent; while (at != null) { fullName = at.Name + '.' + fullName; at = at.Parent; } return fullName; } /// /// Returns a Property object if a property with the specified name is defined in this Instance. /// public Property GetProperty(string name) { Property result = null; if (Properties.ContainsKey(name)) result = Properties[name]; return result; } /// /// Finds or creates a property with the specified name, and sets its value to the provided object. /// Returns the property object that had its value set, if the value is not null. /// public Property SetProperty(string name, object value, PropertyType? preferType = null) { Property prop = GetProperty(name) ?? new Property() { Type = preferType ?? PropertyType.Unknown, Name = name }; if (preferType == null) { object oldValue = prop.Value; Type oldType = oldValue?.GetType(); Type newType = value?.GetType(); if (oldType != newType) { if (value == null) { RemoveProperty(name); return prop; } string typeName = newType.Name; if (value is Instance) typeName = "Ref"; else if (value is int) typeName = "Int"; else if (value is long) typeName = "Int64"; Enum.TryParse(typeName, out prop.Type); } } prop.Value = value; if (prop.Instance == null) AddProperty(ref prop); return prop; } /// /// Looks for a property with the specified property name, and returns its value as an object. /// The resulting value may be null if the property is not serialized. /// You can use the templated ReadProperty overload to fetch it as a specific type with a default value provided. /// /// The name of the property to be fetched from this Instance. /// An object reference to the value of the specified property, if it exists. public object ReadProperty(string propertyName) { Property property = GetProperty(propertyName); return property?.Value; } /// /// Looks for a property with the specified property name, and returns it as the specified type. /// If it cannot be converted, the provided nullFallback value will be returned instead. /// /// The value type to convert to when finding the specified property name. /// The name of the property to be fetched from this Instance. /// A fallback value to be returned if casting to T fails, or the property is not found. /// public T ReadProperty(string propertyName, T nullFallback) { try { object result = ReadProperty(propertyName); return (T)result; } catch { return nullFallback; } } /// /// Looks for a property with the specified property name. If found, it will try to set the value of the referenced outValue to its value. /// Returns true if the property was found and its value was casted to the referenced outValue. /// If it returns false, the outValue will not have its value set. /// /// The value type to convert to when finding the specified property name. /// The name of the property to be fetched from this Instance. /// The value to write to if the property can be casted to T correctly. public bool TryReadProperty(string propertyName, ref T outValue) { try { object result = ReadProperty(propertyName); outValue = (T)result; return true; } catch { return false; } } /// /// Adds a property by reference to this Instance's property list. /// /// A reference to the property that will be added. internal void AddProperty(ref Property prop) { prop.Instance = this; if (prop.Name == "Name") { Property nameProp = GetProperty("Name"); if (nameProp != null) { nameProp.Value = prop.Value; return; } } props.Add(prop.Name, prop); } /// /// Removes a property with the provided name if a property with the provided name exists. /// /// The name of the property to be removed. /// True if a property with the provided name was removed. public bool RemoveProperty(string name) { Property prop = GetProperty(name); if (prop != null) prop.Instance = null; return props.Remove(name); } } }