[SDK] Substrate - Map editing library for C#/.NET [1.3.8]

  • #1
    --- What is Substrate?

    Substrate is a .NET/Mono SDK written in C# for reading, writing, and manipulating data in Minecraft worlds. Substrate isolates the different levels of map data such as blocks, chunks, and regions, and natively supports modifying Alpha and Beta worlds using the same block and chunk interfaces. Substrate also provides interfaces for other data such as Entities, players, and general level data.

    --- Requirements

    Substrate requires .NET Framework 2.0 or higher, or Mono, to run. Compiling Substrate source code requires a compiler that supports C# 3.0 or higher (Visual C# 2008 Express or higher, or comparable Linux tools).

    For convenience to developers, Substrate ships with separate .NET-2.0 and .NET-4.0 assemblies, so that you can include the assembly that best matches your application's target framework.

    --- Download

    Latest Version: 1.3.8

    If you are interested in the development of Substrate, follow or fork the project on the Github Project Page

    --- Additional Information

    See the Introduction wiki page for more information on important classes provided by Substrate: Introduction

    Largely complete API documentation is included with the Substrate download. A number of example projects are also included in the download, and available in the source tree.

    Substrate is provided under the permissive MIT license. If you find this project useful in your software, consider mentioning it in the credits or about box of your application, but this is not required.

    Of course, any feedback, ideas, bug reports, etc. are welcome either in this thread, or on the project page.

    --- Examples

    using System;
    using Substrate;
    
    // This example replaces all instances of one block ID with another in a world.
    // Substrate will handle all of the lower-level headaches that can pop up, such
    // as maintaining correct lighting or replacing TileEntity records for blocks
    // that need them.
    
    // For a more advanced Block Replace example, see replace.cs in NBToolkit.
    
    namespace BlockReplace
    {
    	class Program
    	{
    		static void Main (string[] args)
    		{
    			if (args.Length != 3) {
    				Console.WriteLine("Usage: BlockReplace &--#60;world&--#62; &--#60;before-id&--#62; &--#60;after-id&--#62;");
    				return;
    			}
    
    			string dest = args[0];
    			int before = Convert.ToInt32(args[1]);
    			int after = Convert.ToInt32(args[2]);
    
    			// Open our world
    			BetaWorld world = BetaWorld.Open(dest);
    
    			// The chunk manager is more efficient than the block manager for
    			// this purpose, since we'll inspect every block
    			BetaChunkManager cm = world.GetChunkManager();
    
    			foreach (ChunkRef chunk in cm) {
    				// You could hardcode your dimensions, but maybe some day they
    				// won't always be 16. Also the CLR is a bit stupid and has
    				// trouble optimizing repeated calls to Chunk.Blocks.xx, so we
    				// cache them in locals
    				int xdim = chunk.Blocks.XDim;
    				int ydim = chunk.Blocks.YDim;
    				int zdim = chunk.Blocks.ZDim;
    
    				// x, z, y is the most efficient order to scan blocks (not that
    				// you should care about internal detail)
    				for (int x = 0; x &--#60; xdim; x++) {
    					for (int z = 0; z &--#60; zdim; z++) {
    						for (int y = 0; y &--#60; ydim; y++) {
    
    							// Replace the block with after if it matches before
    							if (chunk.Blocks.GetID(x, y, z) == before) {
    								chunk.Blocks.SetData(x, y, z, 0);
    								chunk.Blocks.SetID(x, y, z, after);
    							}
    						}
    					}
    				}
    
    				// Save the chunk
    				cm.Save();
    			}
    		}
    	}
    }


    --- Projects Using Substrate

    - NBTExplorer - A graphical NBT data editor.
    - MACE - Random cities generator.
    - Teeth of Time - Simulates effects of weather and time on worlds.
    - Eedit - A simple graphical editor for item enchantments.
    - Avanti! - A Paint-like Minecraft world editor.
    - SeeSharp - A Minecraft map exporter / renderer.

    If you've published a tool using Substrate and would like it listed, post your request.

    --- Update History

    [1.3.8]
    - Fix: Item enchantments no longer reset on tree load.
    - Fix: TagNodeString will now accept null values.
    - Fix: Casing error in 'playerGameType' tag.
    - Fix: Initialize Source properties in INbtObjects.
    - Fix: Region Loader no longer tries to pad out non-4KB-aligned files.
    - Fix: Setters in FusedDataArray.
    - Fix: Crash when parsing TAG_END in an NBT List type.
    - Internalized Ionic.Zlib.
    - Updated item and block info for MC 1.5 and 1.6.
    - World classes now accept dimensions specified as strings.
    - Allow setting TileEntity data on blocks not registered as Tile Entities.
    - Regions are now sorted by y,x coordinates on enumeration (contrib. by Sukasa).
    - Added support for GeneratorName setting (contrib. by cry-inc).
    - Added many more attributes to Level (contrib. by snoopen).

    [1.3.7]
    - Fix: Wrong tag name for additional block IDs ("AddBlocks" instead of "Add")
    - Fix: Anvil worlds can now handle block IDs >255
    - Fix: Empty chunk sections below heightmap were not written
    - Fix: NBT Tree discarding root node name on load
    - Fix: Various Entity and TileEntity issues
    - Raw NBT tree exposed on more objects (Entities, TileEntities, Items...)
    - Generic TileEntity objects will be created if specific types are unknown
    - Region files multithread safe
    - MobSpawnerTileEntities have updated data fields

    [1.3.6]
    - Fix: Bug in index calculations of Anvil composite byte/nibble arrays, causing incorrect block updates.
    - Block updates for Minecraft 1.4.

    [1.3.5]
    - Minor changes to RegionFile to make it easier to extend (e.g. for Cubic Chunks). (not released)

    [1.3.4]
    - Fix: Bug in unicode handling of NBT strings which could result in inconsistent NBT binaries being written.

    [1.3.3]
    - Fix: Bug in player loading that could cause Anvil 1.2 worlds to not load. Was introduced in 1.3.2.

    [1.3.2]
    - Fix: ChunkManager.SetChunk did not set chunks at the requested location correctly.
    - Fix: ResetLava in BlockFluid could possibly convert lava blocks into water.
    - Fix: BlockManager could not set blocks above 127. BlockManager is now abstract and split into two concrete classes for Alpha and Anvil.
    - Block, item, and player data updates for Minecraft 1.3.

    [1.3.1]
    - Fixes bug in chunk creation causing the Z coordinate to be used for both X and Z.

    [1.3.0]
    - Unified Anvil map support
    - Multiple breaking changes for Anvil support
    - Block and item updates through Minecraft 1.2

    [1.2.0]
    - Anvil beta (not officially released)

    [1.1.0]
    - Adds TileTick management throughout most of the API
    - AutoTileTick property in AlphaBlockCollection for automatically managing TileTicks.

    [1.0.3]
    - Fixes a bug where clearing the player spawn would have no effect
    - Fixes a bug where deleting a chunk would correctly update the cache, resulting in lost work

    [1.0.2]
    - Fixes a bug reading items with enchantments
    - Fixes a bug enumerating entities not registered with EntityFactory
    - Fixes a bug setting text on sign TileEntities
    - Exposes more direct access to the Chunk cache

    [1.0.1]
    - Fixes a serious bug where entities were written out with invalid data, causing chunk regeneration
    - Several block data enums where updated with fixes
    - Animal entity types are now subclasses of EntityAnimal

    [1.0.0]
    - Ionic.Zlib integrated into the Substrate assembly
    - Project split into .NET2/.NET4 output
    - Added full Enchantment API for items
    - Player and Mob classes updated with additional data
    - Entity/TileEntity registires are enumerable
    - ChangeValueType added to TagNodeList class

    [0.9.0]
    - Data resource / map (item) editing
    - Conversion utilities for map editing
    - NbtFile updated to handle differing compression requirements

    [0.8.4]
    - Fixes TileEntity data not being copied with chunks
    - Unknown/Nonstandard NBT tags are now round-tripped in most objects

    [0.8.3]
    - Fixes painting entities not being updated correctly when their chunk is relocated
    - Entities and TileEntities support MoveBy method for special relocation handling

    [0.8.2]
    - Fixes a bug where Entity/TileEntity coordinates were not updated with chunk

    [0.8.1]
    - Fixes a bug where chunk modified chunk coordinates are not saved

    [0.8.0]
    - 1.9pre5 block, item, and entity types
    - Fixed several bugs in Entities, including an exception caused by Enderman
    - Entity/TileEntity architecture updated to support inheritance, avoid future bugs
    - Optional cache size parameter in OpenWorld/CreateWorld

    [0.7.3]
    - Fixed crash when encountering XPOrb while enumerating entities

    [0.7.2]
    - 1.8 Entity types
    - Added missing properties to Arrow and all Mob entities
    - Fixed lighting bug with Chest blocks

    [0.7.1]
    - 1.8 Block and Item types

    [0.7.0]
    - Major refactoring. You will need to tweak your projects
    - CHM documentation for most of the common APIs
    - Intellisense XML file for the same
    - 1.7 Block and Item types
    - Schematic import and export support
    - CLS Compliance
    - Enumerable PlayerManager
    - Timestamp data exposed
    - NBT Validator event support
    - Numerous bugfixes including cache and TileEntity bugs

    [0.6.2]
    - Fixes bugs with some entities and tile entities

    [0.6.1]
    - Fixes bug with TileEntity updates

    [0.6.0]
    - Added fluid simulation for water and lava
    - AutoFluid property (disabled by default)

    [0.5.3]
    - Fixes performance regression
    - Fixes additional lighting bugs

    [0.5.2]
    - Lighting bugfixes and performance improvements
    - TileEntity fixes
    - Beta 1.6 block and item types
    - 'State' property to BlockInfo (solid/nonsolid/fluid)
    - Chunk copying / setting
    - ItemInfo classes and supporting data enums
    - Re-exposed the PlayerManager
    - All missing examples added

    [0.5.1]
    - Numerous lighting fixes including possible crashing bug
    - Added Beta 1.5 level attributes

    [0.5.0]
    - Major refactoring of Chunk/ChunkRef breaks compatibility, but should be easy to repair
    - Fixes missing Player attributes and bug in level.dat generation
    - Fixes more lighting bugs. Now correctly lights half steps, stairs, and other special blocks
    - Updated chunk caching back-end

    [0.4.1]
    - Lighting fixes for manual chunk relighting
    - Performance fixes for lighting

    [0.4.0]
    - Fixes cache consistency bug, among others
    - Adds manual chunk relighting
    - Example code

    [0.3.0]
    - Adds automatic blocklight and skylight recalculation for all setblockid operations via ChunkRefs or the BlockManager.

    [0.2.1]
    - Fixes serious bug in creating TileEntity or Entity objects.

    [0.2.0]
    - First public release
    Last edited by jaquadro: 7/2/2013 10:46:56 PM
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #2
    I have now released an official (albeit pre-release) version of this library. All feedback is welcome.

    Download: http://code.google.com/p/substrate-mine ... loads/list
    Source: http://code.google.com/p/substrate-mine ... S%2FSource
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #3
    Nice SDK you have there. Would be a shame if something were to happen to it. It's a great help to me, I have tried tackling map editing before, but it was always an epic fail.

    Now, excuse my crappy C# skills, but I have a question for you. I have copied part of the code from your second example, but it shows me errors no matter what I do. To me, it looks like the examples are from version 0.1.0 and in ver 0.2.0 things are done in a different way.

    ...
    
    public void ApplyChunk(NBTWorld world, ChunkRef chunk, int[] fallBlock)
            {
                IBlockManager blockm = world.BlockManager;
    
                // Check all blocks in the chunk
                for (int y = 0; y <= 127; y++)
                {
                    for (int x = chunk.X; x <= chunk.X+15; x++)
                    {
                        for (int z = chunk.Z; z <= chunk.Z+15; z++)
                        {
                            // Skip most blocks
                            double r = rand.NextDouble();
                            if (r > 0.15)
                            {
                                continue;
                            }
    
                            // Attempt to replace block
                            //int oldBlock = chunk.GetBlockID(x, y, z);
                            BlockRef oldBlock = blockm.GetBlockRef(x, y, z); // ERRORS IN THIS LINE
                            foreach (int i in fallBlock)
                            {
                                if (oldBlock.ID == i)
                                {
                                    ...


    Error 1 The call is ambiguous between the following methods or properties: 'Substrate.ILitBlockContainer.GetBlockRef(int, int, int)' and 'Substrate.IPropertyBlockContainer.GetBlockRef(int, int, int)'

    Error 2 Cannot implicitly convert type 'Substrate.ILitBlock' to 'Substrate.BlockRef'. An explicit conversion exists (are you missing a cast?)
  • #4
    Yeah there's still some changes going on. I ripped these examples out of NBToolkit before I had it running correctly on the library. I'm actually working on a small set of complete, standalone, fully tested examples to include with the next release (such as block replace, world conversion, map generation, inventory editing, etc).

    In the example code, change IBlockManager blockm to BlockManager blockm (actually, I don't think I ever used IBlockManager directly in that code?).

    Due to changes in the interface hierarchy, directly using IBlockManager doesn't make sense anymore unless you're requesting an IBlock (which exposes access to ID and data fields, nothing else). This might come into play if I write in a support class for mcschema files or creative classic files, although for the later I need Java interop support so maybe that won't happen. This part of the design is still under review.

    If you think something is overly weird or difficult to use, please mention it. I'll try to either justify it or come up with something different. If you would like something explained in more detail, I can also do that.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #5
    Using BlockManager instead of IBlockManager results in this:

    Error 1 Cannot implicitly convert type 'Substrate.IBlockManager' to 'Substrate.BlockManager'. An explicit conversion exists (are you missing a cast?)

    It's why I tried IBlockManager in the first place.


    I haven't looked at relighting yet, is there any (relatively) simple way to do it?
  • #6
    Sorry, for now, use:

    BlockManager bm = world.BlockManager as BlockManager

    I'm missing a couple overrides in the alpha block container interface, once I put them in place the cast should not be necessary anymore.

    Relighting is .. interesting. I don't really have an easy solution for it but it's next on my list of things to tackle. But as a general idea, you could do an initial sweep of a chunk you modified, and make sure that any block with a luminosity value greater than 0 (can be checked in each block's blockinfo) is set to that light level. Then repeatedly sweep through the chunk looking for blocks that are not set to max-1 of all the neighbor's light values, and update them. Repeat the process until the chunk converges. Edge cases may be tricky as you'll need to inspect neighboring chunks as well. This is one task that can't easily be accomplished with a global block view because of memory constraints, unless you want to thrash your disk.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #7
    Moar bug reports:

    Every time I try placing a chest, my app crashes. Placing normal blocks at the same location works great, but chests, dispensers and similar always crash.



    Here you can see a cobble block (id 4) being placed, and then a chest (54) being placed at the same spot. While cobble is fine, the chest crashes the app instantly after being placed.
  • #8
    Thank you for the bug report. It was a minor problem to fix. There is a fixed download available now.

    http://code.google.com/p/substrate-mine ... loads/list

    The crash occurred on chests, furnaces, and similar because they have TileEntities attached. The routines to update block IDs automatically enforce TileEntity consistency, so if you create a furnace, it will create a reasonable default TileEntity record for it as well. Likewise when you change it back to cobblestone, the records will be removed.

    You can also update TileEntities if, for example, you wanted to fill the new chest block with some items.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #9
    I'm probably not going to package another release for a couple days, unless it's another bugfix.

    However, if you don't mind grabbing the source tree, I committed some block relighting code. Calling SetBlockID on a ChunkRef will immediately recalculate blocklight (but currently, not skylight). Actually, since BlockRef uses ChunkRefs, it will probably recalculate light as well. Eventually I will include some way of toggling this behavior, since realtime lighting recalculation will be too expensive for some use patterns.

    There's still some problems calculating light past region boundaries. That will get patched later.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #10
    Version 0.3.0 is available for download
    http://code.google.com/p/substrate-mine ... loads/list

    This fully incorporates automatic blocklight and sunlight recalculation whenever a block is updated. It seems to do a pretty good job, but it's possible that it violates some of the lighting rules. If you can identify a lighting pattern that occurs in Minecraft but is not faithfully reproduced by Substrate, please post a picture of it.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #11
    Relighting works really good, I haven't been able to find a single flaw so far. It's also quite fast, I have expected my app to slow down drastically, but the speed loss is almost unnoticeable.
  • #12
    Great to hear. I was pleasantly surprised by how well it performs for general purpose tasks. However as I predicted, it's a disaster for map generation. So in recent pushes to the source tree (no new packaged downloads yet), auto-relighting can be toggled on a per-chunkref basis, and there are new methods to manually relight a chunk in one pass. The improvement for generating chunks from scratch (at least, simple flatland-style chunks) is at least 20-30x.

    I've also pushed the first purpose-built, well-commented example, generating a flat map. On a powerful workstation, this generated 1600 solid, lit chunks in under 10 seconds. Of course, it represents a lower bound.

    (Note, the API for World objects has been changed)
    using System;
    using Substrate;
    
    // FlatMap is an example of generating worlds from scratch with Substrate.
    // It will produce a completely flat, solid map with grass, dirt, stone,
    // and bedrock layers.  On a powerful workstation, creating 400 of these
    // chunks only takes a few seconds.
    
    namespace FlatMap
    {
        class Program
        {
            static void Main (string[] args)
            {
                string dest = "F:\\Minecraft\\test";
                int xmin = -20;
                int xmax = 20;
                int zmin = -20;
                int zmaz = 20;
    
                // This will instantly create any necessary directory structure
                BetaWorld world = BetaWorld.Create(dest);
                ChunkManager cm = world.GetChunkManager();
    
                // We can set different world parameters
                world.Level.LevelName = "Flatlands";
                world.Level.SetDefaultPlayer();
    
                // We'll create chunks at chunk coordinates xmin,zmin to xmax,zmax
                for (int xi = xmin; xi < xmax; xi++) {
                    for (int zi = zmin; zi < zmaz; zi++) {
                        // This line will create a default empty chunk, and create a
                        // backing region file if necessary (which will immediately be
                        // written to disk)
                        ChunkRef chunk = cm.CreateChunk(xi, zi);
    
                        // This will suppress generating caves, ores, and all those
                        // other goodies.
                        chunk.IsTerrainPopulated = true;
    
                        // Auto light recalculation is horrifically bad for creating
                        // chunks from scratch, because we're placing thousands
                        // of blocks.  Turn it off.
                        chunk.AutoRecalcLight = false;
    
                        // Set the blocks
                        FlatChunk(chunk, 64);
    
                        // Reset and rebuild the lighting for the entire chunk at once
                        chunk.RebuildBlockLight();
                        chunk.RebuildSkyLight();
    
                        Console.WriteLine("Built Chunk {0},{1}", chunk.X, chunk.Z);
    
                        // Save the chunk to disk so it doesn't hang around in RAM
                        cm.Save();
                    }
                }
    
                // Save all remaining data (including a default level.dat)
                // If we didn't save chunks earlier, they would be saved here
                world.Save();
            }
    
            static void FlatChunk (ChunkRef chunk, int height)
            {
                // Create bedrock
                for (int y = 0; y < 2; y++) {
                    for (int x = 0; x < 16; x++) {
                        for (int z = 0; z < 16; z++) {
                            chunk.SetBlockID(x, y, z, (int)BlockType.BEDROCK);
                        }
                    }
                }
    
                // Create stone
                for (int y = 2; y < height - 5; y++) {
                    for (int x = 0; x < 16; x++) {
                        for (int z = 0; z < 16; z++) {
                            chunk.SetBlockID(x, y, z, (int)BlockType.STONE);
                        }
                    }
                }
    
                // Create dirt
                for (int y = height - 5; y < height - 1; y++) {
                    for (int x = 0; x < 16; x++) {
                        for (int z = 0; z < 16; z++) {
                            chunk.SetBlockID(x, y, z, (int)BlockType.DIRT);
                        }
                    }
                }
    
                // Create grass
                for (int y = height - 1; y < height; y++) {
                    for (int x = 0; x < 16; x++) {
                        for (int z = 0; z < 16; z++) {
                            chunk.SetBlockID(x, y, z, (int)BlockType.GRASS);
                        }
                    }
                }
            }
        }
    }
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #13
    Just added a few heavier functions, manually relighting chunks at the end should give me a considerable speedboost now. Pretty useful everywhere, I think.

    The map generator example will come in handy in the future, too.

    BTW, finally got something to show.

    Before:


    After:


    The universal griefer tool :biggrin.gif: Still have to fix some bugs with TileEntities and add some customizeability (currently everything is hardcoded, even world path), maybe an alpha release after that.
  • #14
    New issue here. Is it possible to update water flow with Substrate? Recently I got water and lava floating in midair after fiddling with blocks in the map.
  • #15
    I don't know how involved it would be to simulate the fluid physics. I'll look into it but no promises.

    I would recommend trying to identify and remove the water. If you're feeling enterprising, you can attempt to do the simulation yourself, and manually set the data value on the water blocks to set their flow value (or rather, create the flow blocks that currently aren't there).
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #16
    Is there any way to get the game itself to update the water?
  • #17
    Something near the block needs to change to trigger an update of the water. I'm not sure if there are other options.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #18
    http://code.google.com/p/substrate-mine ... loads/list

    Version 0.4.0 is another pre-release version (I expect at least a couple more of these to follow before I'm happy enough to bless this project). It rolls up numerous bugfixes, some of them serious. It includes manual chunk relighting, and some example code.
    NBTExplorer - NBT editor for Windows and Mac
    Substrate - Minecraft map editing library for C#/.NET
  • #19
    thanks for this very helpfull tool.

    I couldn't figure out, how to go through all existing blocks of a map and read their ID and Coordinates,
    and also how to select a block by coordinates.
    Could you please give me a short codeexample on both?

    Greetings,
    Tatonka
  • #20
    Two examples are provided within the download, but going through all blocks is currently missing I think.

    You can use this in your app:

    //This function goes through all blocks in every chunk. Useful if you want to replace all blocks with something else, or similar things.
    
    public void RunAllBlocks()
    { 
        // Get a chunk manager
        IChunkManager chunkMan = world.GetChunkManager();
    
        // Go through all chunks
        foreach (ChunkRef chunk in chunkMan)
        {
            // And through all blocks
            for (int y = 0; y <= 127; y++)
            {
                for (int x = 0; x <= 15; x++)
                {
                    for (int z = 0; z <= 15; z++)
                    {
                        int id = chunk.GetBlockID(x, y, z);
                        // At this point, you have x, y, z and id variables.
                    }
                }
            }
        }
    }


    I don't quite understand what you mean by "selecting a block by coordinates". Does it mean you want to access a block at a certain location?

    Edit: I absolutely forgot the same thing is even in the OP, only better documented /facepalm
  • To post a comment, please or register a new account.
Posts Quoted:
Reply
Clear All Quotes