📜 ⬆️ ⬇️

Creating a world generator for minecraft

Introduction


I think almost all Habr's readers have heard about minecraft, someone played in a single, someone on one of the many servers, there was even a small server for someone from habrayuserov. After two months of the game I wondered - is it really possible to write my own map generator? As it turned out, it is quite possible to do it in a few days of unhurried googling and coding.

Some technical part

According to the lane wiki project , maps are stored in files of regions (32x32 blocks of 16x16x128 cubes in total, 262,144 square blocks per region), having the following structure:
  1. 4096 bytes containing offsets of chunks (16x16x128 blocks are so called) and their size in 4kb blocks, rounding up, 3 bytes offset, 1 - size
  2. 4096 bytes of timestamps of chunks, 4 bytes each
  3. The remaining space until the end of the file is actually chunk data compressed by Zlib. 4 bytes - size of compressed data, 1 - compression method (default 2, Zlib (RFC1950)), size-1 packed with an NBT structure , that is, the cube container itself
    If the packed data occupies less than an integer number of 4 kb sectors, then the rest of the sector is filled with zeros, because each chunk must begin with an offset, expressed by an integer number of 4096 byte sectors

Language selection

You can implement such a structure in any language, I stopped Delphi 7. Firstly, this is the only language that I know, and secondly, it was on version 7 of the year 4 that I started writing notebooks for Igromania manuals.

Code

Since the data is stored in a compressed form, we need a zlib module.
I used ZlibEx
')
To begin, create a chunk class in which we will later write data
Tchunk = class(TObject) private public Data: tmemorystream; c_data: tmemorystream; c_stream: tzcompressionstream; constructor Create; procedure writeblock(x, y, z, block: integer); overload; procedure writeblock(x, y, z, block, color: integer); overload; procedure compress; end; 


The code for this class is:

 constructor tchunk.Create; begin Data := TMemoryStream.Create; Data.size := 82360; Data.LoadFromFile('data.bin'); c_data := TMemoryStream.Create; c_stream := tzcompressionstream.Create(c_data, zcdefault, 15, 8, zsdefault); end; procedure tchunk.writeblock(x, y, z, block: integer); begin Data.Seek(form1.getoffset(x, y, z) + 16487, 0); Data.Write(block, 1); end; procedure tchunk.compress; var buffer: array [0..82360] of byte; begin c_data.Position := 0; Data.Position := 0; Data.Read(buffer, 82360); c_stream.writebuffer(buffer, 82360); c_stream.Free; c_data.SaveToFile('file' + IntToStr(n)); end; 


The getoffset function returns the required offset using the formula y + ( z *128 + ( x * 128 * 16 ) )
 function tform1.getoffset(x, y, z: integer): integer; begin Result := y + (z * 128 + (x * 128 * 16)); end; 


Add a couple of variables to var:
 chunks:array[0..32] of array[0..32] of tchunk; n: integer=0; 


The procedure for assembling all the chunks in the finished file:
 procedure tform1.SwapEndiannessOfBytes(var Value: cardinal); var tmp: cardinal; i: integer; begin tmp := 0; for i := 0 to sizeof(Value) - 1 do Inc(tmp, ((Value shr (8 * i)) and $FF) shl (8 * (sizeof(Value) - i - 1))); Value := tmp; end; procedure tform1.generatefile; var fileoffset: integer; time, compressiontype, counter: integer; filename: string; regionfile: tfilestream; tmp: cardinal; size: integer; n_x, n_z: integer; bu: array[0..99999] of byte; n: integer; roundedsize: integer; neededsize: integer; d: byte; begin fileoffset := 2; time := $d8de2f4e; compressiontype := $02; filename := GetVar('Appdata') + '\.minecraft\saves\NewWorld\region\r.0.0.mcr'; regionfile := tfilestream.Create(filename, fmcreate); n := 0; for n_x := 0 to 31 do for n_z := 0 to 31 do begin chunks[n_x][n_z].compress; roundedsize := ((chunks[n_x][n_z].c_data.Size) div 4096); if (((chunks[n_x][n_z].c_data.Size) mod 4096) > 0) then Inc(roundedsize); regionfile.seek((4 * ((n_x mod 32) + (n_z mod 32) * 32)), 0); tmp := fileoffset; SwapEndiannessOfBytes(tmp); tmp := tmp shr 8; regionfile.Write(tmp, 4); regionfile.seek(4 * ((n_x mod 32) + (n_z mod 32) * 32) + 3, 0); regionfile.Write(roundedsize, 1); size := chunks[n_x][n_z].c_data.Size + 1; regionfile.seek(fileoffset * 4096, 0); tmp := size; SwapEndiannessOfBytes(tmp); regionfile.Write(tmp, 4); regionfile.Write(compressiontype, 1); chunks[n_x][n_z].c_data.Position := 0; chunks[n_x][n_z].c_data.readbuffer(bu, chunks[n_x][n_z].c_data.size); regionfile.Writebuffer(bu, chunks[n_x][n_z].c_data.size); regionfile.seek((n) * 4 + 4096, 0); regionfile.Write(time, 4); fileoffset := fileoffset + ((chunks[n_x][n_z].c_data.Size) div 4096); if (((chunks[n_x][n_z].c_data.Size) mod 4096) > 0) then fileoffset := fileoffset + 1; Inc(n); end; neededsize := 4096 * fileoffset - regionfile.Size - 1; regionfile.Seek(regionfile.Size, 0); d := 00; for n := 0 to neededsize do regionfile.Write(d, 1); regionfile.Free; end; 


Everything, now we have a method of record of any block on any coordinate, within the region. If desired, it is easy to repeat the same for other regions, you need lines 10 of the code.

Wrap for writeblock:
 procedure tform1.writeworld(x, y, z, block: integer); var xw, zw: integer; begin xw := (x div 16); zw := (z div 16); chunks[xw][zw].writeblock(x mod 16, y, z mod 16, block); end; 


Generation of the world, its compression and preservation.
 procedure TForm1.Button4Click(Sender: TObject); var x, y, z: integer; xx, zz: integer; image: tbitmap; begin for xx := 0 to 31 do for zz := 0 to 31 do begin chunks[xx][zz] := tchunk.Create; end; image := tbitmap.Create; image.LoadFromFile('image.bmp'); for x:=0 to 127 do for y:=0 to 116 do begin if image.Canvas.Pixels[x,y]=clblack then form1.writeworld(x,117-y,0,49); if image.Canvas.Pixels[x,117-y]=clwhite then form1.writeworld(x,y,0,80); end; form1.generatefile; 


Result:
image
image

You can generate not only pixelart, but arbitrary shapes, everything that can be defined by any formula. For example, a floor in the form of a sinusoid:
image

The project can be downloaded here .

Known bugs:

ToDo:


Update:

References:

Source: https://habr.com/ru/post/125621/


All Articles