WebCraft
WebCraft copied to clipboard
Terrain world for multiplayer
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?
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;
}
};
`
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.
@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