mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-10 23:38:34 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
344 lines
8.3 KiB
C#
344 lines
8.3 KiB
C#
using Sandbox.Engine;
|
|
using Sandbox.Utility;
|
|
using System.Reflection;
|
|
|
|
namespace Sandbox;
|
|
|
|
|
|
partial class Command
|
|
{
|
|
internal static Connection Caller { get; set; }
|
|
|
|
public string Name { get; set; }
|
|
public string Help { get; set; }
|
|
|
|
public bool IsConCommand { get; set; }
|
|
public bool IsVariable => !IsConCommand;
|
|
|
|
/// <summary>
|
|
/// Saved into config file
|
|
/// </summary>
|
|
public bool IsSaved { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true then this cannot be interacted with via game code
|
|
/// </summary>
|
|
public bool IsProtected { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true then this command can only be run on the server
|
|
/// </summary>
|
|
public bool IsServer { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true then this command can only be run by a server administrator
|
|
/// </summary>
|
|
public bool IsAdmin { get; set; }
|
|
|
|
/// <summary>
|
|
/// Server value is replicated to clients
|
|
/// </summary>
|
|
public bool IsReplicated { get; set; }
|
|
|
|
/// <summary>
|
|
/// Not visible in auto complete and find
|
|
/// </summary>
|
|
public bool IsHidden { get; set; }
|
|
|
|
/// <summary>
|
|
/// Client values are sent to the server, accessible via Connection userinfo
|
|
/// </summary>
|
|
public bool IsUserInfo { get; set; }
|
|
|
|
/// <summary>
|
|
/// Can't access unless sv_cheats is 1
|
|
/// </summary>
|
|
public bool IsCheat { get; set; }
|
|
|
|
public float? MinValue { get; set; }
|
|
public float? MaxValue { get; set; }
|
|
|
|
public virtual string Value { get; set; }
|
|
public virtual string DefaultValue => default;
|
|
|
|
public virtual bool IsFromAssembly( Assembly assembly ) => false;
|
|
|
|
public virtual void Run( string args ) { }
|
|
|
|
public virtual void Save() { }
|
|
|
|
public virtual bool TryLoad( out string loaded )
|
|
{
|
|
loaded = null;
|
|
return false;
|
|
}
|
|
|
|
public virtual string BuildDescription()
|
|
{
|
|
if ( IsConCommand )
|
|
{
|
|
return Help; // todo - command line arguments auto
|
|
}
|
|
|
|
return $"\"{Value}\" - Default: \"{DefaultValue}\" - {Help}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// If we have a command line version of the command then set it and return true
|
|
/// </summary>
|
|
public bool SetVariableFromCommandLine()
|
|
{
|
|
if ( !IsVariable ) return false;
|
|
|
|
var value = CommandLine.GetSwitch( "+" + Name, null );
|
|
if ( value is null ) return false;
|
|
|
|
Value = value.TrimQuoted();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
partial class ManagedCommand : Command
|
|
{
|
|
internal ConVarAttribute attribute;
|
|
internal MemberInfo member;
|
|
internal Assembly assembly;
|
|
CookieContainer cookies;
|
|
bool _isMenu;
|
|
|
|
internal ParameterInfo[] parameters;
|
|
|
|
private string _defaultValue;
|
|
public override string DefaultValue => _defaultValue;
|
|
|
|
public ManagedCommand( Assembly assembly, MemberInfo member, ConVarAttribute attribute, CookieContainer cookies = null )
|
|
{
|
|
this.attribute = attribute;
|
|
this.assembly = assembly;
|
|
this.member = member;
|
|
this.cookies = cookies;
|
|
|
|
if ( member is MethodInfo method )
|
|
{
|
|
parameters = method.GetParameters();
|
|
}
|
|
|
|
Name = attribute.Name;
|
|
Help = attribute.Help ?? "No description";
|
|
IsConCommand = attribute is ConCmdAttribute;
|
|
IsHidden = attribute.Flags.Contains( ConVarFlags.Hidden );
|
|
IsServer = attribute.Flags.Contains( ConVarFlags.Server );
|
|
IsAdmin = attribute.Flags.Contains( ConVarFlags.Admin );
|
|
IsProtected = attribute.Flags.Contains( ConVarFlags.Protected );
|
|
IsCheat = attribute.Flags.Contains( ConVarFlags.Cheat );
|
|
_isMenu = attribute.Context == "menu";
|
|
|
|
//
|
|
// Set up default value if we have one
|
|
//
|
|
var type = member is PropertyInfo pi ? pi.PropertyType : member.DeclaringType;
|
|
|
|
var defaultValue = member.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
|
_defaultValue = defaultValue?.ToString()
|
|
?? (type.IsValueType ? Activator.CreateInstance( type )?.ToString() : null);
|
|
|
|
if ( attribute is ConVarAttribute cv )
|
|
{
|
|
IsSaved = cv.Saved;
|
|
MinValue = cv.MinValue;
|
|
MaxValue = cv.MaxValue;
|
|
IsReplicated = cv.Flags.Contains( ConVarFlags.Replicated );
|
|
IsUserInfo = cv.Flags.Contains( ConVarFlags.UserInfo );
|
|
}
|
|
|
|
if ( string.IsNullOrWhiteSpace( Name ) )
|
|
{
|
|
Name = member.Name;
|
|
}
|
|
|
|
// Names should be ascii letter digits only, definitely no spaces, semicolons or quotes
|
|
if ( !Name.All( x => char.IsAsciiLetterOrDigit( x ) || x == '_' || x == '.' || x == '-' ) )
|
|
{
|
|
throw new ArgumentException( $"Console name \"{Name}\" is invalid, it should only contain ascii letters, digits or underscores." );
|
|
}
|
|
|
|
if ( string.IsNullOrWhiteSpace( Help ) )
|
|
{
|
|
var info = DisplayInfo.ForMember( member );
|
|
Help = info.Description;
|
|
}
|
|
}
|
|
|
|
public override void Run( string argstring )
|
|
{
|
|
using var contextLocal = _isMenu ? GlobalContext.MenuScope() : GlobalContext.GameScope();
|
|
using var scope = _isMenu ? IMenuDll.Current?.PushScope() : IGameInstanceDll.Current?.PushScope();
|
|
|
|
var caller = Caller ?? Connection.Local;
|
|
|
|
if ( IsAdmin && !caller.IsHost )
|
|
{
|
|
caller.SendLog( LogLevel.Warn, "You are not allowed to run this command." );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Console command
|
|
//
|
|
if ( member is MethodInfo method )
|
|
{
|
|
if ( method == null )
|
|
throw new Exception( "ConsoleCommand is not a Method" );
|
|
|
|
var callargs = new object[parameters.Length];
|
|
var args = argstring.SplitQuotesStrings();
|
|
var argsCount = args?.Length ?? 0;
|
|
var parameterStartIndex = 0;
|
|
var paramCount = parameters.Length;
|
|
|
|
if ( paramCount > 0 && parameters[0].ParameterType == typeof( Connection ) )
|
|
{
|
|
parameterStartIndex = 1;
|
|
paramCount--;
|
|
callargs[0] = caller;
|
|
}
|
|
|
|
int argIndex = 0;
|
|
for ( int i = parameterStartIndex; i < parameters.Length; i++ )
|
|
{
|
|
var param = parameters[i];
|
|
|
|
if ( param.ParameterType.IsArray && param.GetCustomAttribute<ParamArrayAttribute>() != null )
|
|
{
|
|
var elemType = param.ParameterType.GetElementType();
|
|
var srcValues = (args?.Skip( argIndex ) ?? Enumerable.Empty<string>())
|
|
.Select( x => x.ToType( elemType ) )
|
|
.ToArray();
|
|
|
|
var paramsArray = Array.CreateInstance( elemType, srcValues.Length );
|
|
|
|
Array.Copy( srcValues, paramsArray, srcValues.Length );
|
|
|
|
callargs[i] = paramsArray;
|
|
break;
|
|
}
|
|
|
|
if ( argIndex < argsCount )
|
|
{
|
|
callargs[i] = args[argIndex].ToType( param.ParameterType );
|
|
argIndex++;
|
|
continue;
|
|
}
|
|
|
|
if ( parameters[i].HasDefaultValue )
|
|
{
|
|
callargs[i] = param.DefaultValue;
|
|
continue;
|
|
}
|
|
|
|
Log.Warning( $"Not enough arguments for command \"{Name}\"! Expected {paramCount}, got {argsCount}." );
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
method.Invoke( null, callargs );
|
|
return;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Log.Error( e.InnerException, $"Exception when calling command \"{Name}\"" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Console variable
|
|
//
|
|
if ( member is PropertyInfo propertyInfo )
|
|
{
|
|
if ( argstring == null )
|
|
return;
|
|
|
|
Value = argstring;
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
public override string Value
|
|
{
|
|
get
|
|
{
|
|
if ( member is PropertyInfo propertyInfo )
|
|
{
|
|
return propertyInfo.GetValue( null )?.ToString();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
if ( member is not PropertyInfo propertyInfo )
|
|
{
|
|
Log.Warning( $"Setting {Name} and it's not a variable" );
|
|
return;
|
|
}
|
|
|
|
var oldValue = propertyInfo.GetValue( null );
|
|
var newValue = value.ToType( propertyInfo.PropertyType );
|
|
|
|
if ( newValue is float f )
|
|
{
|
|
if ( MinValue.HasValue ) f = Math.Max( f, MinValue.Value );
|
|
if ( MaxValue.HasValue ) f = Math.Min( f, MaxValue.Value );
|
|
newValue = f;
|
|
}
|
|
else if ( newValue is int i )
|
|
{
|
|
if ( MinValue.HasValue ) i = Math.Max( i, (int)MinValue.Value );
|
|
if ( MaxValue.HasValue ) i = Math.Min( i, (int)MaxValue.Value );
|
|
newValue = i;
|
|
}
|
|
|
|
if ( object.Equals( newValue, oldValue ) )
|
|
return;
|
|
|
|
propertyInfo.SetValue( null, newValue );
|
|
|
|
Save();
|
|
}
|
|
}
|
|
|
|
public override bool IsFromAssembly( Assembly assembly ) => assembly == this.assembly;
|
|
|
|
/// <summary>
|
|
/// Todo: Add support for managed commands to return shit here
|
|
/// Todo: We could maybe do this in a cool way, using parameters?
|
|
/// So that for example, we could list players if it's a player etc
|
|
/// </summary>
|
|
public virtual string[] GetAutoComplete( string v )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public override bool TryLoad( out string loaded )
|
|
{
|
|
loaded = default;
|
|
if ( !IsSaved ) return false;
|
|
if ( cookies == null ) return false;
|
|
|
|
return cookies.TryGetString( $"convar.{Name}", out loaded );
|
|
}
|
|
|
|
public override void Save()
|
|
{
|
|
if ( !IsSaved ) return;
|
|
if ( cookies == null ) return;
|
|
cookies.SetString( $"convar.{Name}", Value );
|
|
}
|
|
}
|