mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-15 01:39:39 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
192 lines
5.4 KiB
C#
192 lines
5.4 KiB
C#
using NativeEngine;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Sandbox
|
|
{
|
|
public partial class Model
|
|
{
|
|
Dictionary<string, object> DataCache = new();
|
|
|
|
readonly JsonSerializerOptions jsonOptions = new()
|
|
{
|
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
PropertyNameCaseInsensitive = true,
|
|
IncludeFields = true,
|
|
Converters =
|
|
{
|
|
new JsonStringEnumConverter()
|
|
}
|
|
};
|
|
|
|
internal string GetJson( string keyname )
|
|
{
|
|
if ( string.IsNullOrEmpty( Name ) ) Log.Warning( $"Warning: Trying to access Model Key Value '{keyname}' on an empty model!" );
|
|
|
|
var key = native.FindModelSubKey( keyname );
|
|
if ( key.IsNull ) return null;
|
|
|
|
return EngineGlue.KeyValues3ToJson( key );
|
|
}
|
|
|
|
internal string DeduceKeyName( Type type )
|
|
{
|
|
var outputType = type;
|
|
var dataType = type;
|
|
if ( dataType.IsArray )
|
|
{
|
|
dataType = dataType.GetElementType();
|
|
}
|
|
|
|
// Try to warn if the data types are mismatched
|
|
var gdAttr = dataType.GetCustomAttribute<ModelEditor.GameDataAttribute>();
|
|
if ( gdAttr == null ) throw new ArgumentException( $"{type.Name} does not have ModelDoc.GameDataAttribute" );
|
|
|
|
if ( !gdAttr.AllowMultiple && outputType.IsArray ) throw new ArgumentException( $"{type.Name} is not a list, but an array generic parameter was given" );
|
|
if ( gdAttr.AllowMultiple && !outputType.IsArray ) throw new ArgumentException( $"{type.Name} is a list, but non array generic parameter was given" );
|
|
|
|
var keyname = gdAttr.Name;
|
|
if ( gdAttr.AllowMultiple ) keyname = gdAttr.ListName ?? $"{keyname}_list";
|
|
|
|
return keyname;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to extract data from model based on the given type's <see cref="ModelEditor.GameDataAttribute">ModelDoc.GameDataAttribute</see>.
|
|
/// </summary>
|
|
/// <param name="data">The extracted data, or default on failure.</param>
|
|
/// <returns>true if data was extracted successfully, false otherwise.</returns>
|
|
public bool TryGetData<T>( out T data )
|
|
{
|
|
bool ret = TryGetData( typeof( T ), out object dat );
|
|
data = (T)dat;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to extract data from model based on the given type's <see cref="ModelEditor.GameDataAttribute">ModelDoc.GameDataAttribute</see>.
|
|
/// </summary>
|
|
/// <param name="data">The extracted data, or default on failure.</param>
|
|
/// <param name="t">The class with <see cref="ModelEditor.GameDataAttribute">ModelDoc.GameDataAttribute</see>.</param>
|
|
/// <returns>true if data was extracted successfully, false otherwise.</returns>
|
|
public bool TryGetData( Type t, out object data )
|
|
{
|
|
string keyname = DeduceKeyName( t );
|
|
|
|
string json;
|
|
|
|
// Try to get data from cache
|
|
var cacheKey = $"{keyname}~{t.Name}";
|
|
if ( DataCache != null && DataCache.TryGetValue( cacheKey, out object cacheValue ) )
|
|
{
|
|
json = (string)cacheValue;
|
|
}
|
|
else
|
|
{
|
|
json = GetJson( keyname );
|
|
if ( string.IsNullOrWhiteSpace( json ) )
|
|
{
|
|
data = default;
|
|
return false;
|
|
}
|
|
|
|
// Special case of nodes that have no properties
|
|
if ( json == "null" ) json = "{}";
|
|
|
|
// Add to cache
|
|
if ( DataCache == null ) DataCache = new Dictionary<string, object>();
|
|
DataCache.Add( cacheKey, json );
|
|
}
|
|
|
|
try
|
|
{
|
|
var obj = JsonSerializer.Deserialize( json, t, jsonOptions );
|
|
|
|
if ( obj == null )
|
|
{
|
|
data = default;
|
|
return false;
|
|
}
|
|
|
|
data = obj;
|
|
return true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Log.Warning( e, $"Failed to deserialize '{keyname}' to {t.Name}" );
|
|
}
|
|
|
|
data = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests if this model has generic data based on given type's <see cref="ModelEditor.GameDataAttribute">ModelDoc.GameDataAttribute</see>.
|
|
/// This will be faster than testing this via GetData<![CDATA[<>]]>()
|
|
/// </summary>
|
|
public bool HasData<T>()
|
|
{
|
|
var keyname = DeduceKeyName( typeof( T ) );
|
|
return native.FindModelSubKey( keyname ).IsValid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts data from model based on the given type's <see cref="ModelEditor.GameDataAttribute">ModelDoc.GameDataAttribute</see>.
|
|
/// </summary>
|
|
public T GetData<T>()
|
|
{
|
|
if ( TryGetData( out T data ) )
|
|
{
|
|
return data;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
#region BreakCommands
|
|
|
|
/// <summary>
|
|
/// Internal function used to get a list of break commands the model has.
|
|
/// </summary>
|
|
public Dictionary<string, string[]> GetBreakCommands()
|
|
{
|
|
var cacheKey = $"break_command_list~{Name}";
|
|
if ( DataCache != null && DataCache.TryGetValue( cacheKey, out object cacheValue ) )
|
|
{
|
|
return (Dictionary<string, string[]>)cacheValue;
|
|
}
|
|
|
|
Dictionary<string, string[]> output = new();
|
|
|
|
var jsonString = GetJson( "break_command_list" );
|
|
if ( jsonString == null ) return output;
|
|
|
|
using ( JsonDocument document = JsonDocument.Parse( jsonString ) )
|
|
{
|
|
JsonElement root = document.RootElement;
|
|
if ( root.ValueKind != JsonValueKind.Array ) return output;
|
|
|
|
foreach ( JsonElement commandData in root.EnumerateArray() )
|
|
{
|
|
if ( commandData.TryGetProperty( "break_command", out JsonElement command ) )
|
|
{
|
|
var cmd = command.GetString();
|
|
List<string> arr = output.ContainsKey( cmd ) ? output[cmd].Cast<string>().ToList() : new();
|
|
arr.Add( commandData.GetRawText() );
|
|
output[cmd] = arr.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( DataCache == null ) DataCache = new Dictionary<string, object>();
|
|
DataCache.Add( cacheKey, output );
|
|
|
|
return output;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|