using System.ComponentModel.DataAnnotations; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; namespace Sandbox.DataModel; /// /// Configuration of a . /// [Expose] public class ProjectConfig { /// /// The directory housing this addon (TODO) /// [JsonIgnore] [Hide] public DirectoryInfo Directory { get; set; } /// /// The directory housing this addon (TODO) /// [JsonIgnore] [Hide] public DirectoryInfo AssetsDirectory { get; set; } /// /// The human readable title, for example "Sandbox", "Counter-Strike" /// [Display( GroupName = "Setup", Order = -100, Name = "Title", Description = "The human readable title, for example \"Sandbox\", \"Counter - Strike\"" )] [MaxLength( 32 )] [MinLength( 2 )] public string Title { get; set; } /// /// The type of addon. Current valid values are "game" /// [Display( GroupName = "Setup", Order = -90, Name = "Addon Type", Description = "Don't change this for christ's sake" )] public string Type { get; set; } /// /// The ident of the org that owns this addon. For example "facepunch", "valve". /// [Display( GroupName = "Setup", Order = -100, Name = "Organization Ident", Description = "The ident of the org that owns this addon. Set to local if you don't have an org or are just testing." )] [Editor( "organization" )] public string Org { get; set; } /// /// The ident of this addon. For example "sandbox", "cs" or "dm98" /// [Display( GroupName = "Setup", Order = -100, Name = "Package Ident", Description = "The ident of this addon. A short name with no special characters." )] [MaxLength( 64 )] [MinLength( 2 )] [RegularExpression( @"^[a-z0-9_\-]+$", ErrorMessage = "Lower case letters and underscores, no spaces or other special characters" )] public string Ident { get; set; } /// /// Type of the package. /// [JsonIgnore] [Obsolete( "Compare string Type instead" )] public Package.Type PackageType => default; /// /// Returns a combination of Org and Ident - for example "facepunch.sandbox" or "valve.cs". /// [Hide] [JsonIgnore] public string FullIdent => $"{Org}.{Ident}"; /// /// The version of the addon file. Allows us to upgrade internally. /// [Hide] public int Schema { get; set; } /// /// If true then we'll include all the source files /// [Hide] public bool IncludeSourceFiles { get; set; } /// /// A list of paths in which to look for extra assets to upload with the addon. Note that compiled asset files are automatically included. /// public string Resources { get; set; } /// /// A list of packages that this package depends on. These should be installed alongside this package. /// public List PackageReferences { get; set; } /// /// A list of packages that this package uses but there is no need to install. For example, a map package might use /// a model package - but there is no need to download that model package because any usage will organically be included /// in the manifest. However, when loading this item in the editor, it'd make sense to install these 'cloud' packages. /// public List EditorReferences { get; set; } /// /// A list of mounts that are required /// public List Mounts { get; set; } /// /// Contains unique elements from , along with any implicit package references. /// An example implicit reference is the parent package of an addon. /// [JsonIgnore, Hide] internal IReadOnlySet DistinctPackageReferences { get { var set = new HashSet( (IList)PackageReferences ?? Array.Empty(), StringComparer.OrdinalIgnoreCase ); return set; } } /// /// Whether or not this project is standalone-only, and supports disabling the whitelist, compiling with /unsafe, etc. /// public bool IsStandaloneOnly { get; set; } /// /// Custom key-value storage for this project. /// [Hide] public Dictionary Metadata { get; set; } = new(); public override string ToString() => FullIdent; internal void Init( string path ) { path = System.IO.Path.GetDirectoryName( path ); Directory = new DirectoryInfo( path ); AssetsDirectory = new DirectoryInfo( System.IO.Path.Combine( path, "Assets" ) ); Org ??= "local"; Ident ??= Directory.Name.ToLower(); PackageReferences ??= new List(); if ( Type == "map" ) Type = "content"; } internal bool Upgrade() { if ( Schema == 1 ) return false; // // Upgrade from 0 to 1 // if ( Schema < 1 ) { // in version 1 all addons were game addons, // with a set file structure Type = "game"; Title = Ident.ToTitleCase(); Schema = 1; Log.Info( $"Upgraded addon {this} schema from 0 > 1" ); } return true; } /// /// Serialize the entire config to a JSON string. /// public string ToJson() { return Json.SerializeAsObject( this ).ToJsonString( Json.options ); } /// /// Try to get a value at given key in . /// /// Type of the value. /// The key to retrieve the value of. /// The value, if it was present in the metadata storage. /// Whether the value was successfully retrieved. public bool TryGetMeta( string keyname, out T outvalue ) { outvalue = default; if ( Metadata == null ) return false; if ( !Metadata.TryGetValue( keyname, out var val ) ) return false; if ( val is T t ) { outvalue = t; return true; } if ( val is JsonElement je ) { try { outvalue = je.Deserialize( Json.options ) ?? default; } catch ( System.Exception ) { return false; } } return true; } /// /// Get the package's meta value. If it's missing or the wrong type then use the default value. /// public T GetMetaOrDefault( string keyname, T defaultValue ) { if ( Metadata == null ) return defaultValue; if ( !Metadata.TryGetValue( keyname, out var val ) ) return defaultValue; if ( val is T t ) { return t; } if ( val is JsonElement je ) { try { return je.Deserialize( Json.options ) ?? defaultValue; } catch ( System.Exception ) { return defaultValue; } } return defaultValue; } /// /// Store custom data at given key in the . /// /// The key for the data. /// The data itself to store. /// Always true. public bool SetMeta( string keyname, object outvalue ) { Metadata ??= new Dictionary(); if ( outvalue is null ) { return Metadata.Remove( keyname ); } else { Metadata[keyname] = outvalue; } return true; } internal Compiler.Configuration GetCompileSettings() { if ( !TryGetMeta( "Compiler", out var compilerSettings ) ) { compilerSettings = new Compiler.Configuration(); } compilerSettings.Clean(); return compilerSettings; } internal void SetMountState( string name, bool state ) { Mounts ??= new List(); if ( state ) Mounts.Add( name ); else Mounts.Remove( name ); // Make sure there's no dupes Mounts = Mounts.Distinct().ToList(); } }