using Native; using System; using System.Reflection; using System.Text; namespace Editor; public partial class MapClassVariable { internal void ToNative( CGameDataVariable gd ) { gd.SetName( Name ); gd.SetLongName( LongName ); gd.SetDescription( Description ); gd.SetGroupName( GroupName ); // Editor override var editor = VariableEditor(); if ( !string.IsNullOrEmpty( editor ) ) { Metadata.Add( "editor", editor ); } // Nullable for ModelDoc if ( PropertyType != null && Nullable.GetUnderlyingType( PropertyType ) != null ) { Metadata.Add( "nullable", "true" ); } // Metadata // TODO: This could probably be done better if ( Metadata.Count > 0 ) { StringBuilder output = new(); output.AppendLine( "{" ); foreach ( var kvp in Metadata ) { bool isNotString = bool.TryParse( kvp.Value, out bool _ ) || float.TryParse( kvp.Value, out float _ ); output.AppendLine( $"\t{kvp.Key} = {(isNotString ? kvp.Value : kvp.Value.QuoteSafe())}" ); } output.AppendLine( "}" ); gd.SetMetadataFromString( output.ToString() ); } // Default value var defaultValue = EditorDefaultValue(); if ( (PropertyType == typeof( int ) || PropertyType == typeof( int? )) && defaultValue is int defVal ) { // idk why valve do this, maybe i can change it // This is necessary for types: int, integer, flags, lod_level, node_dest, node_id gd.SetDefaultValueNumber( defVal ); } else { gd.SetDefaultValue( defaultValue.ToString() ); } // Type if ( string.IsNullOrEmpty( PropertyTypeOverride ) ) { gd.SetTypeFromString( SerializedType( PropertyType ) ); if ( PropertyType.IsEnum ) { var itemSet = gd.GetItemSet(); Assert.True( itemSet.IsValid ); var isFlags = PropertyType.HasAttribute( typeof( FlagsAttribute ) ); int defaultFlags = defaultValue is int @int ? @int : 0; var enumFields = PropertyType.GetFields().Where( e => e.IsLiteral && !e.HasAttribute( typeof( HideAttribute ) ) ); foreach ( var field in enumFields ) { var name = field.Name.ToTitleCase(); var value = field.GetRawConstantValue(); if ( isFlags ) { itemSet.AddFlagItem( (int)value, name, (defaultFlags & (int)value) != 0 ); } else { itemSet.AddChoiceItem( value.ToString(), name ); } } } } else { gd.SetTypeFromString( PropertyTypeOverride ); } } /// /// Translates our C# type to a type mapdoc understands. /// I doubt we actually need to use a string here for it, but it does make things easier. /// e.g an array /// internal string SerializedType( Type type ) { if ( type == typeof( int ) ) return "integer"; if ( type == typeof( float ) ) return "float"; if ( type == typeof( double ) ) return "float"; if ( type == typeof( bool ) ) return "boolean"; if ( type == typeof( Vector3 ) ) return "vector"; if ( type == typeof( Vector2 ) ) return "vector2"; if ( type == typeof( string ) ) return "string"; if ( type == typeof( Rotation ) ) return "angle"; if ( type == typeof( Angles ) ) return "angle"; if ( type == typeof( Color ) ) return "color255"; // TODO: this seems fucked, we should have a proper float Color type in Hammer if ( type == typeof( Color32 ) ) return "color255"; if ( type == typeof( RangedFloat ) ) return "vector"; if ( type == typeof( Material ) ) return "material"; if ( type == typeof( Model ) ) return "resource:vmdl"; if ( type.Name == "TagList" ) return "tags"; if ( type.Name == "SoundEvent" ) return "sound"; // // The type itself knows it's Resource type // e.g Model = resource:vmdl, Texture = resource:vtex, etc.. // var fgdTypeAttr = type.GetCustomAttribute( false ); if ( fgdTypeAttr != null ) { return fgdTypeAttr.Type; } if ( type.IsEnum ) { if ( type.HasAttribute( typeof( FlagsAttribute ) ) ) return "flags"; return "choices"; } // Nullable var nullableType = Nullable.GetUnderlyingType( type ); if ( nullableType != null ) { return SerializedType( nullableType ); } // Game Resources var gameRes = type.GetCustomAttribute( false ); if ( gameRes != null ) { return $"resource:{gameRes.Extension}"; } Log.Warning( $"{this}: Missing type for: {(type.IsGenericType ? type.GetGenericTypeDefinition() : type.Name)}" ); return "string"; } // Should return either an int or a string! internal object EditorDefaultValue() { // // For every primitive or struct type in SerializedType // we also wanna turn the values into a string Hammer can parse // if ( DefaultValue is int ) return DefaultValue; if ( DefaultValue is float ) return DefaultValue.ToString(); if ( DefaultValue is double ) return DefaultValue.ToString(); if ( DefaultValue is bool ) return ((bool)DefaultValue == true ? "1" : "0"); if ( DefaultValue is string ) return DefaultValue.ToString(); if ( DefaultValue is Color32 color32 ) return $"{color32.r} {color32.g} {color32.b} {color32.a}"; if ( DefaultValue is Vector2 vector2 ) return $"{vector2.x} {vector2.y}"; if ( DefaultValue is Vector3 vector3 ) return $"{vector3.x} {vector3.y} {vector3.z}"; if ( DefaultValue is Angles angles ) return $"{angles.pitch} {angles.yaw} {angles.roll}"; if ( DefaultValue != null && DefaultValue.GetType().IsEnum ) return Convert.ToInt32( DefaultValue ); // todo: this is fucked, Hammer should have a proper float based color type if ( DefaultValue is Color color ) { var color322 = color.ToColor32(); return $"{color322.r} {color322.g} {color322.b} {color322.a}"; } if ( DefaultValue is Rotation rotation ) { var rotAngles = rotation.Angles(); return $"{rotAngles.pitch} {rotAngles.yaw} {rotAngles.roll}"; } // // If the default is not set, deduce it from the property type // This is useful for cases like chagning a vector to "0 0 0" and it still counting as changed // if ( PropertyType == typeof( int ) ) return 0; if ( PropertyType == typeof( float ) ) return "0"; if ( PropertyType == typeof( double ) ) return "0"; if ( PropertyType == typeof( bool ) ) return "0"; if ( PropertyType == typeof( Vector3 ) ) return "0 0 0"; if ( PropertyType == typeof( Vector2 ) ) return "0 0"; if ( PropertyType == typeof( Rotation ) ) return "0 0 0"; if ( PropertyType == typeof( Angles ) ) return "0 0 0"; if ( PropertyType == typeof( Color ) ) return "255 255 255 255"; // TODO: For assets/modeldoc this should be "1 1 1 1" if ( PropertyType == typeof( Color32 ) ) return "255 255 255 255"; //if ( PropertyType == typeof( RangedFloat ) ) return "0 0 0"; return ""; } internal string VariableEditor() { if ( PropertyType == null ) return null; if ( PropertyType == typeof( RangedFloat ) ) return "Ranged()"; // The type itself knows the editor var fgdTypeAttr = PropertyType.GetCustomAttribute( false ); if ( fgdTypeAttr != null && !string.IsNullOrEmpty( fgdTypeAttr.Editor ) ) { return fgdTypeAttr.Editor; } return null; } }