Changes properties to use automatic property initializers
This commit is contained in:
parent
f0a48ad431
commit
06fa31a7bf
623 changed files with 13072 additions and 19304 deletions
|
|
@ -4,37 +4,31 @@ namespace Server
|
|||
{
|
||||
public class UsageAttribute : Attribute
|
||||
{
|
||||
private string m_Usage;
|
||||
|
||||
public string Usage => m_Usage;
|
||||
public string Usage { get; }
|
||||
|
||||
public UsageAttribute( string usage )
|
||||
{
|
||||
m_Usage = usage;
|
||||
Usage = usage;
|
||||
}
|
||||
}
|
||||
|
||||
public class DescriptionAttribute : Attribute
|
||||
{
|
||||
private string m_Description;
|
||||
|
||||
public string Description => m_Description;
|
||||
public string Description { get; }
|
||||
|
||||
public DescriptionAttribute( string description )
|
||||
{
|
||||
m_Description = description;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
public class AliasesAttribute : Attribute
|
||||
{
|
||||
private string[] m_Aliases;
|
||||
|
||||
public string[] Aliases => m_Aliases;
|
||||
public string[] Aliases { get; }
|
||||
|
||||
public AliasesAttribute( params string[] aliases )
|
||||
{
|
||||
m_Aliases = aliases;
|
||||
Aliases = aliases;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,31 +9,19 @@ namespace Server.Commands
|
|||
{
|
||||
public class Batch : BaseCommand
|
||||
{
|
||||
private BaseCommandImplementor m_Scope;
|
||||
private string m_Condition;
|
||||
private ArrayList m_BatchCommands;
|
||||
public BaseCommandImplementor Scope { get; set; }
|
||||
|
||||
public BaseCommandImplementor Scope
|
||||
{
|
||||
get => m_Scope;
|
||||
set => m_Scope = value;
|
||||
}
|
||||
public string Condition { get; set; }
|
||||
|
||||
public string Condition
|
||||
{
|
||||
get => m_Condition;
|
||||
set => m_Condition = value;
|
||||
}
|
||||
|
||||
public ArrayList BatchCommands => m_BatchCommands;
|
||||
public ArrayList BatchCommands { get; }
|
||||
|
||||
public Batch()
|
||||
{
|
||||
Commands = new[]{ "Batch" };
|
||||
ListOptimized = true;
|
||||
|
||||
m_BatchCommands = new ArrayList();
|
||||
m_Condition = "";
|
||||
BatchCommands = new ArrayList();
|
||||
Condition = "";
|
||||
}
|
||||
|
||||
public override void ExecuteList( CommandEventArgs e, ArrayList list )
|
||||
|
|
@ -46,19 +34,19 @@ namespace Server.Commands
|
|||
|
||||
try
|
||||
{
|
||||
BaseCommand[] commands = new BaseCommand[m_BatchCommands.Count];
|
||||
CommandEventArgs[] eventArgs = new CommandEventArgs[m_BatchCommands.Count];
|
||||
BaseCommand[] commands = new BaseCommand[BatchCommands.Count];
|
||||
CommandEventArgs[] eventArgs = new CommandEventArgs[BatchCommands.Count];
|
||||
|
||||
for ( int i = 0; i < m_BatchCommands.Count; ++i )
|
||||
for ( int i = 0; i < BatchCommands.Count; ++i )
|
||||
{
|
||||
BatchCommand bc = (BatchCommand)m_BatchCommands[i];
|
||||
BatchCommand bc = (BatchCommand)BatchCommands[i];
|
||||
|
||||
string commandString, argString;
|
||||
string[] args;
|
||||
|
||||
bc.GetDetails( out commandString, out argString, out args );
|
||||
|
||||
BaseCommand command = m_Scope.Commands[commandString];
|
||||
BaseCommand command = Scope.Commands[commandString];
|
||||
|
||||
commands[i] = command;
|
||||
eventArgs[i] = new CommandEventArgs( e.Mobile, commandString, argString, args );
|
||||
|
|
@ -74,7 +62,7 @@ namespace Server.Commands
|
|||
e.Mobile.SendMessage( "You do not have access to that command: {0}.", commandString );
|
||||
return;
|
||||
}
|
||||
if ( !command.ValidateArgs( m_Scope, eventArgs[i] ) )
|
||||
if ( !command.ValidateArgs( Scope, eventArgs[i] ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -83,7 +71,7 @@ namespace Server.Commands
|
|||
for ( int i = 0; i < commands.Length; ++i )
|
||||
{
|
||||
BaseCommand command = commands[i];
|
||||
BatchCommand bc = (BatchCommand)m_BatchCommands[i];
|
||||
BatchCommand bc = (BatchCommand)BatchCommands[i];
|
||||
|
||||
if ( list.Count > 20 )
|
||||
CommandLogging.Enabled = false;
|
||||
|
|
@ -153,26 +141,26 @@ namespace Server.Commands
|
|||
|
||||
public bool Run( Mobile from )
|
||||
{
|
||||
if ( m_Scope == null )
|
||||
if ( Scope == null )
|
||||
{
|
||||
from.SendMessage( "You must select the batch command scope." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( m_Condition.Length > 0 && !m_Scope.SupportsConditionals )
|
||||
if ( Condition.Length > 0 && !Scope.SupportsConditionals )
|
||||
{
|
||||
from.SendMessage( "This command scope does not support conditionals." );
|
||||
return false;
|
||||
}
|
||||
if ( m_Condition.Length > 0 && !Utility.InsensitiveStartsWith( m_Condition, "where" ) )
|
||||
if ( Condition.Length > 0 && !Utility.InsensitiveStartsWith( Condition, "where" ) )
|
||||
{
|
||||
from.SendMessage( "The condition field must start with \"where\"." );
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] args = CommandSystem.Split( m_Condition );
|
||||
string[] args = CommandSystem.Split( Condition );
|
||||
|
||||
m_Scope.Process( from, this, args );
|
||||
Scope.Process( from, this, args );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -194,44 +182,33 @@ namespace Server.Commands
|
|||
|
||||
public class BatchCommand
|
||||
{
|
||||
private string m_Command;
|
||||
private string m_Object;
|
||||
public string Command { get; set; }
|
||||
|
||||
public string Command
|
||||
{
|
||||
get => m_Command;
|
||||
set => m_Command = value;
|
||||
}
|
||||
|
||||
public string Object
|
||||
{
|
||||
get => m_Object;
|
||||
set => m_Object = value;
|
||||
}
|
||||
public string Object { get; set; }
|
||||
|
||||
public void GetDetails( out string command, out string argString, out string[] args )
|
||||
{
|
||||
int indexOf = m_Command.IndexOf( ' ' );
|
||||
int indexOf = Command.IndexOf( ' ' );
|
||||
|
||||
if ( indexOf >= 0 )
|
||||
{
|
||||
argString = m_Command.Substring( indexOf + 1 );
|
||||
argString = Command.Substring( indexOf + 1 );
|
||||
|
||||
command = m_Command.Substring( 0, indexOf );
|
||||
command = Command.Substring( 0, indexOf );
|
||||
args = CommandSystem.Split( argString );
|
||||
}
|
||||
else
|
||||
{
|
||||
argString = "";
|
||||
command = m_Command.ToLower();
|
||||
command = Command.ToLower();
|
||||
args = new string[0];
|
||||
}
|
||||
}
|
||||
|
||||
public BatchCommand( string command, string obj )
|
||||
{
|
||||
m_Command = command;
|
||||
m_Object = obj;
|
||||
Command = command;
|
||||
Object = obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1105,11 +1105,9 @@ namespace Server.Commands
|
|||
|
||||
public class DecorationEntry
|
||||
{
|
||||
private Point3D m_Location;
|
||||
private string m_Extra;
|
||||
public Point3D Location { get; }
|
||||
|
||||
public Point3D Location => m_Location;
|
||||
public string Extra => m_Extra;
|
||||
public string Extra { get; }
|
||||
|
||||
public DecorationEntry( string line )
|
||||
{
|
||||
|
|
@ -1119,8 +1117,8 @@ namespace Server.Commands
|
|||
Pop( out y, ref line );
|
||||
Pop( out z, ref line );
|
||||
|
||||
m_Location = new Point3D( Utility.ToInt32( x ), Utility.ToInt32( y ), Utility.ToInt32( z ) );
|
||||
m_Extra = line;
|
||||
Location = new Point3D( Utility.ToInt32( x ), Utility.ToInt32( y ), Utility.ToInt32( z ) );
|
||||
Extra = line;
|
||||
}
|
||||
|
||||
public void Pop( out string v, ref string line )
|
||||
|
|
|
|||
|
|
@ -1102,11 +1102,9 @@ namespace Server.Commands
|
|||
|
||||
public class DecorationEntryMag
|
||||
{
|
||||
private Point3D m_Location;
|
||||
private string m_Extra;
|
||||
public Point3D Location { get; }
|
||||
|
||||
public Point3D Location => m_Location;
|
||||
public string Extra => m_Extra;
|
||||
public string Extra { get; }
|
||||
|
||||
public DecorationEntryMag( string line )
|
||||
{
|
||||
|
|
@ -1116,8 +1114,8 @@ namespace Server.Commands
|
|||
Pop( out y, ref line );
|
||||
Pop( out z, ref line );
|
||||
|
||||
m_Location = new Point3D( Utility.ToInt32( x ), Utility.ToInt32( y ), Utility.ToInt32( z ) );
|
||||
m_Extra = line;
|
||||
Location = new Point3D( Utility.ToInt32( x ), Utility.ToInt32( y ), Utility.ToInt32( z ) );
|
||||
Extra = line;
|
||||
}
|
||||
|
||||
public void Pop( out string v, ref string line )
|
||||
|
|
|
|||
|
|
@ -1402,16 +1402,14 @@ namespace Server.Commands
|
|||
|
||||
private class SpeechEntry
|
||||
{
|
||||
private int m_Index;
|
||||
private List<string> m_Strings;
|
||||
public int Index { get; }
|
||||
|
||||
public int Index => m_Index;
|
||||
public List<string> Strings => m_Strings;
|
||||
public List<string> Strings { get; }
|
||||
|
||||
public SpeechEntry( int index )
|
||||
{
|
||||
m_Index = index;
|
||||
m_Strings = new List<string>();
|
||||
Index = index;
|
||||
Strings = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1475,25 +1473,23 @@ namespace Server.Commands
|
|||
|
||||
public class DocCommandEntry
|
||||
{
|
||||
private AccessLevel m_AccessLevel;
|
||||
private string m_Name;
|
||||
private string[] m_Aliases;
|
||||
private string m_Usage;
|
||||
private string m_Description;
|
||||
public AccessLevel AccessLevel { get; }
|
||||
|
||||
public AccessLevel AccessLevel => m_AccessLevel;
|
||||
public string Name => m_Name;
|
||||
public string[] Aliases => m_Aliases;
|
||||
public string Usage => m_Usage;
|
||||
public string Description => m_Description;
|
||||
public string Name { get; }
|
||||
|
||||
public string[] Aliases { get; }
|
||||
|
||||
public string Usage { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public DocCommandEntry( AccessLevel accessLevel, string name, string[] aliases, string usage, string description )
|
||||
{
|
||||
m_AccessLevel = accessLevel;
|
||||
m_Name = name;
|
||||
m_Aliases = aliases;
|
||||
m_Usage = usage;
|
||||
m_Description = description;
|
||||
AccessLevel = accessLevel;
|
||||
Name = name;
|
||||
Aliases = aliases;
|
||||
Usage = usage;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2530,31 +2526,29 @@ namespace Server.Commands
|
|||
|
||||
public class BodyEntry
|
||||
{
|
||||
private Body m_Body;
|
||||
private ModelBodyType m_BodyType;
|
||||
private string m_Name;
|
||||
public Body Body { get; }
|
||||
|
||||
public Body Body => m_Body;
|
||||
public ModelBodyType BodyType => m_BodyType;
|
||||
public string Name => m_Name;
|
||||
public ModelBodyType BodyType { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public BodyEntry( Body body, ModelBodyType bodyType, string name )
|
||||
{
|
||||
m_Body = body;
|
||||
m_BodyType = bodyType;
|
||||
m_Name = name;
|
||||
Body = body;
|
||||
BodyType = bodyType;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
BodyEntry e = (BodyEntry)obj;
|
||||
|
||||
return (m_Body == e.m_Body && m_BodyType == e.m_BodyType && m_Name == e.m_Name);
|
||||
return (Body == e.Body && BodyType == e.BodyType && Name == e.Name);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return m_Body.BodyID ^ (int)m_BodyType ^ m_Name.GetHashCode();
|
||||
return Body.BodyID ^ (int)BodyType ^ Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -269,63 +269,59 @@ namespace Server.Commands
|
|||
|
||||
public class CategoryTypeEntry
|
||||
{
|
||||
private Type m_Type;
|
||||
private object m_Object;
|
||||
public Type Type { get; }
|
||||
|
||||
public Type Type => m_Type;
|
||||
public object Object => m_Object;
|
||||
public object Object { get; }
|
||||
|
||||
public CategoryTypeEntry( Type type )
|
||||
{
|
||||
m_Type = type;
|
||||
m_Object = Activator.CreateInstance( type );
|
||||
Type = type;
|
||||
Object = Activator.CreateInstance( type );
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryEntry
|
||||
{
|
||||
private string m_Title;
|
||||
private Type[] m_Matches;
|
||||
private CategoryEntry[] m_SubCategories;
|
||||
private CategoryEntry m_Parent;
|
||||
private ArrayList m_Matched;
|
||||
public string Title { get; }
|
||||
|
||||
public string Title => m_Title;
|
||||
public Type[] Matches => m_Matches;
|
||||
public CategoryEntry Parent => m_Parent;
|
||||
public CategoryEntry[] SubCategories => m_SubCategories;
|
||||
public ArrayList Matched => m_Matched;
|
||||
public Type[] Matches { get; }
|
||||
|
||||
public CategoryEntry Parent { get; }
|
||||
|
||||
public CategoryEntry[] SubCategories { get; }
|
||||
|
||||
public ArrayList Matched { get; }
|
||||
|
||||
public CategoryEntry()
|
||||
{
|
||||
m_Title = "(empty)";
|
||||
m_Matches = new Type[0];
|
||||
m_SubCategories = new CategoryEntry[0];
|
||||
m_Matched = new ArrayList();
|
||||
Title = "(empty)";
|
||||
Matches = new Type[0];
|
||||
SubCategories = new CategoryEntry[0];
|
||||
Matched = new ArrayList();
|
||||
}
|
||||
|
||||
public CategoryEntry( CategoryEntry parent, string title, CategoryEntry[] subCats )
|
||||
{
|
||||
m_Parent = parent;
|
||||
m_Title = title;
|
||||
m_SubCategories = subCats;
|
||||
m_Matches = new Type[0];
|
||||
m_Matched = new ArrayList();
|
||||
Parent = parent;
|
||||
Title = title;
|
||||
SubCategories = subCats;
|
||||
Matches = new Type[0];
|
||||
Matched = new ArrayList();
|
||||
}
|
||||
|
||||
public bool IsMatch( Type type )
|
||||
{
|
||||
bool isMatch = false;
|
||||
|
||||
for ( int i = 0; !isMatch && i < m_Matches.Length; ++i )
|
||||
isMatch = ( type == m_Matches[i] || type.IsSubclassOf( m_Matches[i] ) );
|
||||
for ( int i = 0; !isMatch && i < Matches.Length; ++i )
|
||||
isMatch = ( type == Matches[i] || type.IsSubclassOf( Matches[i] ) );
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
public CategoryEntry( CategoryEntry parent, CategoryLine[] lines, ref int index )
|
||||
{
|
||||
m_Parent = parent;
|
||||
Parent = parent;
|
||||
|
||||
string text = lines[index].Text;
|
||||
|
||||
|
|
@ -334,7 +330,7 @@ namespace Server.Commands
|
|||
if ( start < 0 )
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
m_Title = text.Substring( 0, start ).Trim();
|
||||
Title = text.Substring( 0, start ).Trim();
|
||||
|
||||
int end = text.IndexOf( ')', ++start );
|
||||
|
||||
|
|
@ -356,7 +352,7 @@ namespace Server.Commands
|
|||
list.Add( type );
|
||||
}
|
||||
|
||||
m_Matches = (Type[])list.ToArray( typeof( Type ) );
|
||||
Matches = (Type[])list.ToArray( typeof( Type ) );
|
||||
list.Clear();
|
||||
|
||||
int ourIndentation = lines[index].Indentation;
|
||||
|
|
@ -366,20 +362,18 @@ namespace Server.Commands
|
|||
while ( index < lines.Length && lines[index].Indentation > ourIndentation )
|
||||
list.Add( new CategoryEntry( this, lines, ref index ) );
|
||||
|
||||
m_SubCategories = (CategoryEntry[])list.ToArray( typeof( CategoryEntry ) );
|
||||
SubCategories = (CategoryEntry[])list.ToArray( typeof( CategoryEntry ) );
|
||||
list.Clear();
|
||||
|
||||
m_Matched = list;
|
||||
Matched = list;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryLine
|
||||
{
|
||||
private int m_Indentation;
|
||||
private string m_Text;
|
||||
public int Indentation { get; }
|
||||
|
||||
public int Indentation => m_Indentation;
|
||||
public string Text => m_Text;
|
||||
public string Text { get; }
|
||||
|
||||
public CategoryLine( string input )
|
||||
{
|
||||
|
|
@ -394,8 +388,8 @@ namespace Server.Commands
|
|||
if ( index >= input.Length )
|
||||
throw new FormatException($"Input string not correctly formatted ('{input}')");
|
||||
|
||||
m_Indentation = index;
|
||||
m_Text = input.Substring( index );
|
||||
Indentation = index;
|
||||
Text = input.Substring( index );
|
||||
}
|
||||
|
||||
public static CategoryLine[] Load( string path )
|
||||
|
|
|
|||
|
|
@ -14,55 +14,19 @@ namespace Server.Commands.Generic
|
|||
|
||||
public abstract class BaseCommand
|
||||
{
|
||||
private string[] m_Commands;
|
||||
private AccessLevel m_AccessLevel;
|
||||
private CommandSupport m_Implementors;
|
||||
private ObjectTypes m_ObjectTypes;
|
||||
private bool m_ListOptimized;
|
||||
private string m_Usage;
|
||||
private string m_Description;
|
||||
public bool ListOptimized { get; set; }
|
||||
|
||||
public bool ListOptimized
|
||||
{
|
||||
get => m_ListOptimized;
|
||||
set => m_ListOptimized = value;
|
||||
}
|
||||
public string[] Commands { get; set; }
|
||||
|
||||
public string[] Commands
|
||||
{
|
||||
get => m_Commands;
|
||||
set => m_Commands = value;
|
||||
}
|
||||
public string Usage { get; set; }
|
||||
|
||||
public string Usage
|
||||
{
|
||||
get => m_Usage;
|
||||
set => m_Usage = value;
|
||||
}
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => m_Description;
|
||||
set => m_Description = value;
|
||||
}
|
||||
public AccessLevel AccessLevel { get; set; }
|
||||
|
||||
public AccessLevel AccessLevel
|
||||
{
|
||||
get => m_AccessLevel;
|
||||
set => m_AccessLevel = value;
|
||||
}
|
||||
public ObjectTypes ObjectTypes { get; set; }
|
||||
|
||||
public ObjectTypes ObjectTypes
|
||||
{
|
||||
get => m_ObjectTypes;
|
||||
set => m_ObjectTypes = value;
|
||||
}
|
||||
|
||||
public CommandSupport Supports
|
||||
{
|
||||
get => m_Implementors;
|
||||
set => m_Implementors = value;
|
||||
}
|
||||
public CommandSupport Supports { get; set; }
|
||||
|
||||
public BaseCommand()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,13 +58,11 @@ namespace Server.Commands.Generic
|
|||
Register( new TraceLockdownCommand() );
|
||||
}
|
||||
|
||||
private static List<BaseCommand> m_AllCommands = new List<BaseCommand>();
|
||||
|
||||
public static List<BaseCommand> AllCommands => m_AllCommands;
|
||||
public static List<BaseCommand> AllCommands { get; } = new List<BaseCommand>();
|
||||
|
||||
public static void Register( BaseCommand command )
|
||||
{
|
||||
m_AllCommands.Add( command );
|
||||
AllCommands.Add( command );
|
||||
|
||||
List<BaseCommandImplementor> impls = BaseCommandImplementor.Implementors;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,40 +8,31 @@ namespace Server.Commands.Generic
|
|||
|
||||
public sealed class ExtensionInfo
|
||||
{
|
||||
private static Dictionary<string, ExtensionInfo> m_Table = new Dictionary<string, ExtensionInfo>( StringComparer.InvariantCultureIgnoreCase );
|
||||
|
||||
public static Dictionary<string, ExtensionInfo> Table => m_Table;
|
||||
public static Dictionary<string, ExtensionInfo> Table { get; } = new Dictionary<string, ExtensionInfo>( StringComparer.InvariantCultureIgnoreCase );
|
||||
|
||||
public static void Register( ExtensionInfo ext )
|
||||
{
|
||||
m_Table[ext.m_Name] = ext;
|
||||
Table[ext.Name] = ext;
|
||||
}
|
||||
|
||||
private int m_Order;
|
||||
public int Order { get; }
|
||||
|
||||
private string m_Name;
|
||||
private int m_Size;
|
||||
public string Name { get; }
|
||||
|
||||
private ExtensionConstructor m_Constructor;
|
||||
public int Size { get; }
|
||||
|
||||
public int Order => m_Order;
|
||||
public bool IsFixedSize => ( Size >= 0 );
|
||||
|
||||
public string Name => m_Name;
|
||||
|
||||
public int Size => m_Size;
|
||||
|
||||
public bool IsFixedSize => ( m_Size >= 0 );
|
||||
|
||||
public ExtensionConstructor Constructor => m_Constructor;
|
||||
public ExtensionConstructor Constructor { get; }
|
||||
|
||||
public ExtensionInfo( int order, string name, int size, ExtensionConstructor constructor )
|
||||
{
|
||||
m_Name = name;
|
||||
m_Size = size;
|
||||
Name = name;
|
||||
Size = size;
|
||||
|
||||
m_Order = order;
|
||||
Order = order;
|
||||
|
||||
m_Constructor = constructor;
|
||||
Constructor = constructor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,52 +40,48 @@ namespace Server.Commands.Generic
|
|||
|
||||
public sealed class PropertyValue
|
||||
{
|
||||
private Type m_Type;
|
||||
private object m_Value;
|
||||
private FieldInfo m_Field;
|
||||
public Type Type { get; }
|
||||
|
||||
public Type Type => m_Type;
|
||||
public object Value { get; private set; }
|
||||
|
||||
public object Value => m_Value;
|
||||
public FieldInfo Field { get; private set; }
|
||||
|
||||
public FieldInfo Field => m_Field;
|
||||
|
||||
public bool HasField => ( m_Field != null );
|
||||
public bool HasField => ( Field != null );
|
||||
|
||||
public PropertyValue( Type type, object value )
|
||||
{
|
||||
m_Type = type;
|
||||
m_Value = value;
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public void Load( MethodEmitter method )
|
||||
{
|
||||
if ( m_Field != null )
|
||||
if ( Field != null )
|
||||
{
|
||||
method.LoadArgument( 0 );
|
||||
method.LoadField( m_Field );
|
||||
method.LoadField( Field );
|
||||
}
|
||||
else if ( m_Value == null )
|
||||
else if ( Value == null )
|
||||
{
|
||||
method.LoadNull( m_Type );
|
||||
method.LoadNull( Type );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( m_Value is int i )
|
||||
if ( Value is int i )
|
||||
method.Load( i );
|
||||
else if ( m_Value is long l )
|
||||
else if ( Value is long l )
|
||||
method.Load( l );
|
||||
else if ( m_Value is float f )
|
||||
else if ( Value is float f )
|
||||
method.Load( f );
|
||||
else if ( m_Value is double d )
|
||||
else if ( Value is double d )
|
||||
method.Load( d );
|
||||
else if ( m_Value is char c )
|
||||
else if ( Value is char c )
|
||||
method.Load( c );
|
||||
else if ( m_Value is bool b )
|
||||
else if ( Value is bool b )
|
||||
method.Load( b );
|
||||
else if ( m_Value is string s )
|
||||
else if ( Value is string s )
|
||||
method.Load( s );
|
||||
else if ( m_Value is Enum e )
|
||||
else if ( Value is Enum e )
|
||||
method.Load( e );
|
||||
else
|
||||
throw new InvalidOperationException( "Unrecognized comparison value." );
|
||||
|
|
@ -94,29 +90,29 @@ namespace Server.Commands.Generic
|
|||
|
||||
public void Acquire( TypeBuilder typeBuilder, ILGenerator il, string fieldName )
|
||||
{
|
||||
if ( m_Value is string toParse )
|
||||
if ( Value is string toParse )
|
||||
{
|
||||
if ( !m_Type.IsValueType && toParse == "null" )
|
||||
if ( !Type.IsValueType && toParse == "null" )
|
||||
{
|
||||
m_Value = null;
|
||||
Value = null;
|
||||
}
|
||||
else if ( m_Type == typeof( string ) )
|
||||
else if ( Type == typeof( string ) )
|
||||
{
|
||||
if ( toParse == @"@""null""" )
|
||||
toParse = "null";
|
||||
|
||||
m_Value = toParse;
|
||||
Value = toParse;
|
||||
}
|
||||
else if ( m_Type.IsEnum )
|
||||
else if ( Type.IsEnum )
|
||||
{
|
||||
m_Value = Enum.Parse( m_Type, toParse, true );
|
||||
Value = Enum.Parse( Type, toParse, true );
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo parseMethod = null;
|
||||
object[] parseArgs = null;
|
||||
|
||||
MethodInfo parseNumber = m_Type.GetMethod(
|
||||
MethodInfo parseNumber = Type.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
|
|
@ -139,7 +135,7 @@ namespace Server.Commands.Generic
|
|||
}
|
||||
else
|
||||
{
|
||||
MethodInfo parseGeneral = m_Type.GetMethod(
|
||||
MethodInfo parseGeneral = Type.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
|
|
@ -153,13 +149,13 @@ namespace Server.Commands.Generic
|
|||
|
||||
if ( parseMethod != null )
|
||||
{
|
||||
m_Value = parseMethod.Invoke( null, parseArgs );
|
||||
Value = parseMethod.Invoke( null, parseArgs );
|
||||
|
||||
if ( !m_Type.IsPrimitive )
|
||||
if ( !Type.IsPrimitive )
|
||||
{
|
||||
m_Field = typeBuilder.DefineField(
|
||||
Field = typeBuilder.DefineField(
|
||||
fieldName,
|
||||
m_Type,
|
||||
Type,
|
||||
FieldAttributes.Private | FieldAttributes.InitOnly
|
||||
);
|
||||
|
||||
|
|
@ -171,13 +167,13 @@ namespace Server.Commands.Generic
|
|||
il.Emit( OpCodes.Ldc_I4, (int) parseArgs[1] );
|
||||
|
||||
il.Emit( OpCodes.Call, parseMethod );
|
||||
il.Emit( OpCodes.Stfld, m_Field );
|
||||
il.Emit( OpCodes.Stfld, Field );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to convert string \"{m_Value}\" into type '{m_Type}'."
|
||||
$"Unable to convert string \"{Value}\" into type '{Type}'."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,9 @@ namespace Server.Commands.Generic
|
|||
{
|
||||
public sealed class OrderInfo
|
||||
{
|
||||
private Property m_Property;
|
||||
private int m_Order;
|
||||
|
||||
public Property Property
|
||||
{
|
||||
get => m_Property;
|
||||
set => m_Property = value;
|
||||
}
|
||||
public Property Property { get; set; }
|
||||
|
||||
public bool IsAscending
|
||||
{
|
||||
|
|
@ -42,7 +37,7 @@ namespace Server.Commands.Generic
|
|||
|
||||
public OrderInfo( Property property, bool isAscending )
|
||||
{
|
||||
m_Property = property;
|
||||
Property = property;
|
||||
|
||||
IsAscending = isAscending;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ namespace Server.Commands.Generic
|
|||
|
||||
public override ExtensionInfo Info => ExtInfo;
|
||||
|
||||
private int m_Limit;
|
||||
|
||||
public int Limit => m_Limit;
|
||||
public int Limit { get; private set; }
|
||||
|
||||
public LimitExtension()
|
||||
{
|
||||
|
|
@ -24,16 +22,16 @@ namespace Server.Commands.Generic
|
|||
|
||||
public override void Parse( Mobile from, string[] arguments, int offset, int size )
|
||||
{
|
||||
m_Limit = Utility.ToInt32( arguments[offset] );
|
||||
Limit = Utility.ToInt32( arguments[offset] );
|
||||
|
||||
if ( m_Limit < 0 )
|
||||
if ( Limit < 0 )
|
||||
throw new Exception( "Limit cannot be less than zero." );
|
||||
}
|
||||
|
||||
public override void Filter( ArrayList list )
|
||||
{
|
||||
if ( list.Count > m_Limit )
|
||||
list.RemoveRange( m_Limit, list.Count - m_Limit );
|
||||
if ( list.Count > Limit )
|
||||
list.RemoveRange( Limit, list.Count - Limit );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ namespace Server.Commands.Generic
|
|||
|
||||
public override ExtensionInfo Info => ExtInfo;
|
||||
|
||||
private ObjectConditional m_Conditional;
|
||||
|
||||
public ObjectConditional Conditional => m_Conditional;
|
||||
public ObjectConditional Conditional { get; private set; }
|
||||
|
||||
public WhereExtension()
|
||||
{
|
||||
|
|
@ -26,7 +24,7 @@ namespace Server.Commands.Generic
|
|||
if ( baseType == null )
|
||||
throw new InvalidOperationException( "Insanity." );
|
||||
|
||||
m_Conditional.Compile( ref assembly );
|
||||
Conditional.Compile( ref assembly );
|
||||
}
|
||||
|
||||
public override void Parse( Mobile from, string[] arguments, int offset, int size )
|
||||
|
|
@ -34,12 +32,12 @@ namespace Server.Commands.Generic
|
|||
if ( size < 1 )
|
||||
throw new Exception( "Invalid condition syntax." );
|
||||
|
||||
m_Conditional = ObjectConditional.ParseDirect( from, arguments, offset, size );
|
||||
Conditional = ObjectConditional.ParseDirect( from, arguments, offset, size );
|
||||
}
|
||||
|
||||
public override bool IsValid( object obj )
|
||||
{
|
||||
return m_Conditional.CheckCondition( obj );
|
||||
return Conditional.CheckCondition( obj );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ namespace Server.Commands.Generic
|
|||
{
|
||||
public class AreaCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
private static AreaCommandImplementor m_Instance;
|
||||
|
||||
public static AreaCommandImplementor Instance => m_Instance;
|
||||
public static AreaCommandImplementor Instance { get; private set; }
|
||||
|
||||
public AreaCommandImplementor()
|
||||
{
|
||||
|
|
@ -18,7 +16,7 @@ namespace Server.Commands.Generic
|
|||
Usage = "Area <command> [condition]";
|
||||
Description = "Invokes the command on all appropriate objects in a targeted area. Optional condition arguments can further restrict the set of objects.";
|
||||
|
||||
m_Instance = this;
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public override void Process( Mobile from, BaseCommand command, string[] args )
|
||||
|
|
|
|||
|
|
@ -47,55 +47,23 @@ namespace Server.Commands.Generic
|
|||
Register( new FacetCommandImplementor() );
|
||||
}
|
||||
|
||||
private string[] m_Accessors;
|
||||
private AccessLevel m_AccessLevel;
|
||||
private CommandSupport m_SupportRequirement;
|
||||
private Dictionary<string, BaseCommand> m_Commands;
|
||||
private string m_Usage;
|
||||
private string m_Description;
|
||||
private bool m_SupportsConditionals;
|
||||
public bool SupportsConditionals { get; set; }
|
||||
|
||||
public bool SupportsConditionals
|
||||
{
|
||||
get => m_SupportsConditionals;
|
||||
set => m_SupportsConditionals = value;
|
||||
}
|
||||
public string[] Accessors { get; set; }
|
||||
|
||||
public string[] Accessors
|
||||
{
|
||||
get => m_Accessors;
|
||||
set => m_Accessors = value;
|
||||
}
|
||||
public string Usage { get; set; }
|
||||
|
||||
public string Usage
|
||||
{
|
||||
get => m_Usage;
|
||||
set => m_Usage = value;
|
||||
}
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => m_Description;
|
||||
set => m_Description = value;
|
||||
}
|
||||
public AccessLevel AccessLevel { get; set; }
|
||||
|
||||
public AccessLevel AccessLevel
|
||||
{
|
||||
get => m_AccessLevel;
|
||||
set => m_AccessLevel = value;
|
||||
}
|
||||
public CommandSupport SupportRequirement { get; set; }
|
||||
|
||||
public CommandSupport SupportRequirement
|
||||
{
|
||||
get => m_SupportRequirement;
|
||||
set => m_SupportRequirement = value;
|
||||
}
|
||||
|
||||
public Dictionary<string, BaseCommand> Commands => m_Commands;
|
||||
public Dictionary<string, BaseCommand> Commands { get; }
|
||||
|
||||
public BaseCommandImplementor()
|
||||
{
|
||||
m_Commands = new Dictionary<string, BaseCommand>( StringComparer.OrdinalIgnoreCase );
|
||||
Commands = new Dictionary<string, BaseCommand>( StringComparer.OrdinalIgnoreCase );
|
||||
}
|
||||
|
||||
public virtual void Compile( Mobile from, BaseCommand command, ref string[] args, ref object obj )
|
||||
|
|
@ -106,7 +74,7 @@ namespace Server.Commands.Generic
|
|||
public virtual void Register( BaseCommand command )
|
||||
{
|
||||
for ( int i = 0; i < command.Commands.Length; ++i )
|
||||
m_Commands[command.Commands[i]] = command;
|
||||
Commands[command.Commands[i]] = command;
|
||||
}
|
||||
|
||||
public bool CheckObjectTypes( Mobile from, BaseCommand command, Extensions ext, out bool items, out bool mobiles )
|
||||
|
|
@ -274,7 +242,7 @@ namespace Server.Commands.Generic
|
|||
{
|
||||
if ( e.Length >= 1 )
|
||||
{
|
||||
m_Commands.TryGetValue( e.GetString( 0 ), out BaseCommand command );
|
||||
Commands.TryGetValue( e.GetString( 0 ), out BaseCommand command );
|
||||
|
||||
if ( command == null )
|
||||
{
|
||||
|
|
@ -303,11 +271,11 @@ namespace Server.Commands.Generic
|
|||
|
||||
public void Register()
|
||||
{
|
||||
if ( m_Accessors == null )
|
||||
if ( Accessors == null )
|
||||
return;
|
||||
|
||||
for ( int i = 0; i < m_Accessors.Length; ++i )
|
||||
CommandSystem.Register( m_Accessors[i], m_AccessLevel, Execute );
|
||||
for ( int i = 0; i < Accessors.Length; ++i )
|
||||
CommandSystem.Register( Accessors[i], AccessLevel, Execute );
|
||||
}
|
||||
|
||||
public static void Register( BaseCommandImplementor impl )
|
||||
|
|
|
|||
|
|
@ -9,17 +9,15 @@ namespace Server.Commands.Generic
|
|||
private static readonly Type typeofItem = typeof( Item );
|
||||
private static readonly Type typeofMobile = typeof( Mobile );
|
||||
|
||||
private Type m_ObjectType;
|
||||
|
||||
private ICondition[][] m_Conditions;
|
||||
|
||||
private IConditional[] m_Conditionals;
|
||||
|
||||
public Type Type => m_ObjectType;
|
||||
public Type Type { get; }
|
||||
|
||||
public bool IsItem => ( m_ObjectType == null || m_ObjectType == typeofItem || m_ObjectType.IsSubclassOf( typeofItem ) );
|
||||
public bool IsItem => ( Type == null || Type == typeofItem || Type.IsSubclassOf( typeofItem ) );
|
||||
|
||||
public bool IsMobile => ( m_ObjectType == null || m_ObjectType == typeofMobile || m_ObjectType.IsSubclassOf( typeofMobile ) );
|
||||
public bool IsMobile => ( Type == null || Type == typeofMobile || Type.IsSubclassOf( typeofMobile ) );
|
||||
|
||||
public static readonly ObjectConditional Empty = new ObjectConditional( null, null );
|
||||
|
||||
|
|
@ -33,12 +31,12 @@ namespace Server.Commands.Generic
|
|||
m_Conditionals = new IConditional[m_Conditions.Length];
|
||||
|
||||
for ( int i = 0; i < m_Conditionals.Length; ++i )
|
||||
m_Conditionals[i] = ConditionalCompiler.Compile( emitter, m_ObjectType, m_Conditions[i], i );
|
||||
m_Conditionals[i] = ConditionalCompiler.Compile( emitter, Type, m_Conditions[i], i );
|
||||
}
|
||||
|
||||
public bool CheckCondition( object obj )
|
||||
{
|
||||
if ( m_ObjectType == null )
|
||||
if ( Type == null )
|
||||
return true; // null type means no condition
|
||||
|
||||
if ( !HasCompiled )
|
||||
|
|
@ -243,7 +241,7 @@ namespace Server.Commands.Generic
|
|||
|
||||
public ObjectConditional( Type objectType, ICondition[][] conditions )
|
||||
{
|
||||
m_ObjectType = objectType;
|
||||
Type = objectType;
|
||||
m_Conditions = conditions;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ namespace Server.Commands.Generic
|
|||
{
|
||||
public class RangeCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
private static RangeCommandImplementor m_Instance;
|
||||
|
||||
public static RangeCommandImplementor Instance => m_Instance;
|
||||
public static RangeCommandImplementor Instance { get; private set; }
|
||||
|
||||
public RangeCommandImplementor()
|
||||
{
|
||||
|
|
@ -15,7 +13,7 @@ namespace Server.Commands.Generic
|
|||
Usage = "Range <range> <command> [condition]";
|
||||
Description = "Invokes the command on all appropriate objects within a specified range of you. Optional condition arguments can further restrict the set of objects.";
|
||||
|
||||
m_Instance = this;
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public override void Execute( CommandEventArgs e )
|
||||
|
|
|
|||
|
|
@ -12,12 +12,9 @@ namespace Server.Commands
|
|||
{
|
||||
public class HelpInfo
|
||||
{
|
||||
public static Dictionary<string, CommandInfo> HelpInfos { get; } = new Dictionary<string, CommandInfo>();
|
||||
|
||||
private static Dictionary<string, CommandInfo> m_HelpInfos = new Dictionary<string, CommandInfo>();
|
||||
private static List<CommandInfo> m_SortedHelpInfo = new List<CommandInfo>(); //No need for SortedList cause it's only sorted once at creation...
|
||||
|
||||
public static Dictionary<string, CommandInfo> HelpInfos => m_HelpInfos;
|
||||
public static List<CommandInfo> SortedHelpInfo => m_SortedHelpInfo;
|
||||
public static List<CommandInfo> SortedHelpInfo { get; private set; } = new List<CommandInfo>();
|
||||
|
||||
[CallPriority( 100 )]
|
||||
public static void Initialize()
|
||||
|
|
@ -34,7 +31,7 @@ namespace Server.Commands
|
|||
if ( e.Length > 0 )
|
||||
{
|
||||
string arg = e.GetString( 0 ).ToLower();
|
||||
if (m_HelpInfos.TryGetValue( arg, out CommandInfo c ))
|
||||
if (HelpInfos.TryGetValue( arg, out CommandInfo c ))
|
||||
{
|
||||
Mobile m = e.Mobile;
|
||||
|
||||
|
|
@ -213,12 +210,12 @@ namespace Server.Commands
|
|||
|
||||
list.Sort( new CommandInfoSorter() );
|
||||
|
||||
m_SortedHelpInfo = list;
|
||||
SortedHelpInfo = list;
|
||||
|
||||
foreach( CommandInfo c in m_SortedHelpInfo )
|
||||
foreach( CommandInfo c in SortedHelpInfo )
|
||||
{
|
||||
if ( !m_HelpInfos.ContainsKey( c.Name.ToLower() ) )
|
||||
m_HelpInfos.Add( c.Name.ToLower(), c );
|
||||
if ( !HelpInfos.ContainsKey( c.Name.ToLower() ) )
|
||||
HelpInfos.Add( c.Name.ToLower(), c );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +235,7 @@ namespace Server.Commands
|
|||
{
|
||||
m_List = new List<CommandInfo>();
|
||||
|
||||
foreach( CommandInfo c in m_SortedHelpInfo )
|
||||
foreach( CommandInfo c in SortedHelpInfo )
|
||||
{
|
||||
if ( from.AccessLevel >= c.AccessLevel )
|
||||
m_List.Add( c );
|
||||
|
|
@ -311,7 +308,7 @@ namespace Server.Commands
|
|||
}
|
||||
case 2:
|
||||
{
|
||||
if ( (m_Page + 1) * EntriesPerPage < m_SortedHelpInfo.Count )
|
||||
if ( (m_Page + 1) * EntriesPerPage < SortedHelpInfo.Count )
|
||||
m.SendGump( new CommandListGump( m_Page + 1, m, m_List ) );
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -6,14 +6,9 @@ namespace Server.Commands
|
|||
{
|
||||
public class CommandLogging
|
||||
{
|
||||
private static StreamWriter m_Output;
|
||||
private static bool m_Enabled = true;
|
||||
public static bool Enabled { get; set; } = true;
|
||||
|
||||
public static bool Enabled{ get => m_Enabled;
|
||||
set => m_Enabled = value;
|
||||
}
|
||||
|
||||
public static StreamWriter Output => m_Output;
|
||||
public static StreamWriter Output { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
|
|
@ -29,13 +24,13 @@ namespace Server.Commands
|
|||
|
||||
try
|
||||
{
|
||||
m_Output = new StreamWriter( Path.Combine( directory, $"{DateTime.UtcNow.ToLongDateString()}.log"), true );
|
||||
Output = new StreamWriter( Path.Combine( directory, $"{DateTime.UtcNow.ToLongDateString()}.log"), true );
|
||||
|
||||
m_Output.AutoFlush = true;
|
||||
Output.AutoFlush = true;
|
||||
|
||||
m_Output.WriteLine( "##############################" );
|
||||
m_Output.WriteLine( "Log started on {0}", DateTime.UtcNow );
|
||||
m_Output.WriteLine();
|
||||
Output.WriteLine( "##############################" );
|
||||
Output.WriteLine( "Log started on {0}", DateTime.UtcNow );
|
||||
Output.WriteLine();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -61,7 +56,7 @@ namespace Server.Commands
|
|||
|
||||
public static void WriteLine( Mobile from, string format, params object[] args )
|
||||
{
|
||||
if ( !m_Enabled )
|
||||
if ( !Enabled )
|
||||
return;
|
||||
|
||||
WriteLine( from, string.Format( format, args ) );
|
||||
|
|
@ -69,12 +64,12 @@ namespace Server.Commands
|
|||
|
||||
public static void WriteLine( Mobile from, string text )
|
||||
{
|
||||
if ( !m_Enabled )
|
||||
if ( !Enabled )
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
m_Output.WriteLine( "{0}: {1}: {2}", DateTime.UtcNow, from.NetState, text );
|
||||
Output.WriteLine( "{0}: {1}: {2}", DateTime.UtcNow, from.NetState, text );
|
||||
|
||||
string path = Core.BaseDirectory;
|
||||
|
||||
|
|
|
|||
|
|
@ -686,16 +686,13 @@ namespace Server
|
|||
|
||||
public sealed class Property
|
||||
{
|
||||
private string m_Binding;
|
||||
|
||||
private PropertyInfo[] m_Chain;
|
||||
private PropertyAccess m_Access;
|
||||
|
||||
public string Binding => m_Binding;
|
||||
public string Binding { get; }
|
||||
|
||||
public bool IsBound => ( m_Chain != null );
|
||||
|
||||
public PropertyAccess Access => m_Access;
|
||||
public PropertyAccess Access { get; private set; }
|
||||
|
||||
public PropertyInfo[] Chain
|
||||
{
|
||||
|
|
@ -730,7 +727,7 @@ namespace Server
|
|||
|
||||
bool isFinal = ( i == ( m_Chain.Length - 1 ) );
|
||||
|
||||
PropertyAccess access = m_Access;
|
||||
PropertyAccess access = Access;
|
||||
|
||||
if ( !isFinal )
|
||||
access |= PropertyAccess.Read;
|
||||
|
|
@ -755,7 +752,7 @@ namespace Server
|
|||
if ( IsBound )
|
||||
throw new AlreadyBoundException( this );
|
||||
|
||||
string[] split = m_Binding.Split( '.' );
|
||||
string[] split = Binding.Split( '.' );
|
||||
|
||||
PropertyInfo[] chain = new PropertyInfo[split.Length];
|
||||
|
||||
|
|
@ -782,13 +779,13 @@ namespace Server
|
|||
throw new ReadOnlyException( this );
|
||||
}
|
||||
|
||||
m_Access = desiredAccess;
|
||||
Access = desiredAccess;
|
||||
m_Chain = chain;
|
||||
}
|
||||
|
||||
public Property( string binding )
|
||||
{
|
||||
m_Binding = binding;
|
||||
Binding = binding;
|
||||
}
|
||||
|
||||
public Property( PropertyInfo[] chain )
|
||||
|
|
@ -799,7 +796,7 @@ namespace Server
|
|||
public override string ToString()
|
||||
{
|
||||
if ( !IsBound )
|
||||
return m_Binding;
|
||||
return Binding;
|
||||
|
||||
string[] toJoin = new string[m_Chain.Length];
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue