Overv / WebCraft

Minecraft clone written in Javascript.
zlib License
386 stars 179 forks source link

Terrain world for multiplayer #58

Open TheAio opened 2 years ago

TheAio commented 2 years ago

Hello i have a server and this old code is still working great with a few updates. However i would like to implement terrain generation (currently everything is in a flat world)

I saw a function for creating terrain worlds but i dont really understand how to use it, simply replacing world.createFlatWorld with world.createFromString does not appear to work. Could someone help me?

p3rlphr33k commented 2 years ago

Here are the functions I am using to generate random terrain in webcraft. You need to update the world.js file:

world.js `// ========================================== // World container // // This class contains the elements that make up the game world. // Other modules retrieve information from the world or alter it // using this class. // ==========================================

// Constructor( sx, sy, sz ) // // Creates a new world container with the specified world size. // Up and down should always be aligned with the Z-direction. // // sx - World size in the X-direction. // sy - World size in the Y-direction. // sz - World size in the Z-direction.

//function World( sx, sy, sz ) function World(sx, sy, sz, roughness, smoothAmount, smoothAmt) { // Initialise world array

this.blocks = new Array( sx );
for ( var x = 0; x < sx; x++ )
{
    this.blocks[x] = new Array( sy );
    for ( var y = 0; y < sy; y++ )
    {
        this.blocks[x][y] = new Array( sz );
    }
}

// Set all blocks to air 
for(var x = 0; x < sx;x++){
    for(var y = 0; y < sy;y++){
        for(var z = 0; z < sz;z++){
            this.blocks[x][y][z] = BLOCK.AIR;
        }
    }
}

this.sx = sx;
this.sy = sy;
this.sz = sz;

this.roughness = roughness;
this.smoothAmount = smoothAmount;
this.smoothAmt = smoothAmt;

this.players = {};

}

// createFlatWorld() // // Sets up the world so that the bottom half is filled with dirt // and the top half with air.

World.prototype.createFlatWorld = function( height ) { this.spawnPoint = new Vector( this.sx / 2 + 0.5, this.sy / 2 + 0.5, height );

for ( var x = 0; x < this.sx; x++ )
for ( var y = 0; y < this.sy; y++ )
for ( var z = 0; z < this.sz; z++ )
this.blocks[x][y][z] = z < height ? BLOCK.DIRT : BLOCK.AIR;

}

World.prototype.getPower = function(a,b){ if(b == 0)return 1; var d = a; for(var c = 1;c < b;c++)a = a*d; return a; }

World.prototype.getPowerOfTwo = function(a,b){ if(a<b)a = b; for(b = 0;this.getPower(2, b) <= a;b++); return b; }

World.prototype.createWorld = function() { var map = this.generate(this.getPower(2, this.getPowerOfTwo(this.sx, this.sy)), this.roughness, this.smoothAmount, this.smoothAmt);

for ( var x = 0; x < this.sx; x++ ){
    for ( var y = 0; y < this.sy; y++ ){
        this.blocks[x][y][0] = BLOCK.BEDROCK;
        var pointHeight = map[x][y] * (this.sz - 10);
        for(var z = 1; z < this.sz;z++){

            if(z < pointHeight){
                if(!(z < pointHeight - 4))this.blocks[x][y][z] = BLOCK.DIRT;

                else if(z < pointHeight - 3){

                    var random = Math.random() * 100;
                    if(random < 1){
                        this.blocks[x][y][z] = BLOCK.DIAMOND_ORE;
                    }
                    else if(random < 3){
                        this.blocks[x][y][z] = BLOCK.GOLD_ORE;
                    }
                    else if(random < 6){
                        this.blocks[x][y][z] = BLOCK.REDSTONE_ORE;
                    }
                    else if(random < 14){
                        this.blocks[x][y][z] = BLOCK.IRON_ORE;
                    }
                    else if(random < 24){
                        this.blocks[x][y][z] = BLOCK.COAL_ORE;
                    }
                    else this.blocks[x][y][z] = BLOCK.CONCRETE;
                }
                else { 
                    if(z < 45) { this.blocks[x][y][z] = BLOCK.DIRT; }
                    if(z > 45) { this.blocks[x][y][z] = BLOCK.SNOW; }
                }

            }

            else if(z < 10 && z > 7){
                this.blocks[x][y][z] = BLOCK.WATER;
            }
            else if (z < 14) {

            }
            else if(y != this.sy/2 && x != this.sx/2 && ( z < pointHeight + 1 && z > pointHeight - 1) && (x > 2 && x < this.sx - 2) && (y > 2 && y < this.sy - 2)){
                var random = Math.random() * 100;
                if(random < 1){
                    this.createTree(x,y,z,5,3,2);
                }
            }
        }
    }
}
this.spawnPoint = new Vector( this.sx/2 + 0.5, this.sy/2 + 0.5, map[this.sx/2][this.sy/2] * (this.sz + 10));

};

World.prototype.createTree = function(x,y,z,trunkHeight,leavesHeight,range) { for(iz = 0;iz < trunkHeight;iz++){ this.blocks[x][y][z + iz] = BLOCK.WOOD; }

for(var ix = -1*range;ix <= range;ix++){
    for(var iy = -1*range;iy <= range;iy++){
        for(iz = 0;iz < leavesHeight;iz++){
            if(!((Math.abs(iy) == range) && (Math.abs(ix) == range) && (iz == (leavesHeight - 1))))
            this.setBlock(x + ix, y + iy, z + iz + trunkHeight, BLOCK.LEAVES);
        }
    }
}

};

// createFromString( str ) // // Creates a world from a string representation. // This is the opposite of toNetworkString(). // // NOTE: The world must have already been created // with the appropriate size!

World.prototype.createFromString = function( str ) { var i = 0;

for ( var x = 0; x < this.sx; x++ ) {
    for ( var y = 0; y < this.sy; y++ ) {
        for ( var z = 0; z < this.sz; z++ ) {
            this.blocks[x][y][z] = BLOCK.fromId( str.charCodeAt( i ) - 97 );
            i = i + 1;
        }
    }
}

}

// getBlock( x, y, z ) // // Get the type of the block at the specified position. // Mostly for neatness, since accessing the array // directly is easier and faster.

World.prototype.getBlock = function( x, y, z ) { if ( x < 0 || y < 0 || z < 0 || x > this.sx - 1 || y > this.sy - 1 || z > this.sz - 1 ) return BLOCK.AIR; return this.blocks[x][y][z]; }

// setBlock( x, y, z )

World.prototype.setBlock = function( x, y, z, type ) { if(this.blocks[x][y][z]) { this.blocks[x][y][z] = type; //console.log("renderer: "+this.renderer); if ( this.renderer != null ) this.renderer.onBlockChanged( x, y, z ); } }

// toNetworkString() // // Returns a string representation of this world.

World.prototype.toNetworkString = function() { var blockArray = [];

for ( var x = 0; x < this.sx; x++ )
    for ( var y = 0; y < this.sy; y++ )
        for ( var z = 0; z < this.sz; z++ )
            blockArray.push( String.fromCharCode( 97 + this.blocks[x][y][z].id ) );

return blockArray.join( "" );

}

// Export to node.js if ( typeof( exports ) != "undefined" ) { // loadFromFile( filename ) // // Load a world from a file previously saved with saveToFile(). // The world must have already been allocated with the // appropriate dimensions.

World.prototype.loadFromFile = function( filename )
{
    var fs = require( "fs" );
    try {
        fs.lstatSync( filename );
        var data = fs.readFileSync( filename, "utf8" ).split( "," );
        this.createFromString( data[3] );
        this.spawnPoint = new Vector( parseInt( data[0] ), parseInt( data[1] ), parseInt( data[2] ) );
        return true;
    } catch ( e ) {
        return false;
    }
}

// saveToFile( filename )
//
// Saves a world and the spawn point to a file.
// The world can be loaded from it afterwards with loadFromFile().

World.prototype.saveToFile = function( filename )
{
    console.log( "Saved world to file: " + filename );
    var data = this.spawnPoint.x + "," + this.spawnPoint.y + "," + this.spawnPoint.z + "," + this.toNetworkString();
    require( "fs" ).writeFileSync( filename, data );    
}

exports.World = World;

}

/////////////////////////////////////////////////////////////////////////////////////

World.prototype.generate = function(mapSize, roughness, smoothAmount, smoothAmt){ var map; map = this.generateMapTerrain(mapSize, roughness); for(var i = 0; i < smoothAmount; i++){ map = smooth(map, mapSize,smoothAmt); }

function round(n)
{
  if (n-(parseInt(n, 10)) >= 0.5){
    return parseInt(n, 10)+1;
  }else{
    return parseInt(n, 10);
  }
}

function smooth(data, size, amt) {
    /* Rows, left to right */
    for (var x = 1; x < size; x++){
    for (var z = 0; z < size; z++){
        data[x][z] = data[x - 1][z] * (1 - amt) + data[x][z] * amt;
    }
    }

    /* Rows, right to left*/
    for (x = size - 2; x < -1; x--){
    for (z = 0; z < size; z++){
        data[x][z] = data[x + 1][z] * (1 - amt) + data[x][z] * amt;
    }
    }

    /* Columns, bottom to top */
    for (x = 0; x < size; x++){
    for (z = 1; z < size; z++){
        data[x][z] = data[x][z - 1] * (1 - amt) + data[x][z] * amt;
    }
    }

    /* Columns, top to bottom */
    for (x = 0; x < size; x++){
    for (z = size; z < -1; z--){
        data[x][z] = data[x][z + 1] * (1 - amt) + data[x][z] * amt;
    }
    }

    return data;
  }

return map; };

World.prototype.generateMapTerrain = function(mapSize, roughness) { var map = create2DArray(mapSize+1); startDisplacement(map, mapSize); return map;

function create2DArray(d1) {
    var x = new Array(d1),
    i = 0,
    j = 0;

    for (i = 0; i < d1; i += 1) {
        x[i] = new Array(d1);
    }

    for (i=0; i < d1; i += 1) {
        for (j = 0; j < d1; j += 1) {
            x[i][j] = 0;
        }
    }

    return x;
}

// Starts off the map generation, seeds the first 4 corners
function startDisplacement(map,mapSize){
    var tr, tl, t, br, bl, b, r, l, center;

    // top left
    map[0][0] = Math.random(1.0);
    tl = map[0][0];

    // bottom left
    map[0][mapSize] = Math.random(1.0);
    bl = map[0][mapSize];

    // top right
    map[mapSize][0] = Math.random(1.0);
    tr = map[mapSize][0];

    // bottom right
    map[mapSize][mapSize] = Math.random(1.0);
    br = map[mapSize][mapSize];

    // Center
    map[mapSize/2][mapSize/2] = map[0][0] + map[0][mapSize] + map[mapSize][0] + map[mapSize][mapSize] / 4;
    map[mapSize/2][mapSize/2] = normalize(map[mapSize/2][mapSize/2]);
    center = map[mapSize/2][mapSize/2];

    /* Non wrapping terrain */
    map[mapSize/2][mapSize] = bl + br + center / 3;
    map[mapSize/2][0] = tl + tr + center / 3;
    map[mapSize][mapSize/2] = tr + br + center / 3;
    map[0][mapSize/2] = tl + bl + center / 3;

    // Call displacment
    midpointDisplacment(mapSize);
}

// Workhorse of the terrain generation.
function midpointDisplacment(mSize){
    var newSize = mSize/2,
        top, topRight, topLeft, bottom, bottomLeft, bottomRight, right, left, center,
        x = 0, y = 0,
        i = 0, j = 0;

    if (newSize > 1 && newSize > 1){
        for(i = newSize; i <= mapSize; i += newSize){
            for(j = newSize; j <= mapSize; j += newSize){
                x = i - (newSize / 2);
                y = j - (newSize / 2);

                topLeft = map[i - newSize][j - newSize];
                topRight = map[i][j - newSize];
                bottomLeft = map[i - newSize][j];
                bottomRight = map[i][j];

                // Center
                map[x][y] = (topLeft + topRight + bottomLeft + bottomRight) / 4 + displace(mSize); ///tu
                map[x][y] = normalize(map[x][y]);
                center = map[x][y];

                // Top
                if(j - (newSize * 2) + (newSize / 2) > 0){
                    map[x][j - newSize] = (topLeft + topRight + center + map[x][j - mSize + (newSize/2)]) / 4 + displace(mSize);
                }else{
                    map[x][j - newSize] = (topLeft + topRight + center) / 3+ displace(mSize);
                }

                map[x][j - newSize] = normalize(map[x][j - newSize]);

                // Bottom
                if(j + (newSize / 2) < mapSize){
                    map[x][j] = (bottomLeft + bottomRight + center + map[x][j + (newSize/2)]) / 4+ displace(mSize);
                }else{
                    map[x][j] = (bottomLeft + bottomRight + center) / 3+ displace(mSize);
                }

                map[x][j] = normalize(map[x][j]);

                //Right
                if(i + (newSize / 2) < mapSize){
                    map[i][y] = (topRight + bottomRight + center + map[i + (newSize/2)][y]) / 4+ displace(mSize);
                }else{
                    map[i][y] = (topRight + bottomRight + center) / 3+ displace(mSize);
                }

                map[i][y] = normalize(map[i][y]);

                // Left
                if(i - (newSize * 2) + (newSize / 2) > 0){
                    map[i - newSize][y] = (topLeft + bottomLeft + center + map[i - mSize + (newSize/2)][y]) / 4 + displace(mSize);
                }else{
                    map[i - newSize][y] = (topLeft + bottomLeft + center) / 3+ displace(mSize);
                }

                map[i - newSize][y] = normalize(map[i - newSize][y]);
            }
        }
        midpointDisplacment(newSize);
    }
}

function displace(num){
    var max = num / (mapSize + mapSize) * roughness;
    return (Math.random(1.0)- 0.5) * max;
}

function normalize(value){
    if(value > 1)
        value = 1;
    else if(value < 0)
        value = 0;
    return value;
}

}; `

