User:Mkmagicannon/Map format

Now that the map editor has been released, we have a format we can use.

.bnlbin format
import base64,json,zlib;print zlib.decompress(base64.b64decode(json.loads(zlib.decompress(open("map-1.bnlbin","rb").read))['map']['blocks_data']))

will get you the block data of `map-1.bnlbin`.

After inflating the map data, you will have a JSON structure of this form (here's an example incomplete map):

{ "name":"Map Name Goes Here", "description":"Completely accurate description that turns out to be not completely accurate and actually a red herring.", "default_image":false, "is_published":false, "map":{ "version":6, "schema":4, "match":"shield_rush_2", "color_palette":[ {"r":96,"g":209,"b":234,"a":255}, {"r":255,"g":55,"b":55,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255}, {"r":0,"g":0,"b":0,"a":255} ], "spawn_points":[], "units":[], "cameras":[], "triggers":[], "properties":{ "audio_ambience":"", "render":"Overcast", "plane":"AcidPlane", "plane_position":1.5, "kill_position":1.5, "barrier_1_team_1":65.5, "barrier_1_team_2":91.5, "barrier_2_team_1":65.5, "barrier_2_team_2":91.5, "min_fall_height":5, "max_fall_height":25 }, "size":{"x":156,"y":48,"z":108}, "blocks_data": , "colors_data":  } }

See the stuff about the on-the-wire format which follows, although the 16-bit size ints don't show up in this format, it goes straight into the map data, and the colour data is separate.

On-the-wire format
I really don't remember much of this. I've been told it's zlib-compressed. Header or headerless, I'm not sure as my rip was rather bad and I was sent a map in uncompressed form.

After zlib-decompression, there should be 3 little-endian 16-bit ints denoting the map size (lx,ly,lz), followed by 4-byte entries (this is ) in this form (TODO: confirm order):


 * Block type
 * Damage value (so I've been told)
 * Slope OR orientation
 * Team association or something else

I've been told that these should be followed by 1-byte entries of the same dimensions denoting block colour.

Block types
Partially deciphered on my part using one snapshot of Pillerd I was given. Had to "child-friendlify" my notes. I'll give proper notes at a later date.

TODO: 0x04U == some stuff at the border mid 0x07U == stuff occluded but can be seen when you leave the base 0x08U == stairs on some pillar things 0x10U == front of that undeciphered ****ed up slope "lump" on the top of the base 0x11U == wood boxes? 0x12U == back of that undeciphered ****ed up slope "lump" on the top of the base 0x17U == fin things on the back of the bases 0x23U == weird broken **** on base that's OBSCURED IN YOUR SCREENSHOT, KAMEEL

Checked: 0x01U == dirt 0x03U == light rough stone 0x05U == upper dirt grass 0x06U == grass tuft 0x09U == light smooth stone 0x0AU == probably shiny metal ****, definitely unbreakable **** 0x0BU == tree trunks 0x0CU == tree leaves 0x1BU == mid more brickish stone 0x2DU == regular grass 0x2EU == obsidian. there, i said it. 0x33U == flowers grass 0x35U == upper layer of cube surrounding thing

Slopes
Note, if it's not a full block, this field does not denote slopes. For example, lamp posts look like utter crap when treated like regular blocks.

Slope types


 * 0x00 is, of course, no-slope solid.
 * CS = corner solid - more solid than air.
 * CA = corner air - more air than solid.
 * E = edge.

Patterns observed:


 * I have 4 values denoting top CS - these have 1 bit set.
 * I have 8 values denoting CA - these have 4 bits set.
 * I have 12 values denoting E - these have 2 bits set.

Conjecture
Firstly, sum together by bits. + denotes +1, - denotes -1.

====:=||| 0x01: --- 0x02: +-- 0x04: -+- < 0x08: --+ 0x10: ++- < 0x20: -++ < 0x40: +-+ 0x80: +++ <

Take a popcount.


 * If 1, add  to the plane offset.
 * If 4, subtract  from the plane offset.
 * If 3, subtract  from the plane offset (approximate - I don't have an exact value)
 * If 2, add nothing.
 * Other popcounts are undefined.

This works for all slope values which are manually confirmed below. However, other values may be incorrect.

Actual values spotted on a map that I actually managed to make sense of
If you can decipher this mess that I've been using in my shader, congratulations.

00000100 CS case 0x04U: norm = vec3(-1.0, 1.0, -1.0); norig = vec3(0.0, 0.0, 0.0); break; 00010000 CS case 0x10U: norm = vec3( 1.0, 1.0, -1.0); norig = vec3(0.0, 1.0, 0.0); break; 00100000 CS case 0x20U: norm = vec3(-1.0, 1.0,  1.0); norig = vec3(0.0, 1.0, 0.0); break; 10000000 CS case 0x80U: norm = vec3( 1.0, 1.0,  1.0); norig = vec3(1.0, 1.0, 0.0); break;

v vv v 00000011 E case 0x03U: norm = vec3( 0.0, -1.0, -1.0); break; 00000101 E case 0x05U: norm = vec3(-1.0,  0.0, -1.0); break; 00001001 E case 0x09U: norm = vec3(-1.0, -1.0,  0.0); break; 00010010 E case 0x12U: norm = vec3( 1.0,  0.0, -1.0); break; 00010100 E case 0x14U: norm = vec3( 0.0,  1.0, -1.0); break; 00100100 E case 0x24U: norm = vec3(-1.0,  1.0,  0.0); break; 00101000 E case 0x28U: norm = vec3(-1.0,  0.0,  1.0); break; 01000010 E case 0x42U: norm = vec3( 1.0, -1.0,  0.0); break; 01001000 E case 0x48U: norm = vec3( 0.0, -1.0,  1.0); break; 10010000 E case 0x90U: norm = vec3( 1.0,  1.0,  0.0); break; 10100000 E case 0xA0U: norm = vec3( 0.0,  1.0,  1.0); break; 11000000 E case 0xC0U: norm = vec3( 1.0,  0.0,  1.0); break; ^ ^^ ^

v vv v 00001111 CA case 0x0FU: norm = vec3(-1.0, -1.0, -1.0); norig = vec3(1.0, 1.0, 0.0); break; 00110101 CA case 0x35U: norm = vec3(-1.0, 1.0, -1.0); norig = vec3(1.0, 0.0, 0.0); break; 01010011 CA case 0x53U: norm = vec3( 1.0, -1.0, -1.0); norig = vec3(0.0, 1.0, 0.0); break; 01101001 CA case 0x69U: norm = vec3(-1.0, -1.0, 1.0); norig = vec3(0.0, 1.0, 0.0); break; 10010110 CA case 0x96U: norm = vec3( 1.0, 1.0, -1.0); norig = vec3(1.0, 0.0, 1.0); break; 10101100 CA case 0xACU: norm = vec3(-1.0, 1.0,  1.0); norig = vec3(1.0, 0.0, 1.0); break; 11001010 CA case 0xCAU: norm = vec3( 1.0, -1.0, 1.0); norig = vec3(0.0, 0.0, 0.0); break; 11110000 CA case 0xF0U: norm = vec3( 1.0, 1.0,  1.0); norig = vec3(1.0, 0.0, 0.0); break; ^ ^^ ^