using Editor.MapDoc; using NativeHammer; namespace Editor.MapEditor; /// /// Create prop_static entities from a Package model. /// [CanDrop( "model" )] [Expose] class ModelDropTarget : IMapViewDropTarget { MapEntity Entity { get; set; } public void DragEnter( Package package, MapView view ) { // Get this early before any async shit ( only really needed cause of free drag mode ) Vector3 normal = Vector3.Zero; Vector3 position = Vector3.Zero; view.native.GetDropTarget( ref normal, ref position, view.MousePosition ); Entity = CreateModelEntityFromPackage( view.native, package, position ); // Letting native hammer deal with this shit, it's not great though.. view.native.EnterFreeDragMode( view.MousePosition, Entity, Vector3.Up, true ); } public void DragMove( MapView view ) { if ( !Entity.IsValid() ) return; // // Free drag mode keeps a copy of the baseline entity that it restores from every drag // So just fucking hack around it and restore it // As we get more and more Hammer C# API exposed we can do our own free drag and ignore this shit // var model = Entity.GetKeyValue( "model" ); view.native.UpdateFreeDragMode( view.MousePosition, false ); Entity.SetKeyValue( "model", model ); } public void DragDropped( MapView view ) { view.native.ExitFreeDragMode( false ); History.MarkUndoPosition( "Dropped Model" ); History.KeepNew( Entity ); } public void DragLeave( MapView view ) { view.native.ExitFreeDragMode( true ); if ( Entity.IsValid() ) view.MapDoc.DeleteNode( Entity ); } static MapEntity CreateModelEntityFromPackage( CMapView view, Package package, Vector3 position ) { var entity = new MapEntity( view.GetMapDoc() ); entity.ClassName = "prop_static"; entity.Position = position; // // If we already have the package installed, no need to query any remote shit // Set the model immediately and exit // var installedPackage = AssetSystem.CloudDirectory.FindPackage( package.FullIdent ); if ( installedPackage != null ) { var modelName = installedPackage.PrimaryAsset; if ( FileSystem.Cloud.FileExists( modelName ) ) { entity.SetKeyValue( "model", modelName ); return entity; } } // // Async install the model package, and then update the entity's model // We're passing the entity node id for safety // SetEntityToPackageModelAsync( entity, package.FullIdent ); return entity; } /// /// Installs the package and sets the model on the entity when that's done /// static async void SetEntityToPackageModelAsync( MapEntity entity, string packageIdent ) { ThreadSafe.AssertIsMainThread(); // // Fetch full package for the meta // var package = await Package.Fetch( packageIdent, false ); // // Set some default bounds so the user knows the right size whilst it loads // { if ( !entity.IsValid() ) return; var mins = package.GetMeta( "RenderMins", Vector3.Zero ); var maxs = package.GetMeta( "RenderMaxs", Vector3.Zero ); entity.SetDefaultBounds( mins, maxs ); } // // Install our package // var asset = await AssetSystem.InstallAsync( package, loading: ( float progress ) => { if ( !entity.IsValid() ) return; LoadingProgress[entity] = progress; Hammer.DirtyViewHuds(); // ? } ); if ( asset == null ) return; // // Package is installed, update the model and we're done. // { if ( entity.IsValid() ) { entity.SetKeyValue( "model", asset.Path ); } } LoadingProgress.Remove( entity ); } static Dictionary LoadingProgress = new(); [Event( "hammer.rendermapview" )] protected static void RenderLoadProgress( MapView oniichan ) { // You could do some lovely stuff here if you really wanted // But just showing the % is plenty foreach ( var kvp in LoadingProgress ) { if ( !kvp.Key.IsValid() ) continue; Gizmo.Draw.Sprite( kvp.Key.Position + Vector3.Up * 16, 16, "materials/gizmo/downloads.png" ); Gizmo.Draw.Text( $"Downloading {kvp.Value * 100}%", new Transform( kvp.Key.Position ), size: 14 * Application.DpiScale ); } } }