commit c21bda65aac71241493b5999cc3d8ed1d3ce4dd9 Author: Olof Larsson Date: Sat Mar 3 12:58:39 2012 +0100 first commit diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..628c952 --- /dev/null +++ b/readme.md @@ -0,0 +1,28 @@ +s2b - Schematic to bo2 coverter +==================== +This ruby script converts minecraft *.schematic files into *.bo2 files which is highly usable when creating custom trees for the plugin [TerrainControl](http://dev.bukkit.org/server-mods/terrain-control/). You build your custom tree ingame, select it and export it with [WorldEdit](http://dev.bukkit.org/server-mods/worldedit/). + +Usage +--------- +1. download the file s2b.rb from this repository. +1. [install ruby](http://www.ruby-lang.org/en/downloads/) +1. [install the nbtfile gem](http://rubygems.org/gems/nbtfile) +1. run s2b.rb once. This will create the folders "in" and "out" next to s2b.rb +1. place all *.schematics files in that folder named "in". +1. run s2b.rb. +1. The "out" folder now contains all new .bo2 files + +Special behavior and Suggested workflow +--------- +This script have some special behaviors: +* Air blocks are not exported - Because in general you want the bo2 to to be transparent. Use the BBOB application to add in air blocks afterwards. +* Magenta wool is not exported - Use magenta wool for cuboid min and max points. Since these will be in the WorldEdit selection it is handy if they are not exported. +* Dark blue wool is not exported - We use this to stop wines from growing to long. +* If the name of the .schematic file ends with R5 (as in Root-depth 5) a z-offset of 5 will be used. The numer can ofcource be any number and is not limited to 5. + +This is how we do it on MassiveCraft: +Create a creative world on your server using a multiworld plugin such as [MultiVerse-Core](http://dev.bukkit.org/server-mods/multiverse-core/). You may also want to keep separate inventories for that world using a plugin such as [MultiVerse-Inventories](http://dev.bukkit.org/server-mods/multiverse-inventories/). To make that world be a nice huge grass field you will want to use a custom world generator for it. We suggest
[CleanroomGenerator](http://dev.bukkit.org/server-mods/cleanroomgenerator/):1,bedrock,30,dirt,1,grass + +Now you can let people build custom trees there. The players place a sign with the name of the custom tree on top of the WorldEdit maxpoint. The name should end with something like R5 to make use of the z-offset feature. + +We use a plugin called WorldEditSignSave. It will save the current WorldEdit clipboard using the name of the sign you are currently looking at. We will release this plugin when we get time. \ No newline at end of file diff --git a/s2b.rb b/s2b.rb new file mode 100644 index 0000000..b7f5a00 --- /dev/null +++ b/s2b.rb @@ -0,0 +1,114 @@ +# Massconverter from .schematic files to .bo2 files +# Usage: ruby s2b.rb +# This will convert all schematics in the sibling folder "in". +# The results will be placed in the folder "out". +# Air and magenta colored wool is ignored. +# If the file name is ending with _z4 the z will use a -4 offset +# +# See: http://www.minecraftwiki.net/wiki/Schematic_File_Format +# https://github.com/Wickth/TerrainControll/blob/master/bo2spec.txt +# +# Lisence: MIT + +require "nbtfile" + +# Needy blocks are items like torches and doors, which require other blocks to +# be in place -- otherwise they'll simply fall to the ground as entities. We +# defer them to the end of the BOB output to ensure they're "built" last. +NEEDY_BLOCKS = [6, 26, 27, 28, 31, 32, 37, 38, 39, 40, 50, 51, 55, 59, 63, 64, 65, 66, 68, 69, 70, 71, 72, 75, 76, 77, 78, 81, 83, 85, 90, 96, 104, 105, 106, 111, 115] +FILE_HEADER = ["[META]","version=2.0","[DATA]"] + +# Folder names +NAME_IN = "in" +NAME_OUT = "out" + +# Parse args +@options = ARGV.select { |arg| arg =~ /^\-+[a-z]+/ } +@args = ARGV - @options + +# Create folders if they don't exist +Dir.mkdir(NAME_IN) if ! File::exists?(NAME_IN) +Dir.mkdir(NAME_OUT) if ! File::exists?(NAME_OUT) + +# Find all the files in the in-folder +@filenames = Dir.glob(NAME_IN + "/*.*") + +# Handle a file according to our rules +def handleFileName(filename) + # Declare what to use + schematic = nil + zoffset = 0 + outfilename = nil + outlines = nil + + # Load the schematic + open(filename, "rb") do |io| + schematic = NBTFile.load(io)[1] + end + + # Figure out the zoffset + matches = filename.scan(/R([0-9]+)\./) + if matches.count == 1 + zoffset = -(matches[0][0].to_i) + end + + # Create the new outfilename + outfilename = filename.clone + outfilename[".schematic"] = ".bo2" + outfilename[NAME_IN+"/"] = NAME_OUT+"/" + + outlines = schematicToBO2(schematic, zoffset) + + open(outfilename, "w") do |io| + outlines.each { |line| io.puts(line) } + end + + print "Converting " + filename + "\n" +end + +# The method to convert a schematic to a list of strings (lines in a file) +def schematicToBO2(schematic, zoffset) + ret = [] + # Slice blocks and block metadata by the size of the schematic's axes, then + # zip the slices up into pairs of [id, metadata] for convenient consumption. + blocks = schematic["Blocks"].bytes.each_slice(schematic["Width"]).to_a + blocks = blocks.each_slice(schematic["Length"]).to_a + data = schematic["Data"].bytes.each_slice(schematic["Width"]).to_a + data = data.each_slice(schematic["Length"]).to_a + layers = blocks.zip(data).map { |blocks, data| blocks.zip(data).map { |blocks, data| blocks.zip(data) } } + deferred = [] + + ret.push(*FILE_HEADER) + layers.each_with_index do |rows, z| + z += zoffset + rows.each_with_index do |columns, x| + x -= schematic["Width"] / 2 # Center the object on the X axis + columns.each_with_index do |(block, data), y| + if block == 0 || (block == 35 && (data == 2 || data == 11)) then # We ignore air and magentawool + next + end + y -= schematic["Length"] / 2 # Center the object on the Y axis + line = "#{y},#{x},#{z}:#{block}.#{data}" + if NEEDY_BLOCKS.include?(block) + deferred << line + else + ret << line + end + end + end + end + + # Write needy blocks to the end of the BOB file, respecting the order of + # NEEDY_BLOCKS, in case some blocks are needier than others. + deferred.sort! { |a, b| NEEDY_BLOCKS.index(a) <=> NEEDY_BLOCKS.index(b) } + deferred.reverse.each { |line| ret << line } + + return ret +end + +print "== START ==\n" +# For each filename +@filenames.each do |filename| + handleFileName(filename) +end +print "== DONE ==" \ No newline at end of file