Create generator.js:

`function generate(mapSize, roughness, smoothAmount, smoothAmt){ var map; map = generateMapTerrain(mapSize, roughness); for(var i = 0; i < smoothAmount; i++){ map = smooth(map, mapSize,smoothAmt); }

function round(n)
{
  if (n-(parseInt(n, 10)) >= 0.5){
    return parseInt(n, 10)+1;
  }else{
    return parseInt(n, 10);
  }
}

function smooth(data, size, amt) {
    /* Rows, left to right */
    for (var x = 1; x < size; x++){
    for (var z = 0; z < size; z++){
        data[x][z] = data[x - 1][z] * (1 - amt) + data[x][z] * amt;
    }
    }

    /* Rows, right to left*/
    for (x = size - 2; x < -1; x--){
    for (z = 0; z < size; z++){
        data[x][z] = data[x + 1][z] * (1 - amt) + data[x][z] * amt;
    }
    }

    /* Columns, bottom to top */
    for (x = 0; x < size; x++){
    for (z = 1; z < size; z++){
        data[x][z] = data[x][z - 1] * (1 - amt) + data[x][z] * amt;
    }
    }

    /* Columns, top to bottom */
    for (x = 0; x < size; x++){
    for (z = size; z < -1; z--){
        data[x][z] = data[x][z + 1] * (1 - amt) + data[x][z] * amt;
    }
    }

    return data;
  }

return map; };

function generateMapTerrain(mapSize, roughness) { var map = create2DArray(mapSize+1); startDisplacement(map, mapSize); return map;

function create2DArray(d1) {
    var x = new Array(d1),
    i = 0,
    j = 0;

    for (i = 0; i < d1; i += 1) {
        x[i] = new Array(d1);
    }

    for (i=0; i < d1; i += 1) {
        for (j = 0; j < d1; j += 1) {
            x[i][j] = 0;
        }
    }

    return x;
}

// Starts off the map generation, seeds the first 4 corners
function startDisplacement(map,mapSize){
    var tr, tl, t, br, bl, b, r, l, center;

    // top left
    map[0][0] = Math.random(1.0);
    tl = map[0][0];

    // bottom left
    map[0][mapSize] = Math.random(1.0);
    bl = map[0][mapSize];

    // top right
    map[mapSize][0] = Math.random(1.0);
    tr = map[mapSize][0];

    // bottom right
    map[mapSize][mapSize] = Math.random(1.0);
    br = map[mapSize][mapSize];

    // Center
    map[mapSize/2][mapSize/2] = map[0][0] + map[0][mapSize] + map[mapSize][0] + map[mapSize][mapSize] / 4;
    map[mapSize/2][mapSize/2] = normalize(map[mapSize/2][mapSize/2]);
    center = map[mapSize/2][mapSize/2];

    /* Non wrapping terrain */
    map[mapSize/2][mapSize] = bl + br + center / 3;
    map[mapSize/2][0] = tl + tr + center / 3;
    map[mapSize][mapSize/2] = tr + br + center / 3;
    map[0][mapSize/2] = tl + bl + center / 3;

    // Call displacment
    midpointDisplacment(mapSize);
}

// Workhorse of the terrain generation.
function midpointDisplacment(mSize){
    var newSize = mSize/2,
        top, topRight, topLeft, bottom, bottomLeft, bottomRight, right, left, center,
        x = 0, y = 0,
        i = 0, j = 0;

    if (newSize > 1 && newSize > 1){
        for(i = newSize; i <= mapSize; i += newSize){
            for(j = newSize; j <= mapSize; j += newSize){
                x = i - (newSize / 2);
                y = j - (newSize / 2);

                topLeft = map[i - newSize][j - newSize];
                topRight = map[i][j - newSize];
                bottomLeft = map[i - newSize][j];
                bottomRight = map[i][j];

                // Center
                map[x][y] = (topLeft + topRight + bottomLeft + bottomRight) / 4 + displace(mSize); ///tu
                map[x][y] = normalize(map[x][y]);
                center = map[x][y];

                // Top
                if(j - (newSize * 2) + (newSize / 2) > 0){
                    map[x][j - newSize] = (topLeft + topRight + center + map[x][j - mSize + (newSize/2)]) / 4 + displace(mSize);
                }else{
                    map[x][j - newSize] = (topLeft + topRight + center) / 3+ displace(mSize);
                }

                map[x][j - newSize] = normalize(map[x][j - newSize]);

                // Bottom
                if(j + (newSize / 2) < mapSize){
                    map[x][j] = (bottomLeft + bottomRight + center + map[x][j + (newSize/2)]) / 4+ displace(mSize);
                }else{
                    map[x][j] = (bottomLeft + bottomRight + center) / 3+ displace(mSize);
                }

                map[x][j] = normalize(map[x][j]);

                //Right
                if(i + (newSize / 2) < mapSize){
                    map[i][y] = (topRight + bottomRight + center + map[i + (newSize/2)][y]) / 4+ displace(mSize);
                }else{
                    map[i][y] = (topRight + bottomRight + center) / 3+ displace(mSize);
                }

                map[i][y] = normalize(map[i][y]);

                // Left
                if(i - (newSize * 2) + (newSize / 2) > 0){
                    map[i - newSize][y] = (topLeft + bottomLeft + center + map[i - mSize + (newSize/2)][y]) / 4 + displace(mSize);
                }else{
                    map[i - newSize][y] = (topLeft + bottomLeft + center) / 3+ displace(mSize);
                }

                map[i - newSize][y] = normalize(map[i - newSize][y]);
            }
        }
        midpointDisplacment(newSize);
    }
}

function displace(num){
    var max = num / (mapSize + mapSize) * roughness;
    return (Math.random(1.0)- 0.5) * max;
}

function normalize(value){
    if(value > 1)
        value = 1;
    else if(value < 0)
        value = 0;
    return value;
}

};

`