Skip to content
This repository has been archived by the owner on Sep 16, 2019. It is now read-only.

Document loaders

Daniel Walder edited this page Oct 23, 2018 · 2 revisions

In Sledge, a document is what appears inside the main tabbed view in the middle of the window. Currently the only available document type is the MapDocument, which is the document class for the BSP editor.

Adding a BSP Source format

Required reading: Exporting and importing classesMap primitive objects

By default, most common formats are built into Sledge, including:

  • RMF (Hammer 3)
  • VMF (Hammer 4)
  • MAP (Quake)
  • SMF (Sledge native format)

Other formats are planned, including JMF (Jackhammer editor) and OBJ (3D models).

Adding a custom format isn't difficult if you already know the format. The example below is from the OBJ loader.

Start by implementing the shell of the IBspSourceProvider interface. The interface is documented below.

[Export(typeof(IBspSourceProvider))]
public class ObjBspSourceProvider : IBspSourceProvider
{
    private static readonly IEnumerable<Type> SupportedTypes = new List<Type>
    {
        // Sledge only supports solids in the OBJ format
        typeof(Solid),
    };

    // A list of data types supported by this loader.
    public IEnumerable<Type> SupportedDataTypes => SupportedTypes;

    // A list of file extensions supported by this loader.
    public IEnumerable<FileExtensionInfo> SupportedFileExtensions { get; } = new[]
    {
        new FileExtensionInfo("Wavefront model format", ".obj")
    };

    // Code to load the map from a file goes here
    public Task<BspFileLoadResult> Load(Stream stream, IEnvironment environment)
    {
        throw new NotImplementedException();
    }

    // Code to save the map to a file goes here
    public Task Save(Stream stream, Map map)
    {
        throw new NotImplementedException();
    }
}

The BspFileLoadResult is a simple class which contains the loaded map, a list of invalid objects, and a list of messages to show the user. If there are no messages or invalid objects for the format, these can be ignored, you just have to set the map.

Both the Load and Save methods are asynchronous, so it's a good idea to take advantage of that fact whenever possible. But loading data from a single file is usually synchronous code, so most loaders will probably just launch a new task to do the work.

public Task<BspFileLoadResult> Load(Stream stream, IEnvironment environment)
{
    return Task.Run(() =>
    {
        using (var reader = new StreamReader(stream, Encoding.ASCII, true, 1024, false))
        {
            var result = new BspFileLoadResult();

            var map = new Map();

            // Read map from file

            result.Map = map;
            return result;
        }
    });
}

public Task Save(Stream stream, Map map)
{
    return Task.Run(() =>
    {
        using (var writer = new StreamWriter(stream, Encoding.ASCII, 1024, true))
        {
            // Write map data here
        }
    });
}

The actual loading code will vary depending on the format you are loading. Refer to the code for the current loaders for an idea on how the code should look.

The most important rule is that DescendantsChanged should be called on each object when the code has finished building an object. This will generate the bounding box for the object and update its parent bounding boxes as well. This is required for interaction to work correctly.

Adding a loader for completely new document type

If you're doing some advanced stuff by adding a whole new document type, then you will need to implement the IDocumentLoader interface for your document type. The BspSourceDocumentLoader is the only class that currently implements this interface.

This interface is well documented and shouldn't be too difficult to implement, so refer to the code comments and the BspSourceDocumentLoader code when implementing this interface. Be sure that you have exported your class with [Export(typeof(IDocumentLoader))].