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