using NativeEngine; using System.Threading; namespace Sandbox; /// /// Map geometry that can be rendered within a . /// public sealed partial class SceneMap : IValid { /// /// The scene world this map belongs to. /// public SceneWorld World { get; internal set; } private IWorldReference WorldRef; internal string WorldGroup { get; private set; } internal IPVS PVS { get; private set; } /// /// Is the map valid. /// public bool IsValid => WorldRef.IsValid; /// /// Bounds of the map. /// public BBox Bounds { get { if ( !IsValid ) return default; WorldRef.GetWorldBounds( out var min, out var max ); return new BBox( min, max ); } } public Vector3 WorldOrigin { set { if ( !IsValid ) return; WorldRef.SetWorldTransform( new Transform( value ) ); } } /// /// cs_assault /// public string MapName { get; private set; } /// /// maps/davej/cs_assault /// public string MapFolder { get; private set; } private SceneMap() { } /// /// Create a scene map within a scene world. /// public SceneMap( SceneWorld sceneWorld, string map ) : this( sceneWorld, map, null ) { } /// /// Create a scene map within a scene world. /// internal SceneMap( SceneWorld sceneWorld, string map, MapLoader loader ) { if ( !CreateWorld( sceneWorld, map, false, loader is null ? 0 : loader.WorldOrigin ) ) return; CreateEntities( "default_ents", loader ); sceneWorld.UpdateObjectsForRendering(); } /// /// Invoked when a map file is updated (re-compiled in Hammer.) /// internal static Action OnMapUpdated; private void CreateEntities( string map, MapLoader loader ) { loader ??= new SceneMapLoader( World, null ); loader.CreateEntities( WorldRef, map ); } private bool CreateWorld( SceneWorld sceneWorld, string map, bool async, Vector3 origin ) { Assert.IsValid( sceneWorld ); MapFolder = System.IO.Path.ChangeExtension( map, null ); // CWorldRendererMgr::GetLocalMapName just strips maps/ from the start and uses that map = System.IO.Path.ChangeExtension( map, null ); map = map.TrimStart( '\\', '/', ' ' ); if ( map.StartsWith( "maps/" ) ) map = map[5..]; if ( string.IsNullOrWhiteSpace( map ) ) return false; MapName = map; const bool loadVis = true; const bool precacheOnly = false; var worldGroup = sceneWorld.native.GetWorldDebugName(); var worldRef = g_pWorldRendererMgr.CreateWorld( MapFolder + ".vpk", sceneWorld, async, true, loadVis, precacheOnly, worldGroup, // Worldgroup ID new Transform( origin ) ); if ( !worldRef.IsValid ) { Log.Warning( $"{this}: Unable to create world for map {map}" ); return false; } lock ( sceneWorld.InternalSceneMaps ) { Assert.False( sceneWorld.InternalSceneMaps.Any( x => x.WorldRef == worldRef ), "Scene world already contains this scene map" ); } WorldRef = worldRef; MapFolder = worldRef.GetFolder(); WorldGroup = worldGroup; World = sceneWorld; if ( !async ) { // We're not loading async so the world should be loaded now OnWorldLoaded(); } return true; } private void OnWorldLoaded() { WorldRef.PrecacheAllWorldNodes( 0x0080 ); PVS = g_pEnginePVSManager.BuildPvs( WorldRef ); World.AddSceneMap( this ); } /// /// Create scene map asynchronously for when large maps take time to load. /// public static Task CreateAsync( SceneWorld sceneWorld, string map, CancellationToken cancelToken = default ) { return CreateAsync( sceneWorld, map, null, cancelToken ); } /// /// Create scene map asynchronously for when large maps take time to load. /// internal static async Task CreateAsync( SceneWorld sceneWorld, string map, MapLoader loader, CancellationToken cancelToken = default ) { var sceneMap = new SceneMap(); if ( !sceneMap.CreateWorld( sceneWorld, map, true, loader.WorldOrigin ) ) return null; if ( !sceneMap.IsValid ) return null; var worldRef = sceneMap.WorldRef; while ( !worldRef.IsWorldLoaded() ) { await Task.Delay( 1, cancelToken ); cancelToken.ThrowIfCancellationRequested(); } sceneMap.OnWorldLoaded(); sceneMap.CreateEntities( "default_ents", loader ); return sceneMap; } /// /// Delete this scene map. You shouldn't access it anymore. /// public void Delete() { if ( World.IsValid() ) { World.RemoveSceneMap( this ); //World.Delete(); // don#t delete this are you crazy World = null; } if ( WorldRef.IsValid ) { WorldRef.Release(); WorldRef = IntPtr.Zero; } if ( PVS.IsValid ) { // don't destroy if another SceneWorld is using it if ( !SceneWorld.All.Where( x => x.IsValid() && x.native.GetPVS() == PVS ).Any() ) { g_pEnginePVSManager.DestroyPvs( PVS ); } PVS = default; } WorldGroup = null; } }