WebCraft icon indicating copy to clipboard operation
WebCraft copied to clipboard

Terrain world for multiplayer

Open TheAio opened this issue 3 years ago • 1 comments

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?

TheAio avatar Feb 09 '22 19:02 TheAio

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;
    }

};


`

p3rlphr33k avatar Feb 25 '22 15:02 p3rlphr33k

Cleaning out my open issues, closing this as resolved as im assuming the above solution works. However i currently have no ability to test it myself.

TheAio avatar Aug 09 '24 10:08 TheAio

@p3rlphr33k can you put it in a code box? its the button to the left of the 1 key ``` three times before and after your code (someone had to tell me too) but this looks cool iv messed with this project a few times over the years this time im looking to really do some work on it because of another project im working on and these snippets are really helpful

develperbayman avatar Aug 16 '24 19:08 develperbayman