aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/common_irrlicht.h2
-rw-r--r--src/main.cpp42
-rw-r--r--src/map.cpp362
-rw-r--r--src/map.h3
-rw-r--r--src/mapnode.h2
-rw-r--r--src/materials.cpp3
7 files changed, 315 insertions, 100 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d027c7cd9..1c5e5f6e1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -39,6 +39,7 @@ configure_file(
)
set(common_SRCS
+ noise.cpp
mineral.cpp
porting.cpp
materials.cpp
diff --git a/src/common_irrlicht.h b/src/common_irrlicht.h
index 9f28687d9..88e74878b 100644
--- a/src/common_irrlicht.h
+++ b/src/common_irrlicht.h
@@ -32,5 +32,7 @@ typedef core::vector2d<s32> v2s32;
typedef core::vector2d<u32> v2u32;
typedef core::vector2d<f32> v2f32;
+typedef unsigned long long u64;
+
#endif
diff --git a/src/main.cpp b/src/main.cpp
index 57ebfce1a..a97d1f45d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -315,43 +315,45 @@ FEATURE: The map could be generated procedually:
- Simulate rock falling from cliffs when water has removed
enough solid rock from the bottom
-Doing now:
-----------
+Doing now (most important at the top):
+--------------------------------------
# maybe done
* not done
+* Perlin noise stuff sucks in heightmap generation, fix it
+* Create perlin noise functions and use them to get natural randomness
+ in everything. No need for attributes or fractal terrain.
+* Do something about AttributeDatabase/List being too slow
+ - Remove it
+* Save chunk metadata on disk
* Remove all kinds of systems that are made redundant by the new map
generator
- Sector heightmaps? At least they should be made redundant.
- Sector objects
-* Do something about AttributeDatabase/List being too slow
-* Save chunk metadata on disk
-* Change water side textures so that buggy water doesn't look bad
+* Fix the strange mineral occurences
+ - Do they appear anymore?
* Make server find the spawning place from the real map data, not from
the heightmap
-* only_from_disk doesn't work that well anymore
+ - But the changing borders of chunk have to be avoided, because
+ there is time to generate only one chunk.
+* only_from_disk might not work anymore - check and fix it.
* Make the generator to run in background and not blocking block
placement and transfer
-* Fix the strange mineral occurences
-* When the map is generated and a place is found for the player, the
- first chunk is actually still volatile and will have stuff still
- changed after spawning, which creates a lot of glitches.
- - This is partly fixed by now allowing only 2-sector deeep
- modification of volatile chunks. But it should still be fixed?
- - How about checking that the neighbors are fully generated too and
- generate them when the middle piece is needed
- - This is very slow
- - How about just enabling changed_blocks properly
- - This is probably a good idea
- - The server has to make sure the spawn point is not at the
- changing borders of a chunk
* Add some kind of erosion and other stuff that now is possible
* Make client to fetch stuff asynchronously
- Needs method SyncProcessData
* What is the problem with the server constantly saving one or a few
blocks? List the first saved block, maybe it explains.
+ - Does it still do this?
* Water doesn't start flowing after map generation like it should
-* Better water generation
+ - Are there still problems?
+* Better water generation (spread it to underwater caverns)
+* When generating a chunk and the neighboring chunk doesn't have mud
+ and stuff yet and the ground is fairly flat, the mud will flow to
+ the other chunk making nasty straight walls when the other chunk
+ is generated. Fix it.
+* Save map seed to a metafile (with version information)
+ - Remove master heightmap
======================================================================
diff --git a/src/map.cpp b/src/map.cpp
index 3a3169200..117435565 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "voxel.h"
#include "porting.h"
#include "mineral.h"
+#include "noise.h"
/*
Map
@@ -1731,7 +1732,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
Map(dout_server),
- m_heightmap(NULL)
+ m_heightmap(NULL),
+ m_seed(0)
{
//m_chunksize = 64;
@@ -1739,6 +1741,12 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
m_chunksize = 8;
//m_chunksize = 4;
//m_chunksize = 2;
+
+ // TODO: Save to and load from a file
+ m_seed = (((u64)myrand()<<0)%0x7fff)
+ + (((u64)myrand()<<16)%0x7fff)
+ + (((u64)myrand()<<32)%0x7fff)
+ + (((u64)myrand()<<48)%0x7fff);
/*
Experimental and debug stuff
@@ -1979,6 +1987,8 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
dstream<<DTIME<<"Initializing new map."<<std::endl;
// Create master heightmap
+ // NOTE: Yes, that is a magic number down there. It specifies
+ // the size of the internal FixedHeightmaps.
m_heightmap = new UnlimitedHeightmap
(32, &m_padb);
@@ -2029,7 +2039,7 @@ ServerMap::~ServerMap()
}
/*
- Some helper functions
+ Some helper functions for the map generator
*/
s16 find_ground_level(VoxelManipulator &vmanip, v2s16 p2d)
@@ -2053,6 +2063,29 @@ s16 find_ground_level(VoxelManipulator &vmanip, v2s16 p2d)
return y_nodes_min;
}
+s16 find_ground_level_clever(VoxelManipulator &vmanip, v2s16 p2d)
+{
+ v3s16 em = vmanip.m_area.getExtent();
+ s16 y_nodes_max = vmanip.m_area.MaxEdge.Y;
+ s16 y_nodes_min = vmanip.m_area.MinEdge.Y;
+ u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
+ s16 y;
+ for(y=y_nodes_max; y>=y_nodes_min; y--)
+ {
+ MapNode &n = vmanip.m_data[i];
+ if(content_walkable(n.d)
+ && n.d != CONTENT_TREE
+ && n.d != CONTENT_LEAVES)
+ break;
+
+ vmanip.m_area.add_y(em, i, -1);
+ }
+ if(y >= y_nodes_min)
+ return y;
+ else
+ return y_nodes_min;
+}
+
void make_tree(VoxelManipulator &vmanip, v3s16 p0)
{
MapNode treenode(CONTENT_TREE);
@@ -2123,6 +2156,49 @@ void make_tree(VoxelManipulator &vmanip, v3s16 p0)
}
}
+/*
+ Noise functions. Make sure seed is mangled differently in each one.
+*/
+
+// Amount of trees per area in nodes
+double tree_amount_2d(u64 seed, v2s16 p)
+{
+ double noise = noise2d_perlin(
+ 0.5+(float)p.X/500, 0.5+(float)p.Y/500,
+ seed+2, 4, 0.55);
+ double zeroval = -0.3;
+ if(noise < zeroval)
+ return 0;
+ else
+ return 0.05 * (noise-zeroval) / (0.5-zeroval);
+}
+
+double base_rock_level_2d(u64 seed, v2s16 p)
+{
+ return -4. + 25. * noise2d_perlin(
+ 0.5+(float)p.X/500., 0.5+(float)p.Y/500.,
+ seed, 6, 0.6);
+}
+
+double highlands_level_2d(u64 seed, v2s16 p)
+{
+ double a = noise2d_perlin(
+ 0.5+(float)p.X/1000., 0.5+(float)p.Y/1000.,
+ seed-359, 6, 0.55);
+ if(a > 0.2)
+ {
+ return 35. + 10. * noise2d_perlin(
+ 0.5+(float)p.X/500., 0.5+(float)p.Y/500.,
+ seed+85039, 6, 0.55);
+ }
+ else
+ return -100000;
+}
+
+/*
+ This is the main map generation method
+*/
+
MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
core::map<v3s16, MapBlock*> &changed_blocks)
{
@@ -2185,8 +2261,8 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
);
// Relative values to control amount of stuff in one chunk
- u32 relative_area = (u32)sectorpos_base_size*MAP_BLOCKSIZE
- *(u32)sectorpos_base_size*MAP_BLOCKSIZE;
+ /*u32 relative_area = (u32)sectorpos_base_size*MAP_BLOCKSIZE
+ *(u32)sectorpos_base_size*MAP_BLOCKSIZE;*/
u32 relative_volume = (u32)sectorpos_base_size*MAP_BLOCKSIZE
*(u32)sectorpos_base_size*MAP_BLOCKSIZE
*(u32)h_blocks*MAP_BLOCKSIZE;
@@ -2235,12 +2311,6 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
}
/*
- Clear all light emitted
- */
-
- core::map<v3s16, u8> unlight_from;
-
- /*
Now we have a big empty area.
Make a ManualMapVoxelManipulator that contains this and the
@@ -2281,22 +2351,17 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
// Ground height at this point
float surface_y_f = 0.0;
- /*
- A hack to get the ground height from the sector.
- Do this better.
- */
+
+ // Use perlin noise for ground height
+ surface_y_f = base_rock_level_2d(m_seed, p2d);
+
+ // Experimental stuff
{
- v2s16 sectorpos = getContainerPos(p2d, MAP_BLOCKSIZE);
- v2s16 sector_relpos = p2d - sectorpos*MAP_BLOCKSIZE;
- MapSector *sector = getSectorNoGenerate(sectorpos);
- assert(sector);
- float h = sector->getGroundHeight(sector_relpos);
- if(h > GROUNDHEIGHT_VALID_MINVALUE)
- surface_y_f = h;
- else
- dstream<<"WARNING: "<<__FUNCTION_NAME
- <<": sector->getGroundHeight returned bad height"<<std::endl;
+ float a = highlands_level_2d(m_seed, p2d);
+ if(a > surface_y_f)
+ surface_y_f = a;
}
+
// Convert to integer
s16 surface_y = (s16)surface_y_f;
@@ -2327,6 +2392,7 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
stone_obstacle_amount = myrand_range(0, myrand_range(20, 150));
else
stone_obstacle_amount = myrand_range(0, myrand_range(20, 50));
+
//u32 stone_obstacle_amount =
// myrand_range(0, myrand_range(20, myrand_range(80,150)));
@@ -2364,6 +2430,11 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
myrand_range(0, maxheight_randomized),
myrand_range(5, stone_obstacle_max_size)
);
+
+ // Don't make stupid small rectable bumps
+ if(ob_size.Y < 5)
+ continue;
+
/*v2s16 ob_place(
myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1),
myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1)
@@ -2477,11 +2548,13 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
/*
Make dungeons
*/
- u32 dungeons_count = relative_volume / 200000;
+ u32 dungeons_count = relative_volume / 400000;
u32 bruises_count = relative_volume * stone_surface_max_y / 200000 / 50;
+ /*u32 dungeons_count = 0;
+ u32 bruises_count = 0;*/
for(u32 jj=0; jj<dungeons_count+bruises_count; jj++)
{
- s16 min_tunnel_diameter = 3;
+ s16 min_tunnel_diameter = 2;
s16 max_tunnel_diameter = 6;
u16 tunnel_routepoints = 15;
@@ -2519,6 +2592,11 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
s16 route_y_min = 0;
//s16 route_y_max = ar.Y-1;
s16 route_y_max = -of.Y + stone_surface_max_y + max_tunnel_diameter/2;
+ if(bruise_surface == false)
+ {
+ // Don't go through surface too often
+ route_y_max -= myrand_range(0, max_tunnel_diameter);
+ }
route_y_max = rangelim(route_y_max, 0, ar.Y-1);
if(bruise_surface)
@@ -2773,7 +2851,8 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
Add mud to the central chunk
*/
- s16 mud_add_amount = myrand_range(1, 5);
+ //s16 mud_add_amount = myrand_range(2, 4);
+ //s16 mud_add_amount = 0;
for(s16 x=0; x<sectorpos_base_size*MAP_BLOCKSIZE; x++)
for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++)
@@ -2781,6 +2860,11 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
// Node position in 2d
v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
+ // Randomize mud amount
+ s16 mud_add_amount = (s16)(3.5 + 2. * noise2d_perlin(
+ 0.5+(float)p2d.X/200, 0.5+(float)p2d.Y/200,
+ m_seed+1, 3, 0.55));
+
// Find ground level
s16 surface_y = find_ground_level(vmanip, p2d);
@@ -2806,12 +2890,13 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
for(s16 y=y_start; y<=y_nodes_max; y++)
{
- MapNode &n = vmanip.m_data[i];
- n.d = CONTENT_MUD;
- mudcount++;
if(mudcount >= mud_add_amount)
break;
+ MapNode &n = vmanip.m_data[i];
+ n.d = CONTENT_MUD;
+ mudcount++;
+
vmanip.m_area.add_y(em, i, 1);
}
}
@@ -2828,7 +2913,7 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
*/
// Iterate a few times
- for(s16 k=0; k<4; k++)
+ for(s16 k=0; k<3; k++)
{
/*for(s16 x=0-max_spread_amount+1;
@@ -2865,7 +2950,7 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
s16 y=y_nodes_max;
- for(;;)
+ for(;; y--)
{
MapNode *n = NULL;
// Find mud
@@ -2890,6 +2975,20 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS)
continue;*/
+ /*
+ Don't flow it if the stuff under it is not mud
+ */
+ {
+ u32 i2 = i;
+ vmanip.m_area.add_y(em, i2, -1);
+ // Cancel if out of area
+ if(vmanip.m_area.contains(i2) == false)
+ continue;
+ MapNode *n2 = &vmanip.m_data[i2];
+ if(n2->d != CONTENT_MUD && n2->d != CONTENT_GRASS)
+ continue;
+ }
+
// Make it exactly mud
n->d = CONTENT_MUD;
@@ -2904,61 +3003,58 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
};
// Theck that upper is air or doesn't exist.
- // Only drop mud if upper doesn't contain anything that
- // would keep the mud in place.
+ // Cancel dropping if upper keeps it in place
u32 i3 = i;
vmanip.m_area.add_y(em, i3, 1);
- if(vmanip.m_area.contains(i3) == false
- || content_walkable(vmanip.m_data[i3].d) == false)
+ if(vmanip.m_area.contains(i3) == true
+ && content_walkable(vmanip.m_data[i3].d) == true)
{
+ continue;
+ }
- // Drop mud on side
-
- for(u32 di=0; di<4; di++)
- {
- v3s16 dirp = dirs4[di];
- u32 i2 = i;
- // Move to side
- vmanip.m_area.add_p(em, i2, dirp);
- // Fail if out of area
- if(vmanip.m_area.contains(i2) == false)
- continue;
- // Check that side is air
- MapNode *n2 = &vmanip.m_data[i2];
- if(content_walkable(n2->d))
- continue;
- // Check that under side is air
+ // Drop mud on side
+
+ for(u32 di=0; di<4; di++)
+ {
+ v3s16 dirp = dirs4[di];
+ u32 i2 = i;
+ // Move to side
+ vmanip.m_area.add_p(em, i2, dirp);
+ // Fail if out of area
+ if(vmanip.m_area.contains(i2) == false)
+ continue;
+ // Check that side is air
+ MapNode *n2 = &vmanip.m_data[i2];
+ if(content_walkable(n2->d))
+ continue;
+ // Check that under side is air
+ vmanip.m_area.add_y(em, i2, -1);
+ // Fail if out of area
+ if(vmanip.m_area.contains(i2) == false)
+ continue;
+ n2 = &vmanip.m_data[i2];
+ if(content_walkable(n2->d))
+ continue;
+ // Loop further down until not air
+ do{
vmanip.m_area.add_y(em, i2, -1);
// Fail if out of area
if(vmanip.m_area.contains(i2) == false)
continue;
n2 = &vmanip.m_data[i2];
- if(content_walkable(n2->d))
- continue;
- // Loop further down until not air
- do{
- vmanip.m_area.add_y(em, i2, -1);
- // Fail if out of area
- if(vmanip.m_area.contains(i2) == false)
- continue;
- n2 = &vmanip.m_data[i2];
- }while(content_walkable(n2->d) == false);
- // Loop one up so that we're in air
- vmanip.m_area.add_y(em, i2, 1);
- n2 = &vmanip.m_data[i2];
+ }while(content_walkable(n2->d) == false);
+ // Loop one up so that we're in air
+ vmanip.m_area.add_y(em, i2, 1);
+ n2 = &vmanip.m_data[i2];
- // Move mud to new place
- *n2 = *n;
- // Set old place to be air
- *n = MapNode(CONTENT_AIR);
+ // Move mud to new place
+ *n2 = *n;
+ // Set old place to be air
+ *n = MapNode(CONTENT_AIR);
- // Done
- break;
- }
+ // Done
+ break;
}
-
- // Continue from next y
- y--;
}
}
@@ -3054,6 +3150,67 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
} // Aging loop
+ {//TimeTaker timer1("convert mud to sand");
+
+ /*
+ Convert mud to sand
+ */
+
+ //s16 mud_add_amount = myrand_range(2, 4);
+ //s16 mud_add_amount = 0;
+
+ /*for(s16 x=0; x<sectorpos_base_size*MAP_BLOCKSIZE; x++)
+ for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++)*/
+ for(s16 x=0-max_spread_amount+1;
+ x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount-1;
+ x++)
+ for(s16 z=0-max_spread_amount+1;
+ z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount-1;
+ z++)
+ {
+ // Node position in 2d
+ v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
+
+ // Determine whether to have sand here
+ double sandnoise = noise2d_perlin(
+ 0.5+(float)p2d.X/500, 0.5+(float)p2d.Y/500,
+ m_seed+59420, 3, 0.50);
+
+ bool have_sand = (sandnoise > 0.0);
+
+ if(have_sand == false)
+ continue;
+
+ // Find ground level
+ s16 surface_y = find_ground_level_clever(vmanip, p2d);
+
+ if(surface_y > WATER_LEVEL + 2)
+ continue;
+
+ {
+ v3s16 em = vmanip.m_area.getExtent();
+ s16 y_start = surface_y;
+ u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
+ u32 not_sand_counter = 0;
+ for(s16 y=y_start; y>=y_nodes_min; y--)
+ {
+ MapNode *n = &vmanip.m_data[i];
+ if(n->d == CONTENT_MUD || n->d == CONTENT_GRASS)
+ n->d = CONTENT_SAND;
+ else
+ {
+ not_sand_counter++;
+ if(not_sand_counter > 3)
+ break;
+ }
+
+ vmanip.m_area.add_y(em, i, -1);
+ }
+ }
+
+ }
+
+ }//timer1
{
// 1ms @cs=8
//TimeTaker timer1("plant trees");
@@ -3062,9 +3219,56 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
Plant some trees
*/
{
- u32 tree_max = relative_area / 60;
-
- u32 count = myrand_range(0, tree_max);
+ // Divide area into parts
+ s16 div = 8;
+ s16 sidelen = sectorpos_base_size*MAP_BLOCKSIZE / div;
+ double area = sidelen * sidelen;
+ for(s16 x0=0; x0<div; x0++)
+ for(s16 z0=0; z0<div; z0++)
+ {
+ // Center position of part of division
+ v2s16 p2d_center(
+ sectorpos_base.X*MAP_BLOCKSIZE + sidelen/2 + sidelen*x0,
+ sectorpos_base.Y*MAP_BLOCKSIZE + sidelen/2 + sidelen*z0
+ );
+ // Minimum edge of part of division
+ v2s16 p2d_min(
+ sectorpos_base.X*MAP_BLOCKSIZE + sidelen*x0,
+ sectorpos_base.Y*MAP_BLOCKSIZE + sidelen*z0
+ );
+ // Maximum edge of part of division
+ v2s16 p2d_max(
+ sectorpos_base.X*MAP_BLOCKSIZE + sidelen + sidelen*x0 - 1,
+ sectorpos_base.Y*MAP_BLOCKSIZE + sidelen + sidelen*z0 - 1
+ );
+ // Amount of trees
+ u32 tree_count = area * tree_amount_2d(m_seed, p2d_center);
+ // Put trees in random places on part of division
+ for(u32 i=0; i<tree_count; i++)
+ {
+ s16 x = myrand_range(p2d_min.X, p2d_max.X);
+ s16 z = myrand_range(p2d_min.Y, p2d_max.Y);
+ s16 y = find_ground_level(vmanip, v2s16(x,z));
+ // Don't make a tree under water level
+ if(y < WATER_LEVEL)
+ continue;
+ v3s16 p(x,y,z);
+ /*
+ Trees grow only on mud and grass
+ */
+ {
+ u32 i = vmanip.m_area.index(v3s16(p));
+ MapNode *n = &vmanip.m_data[i];
+ if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS)
+ continue;
+ }
+ p.Y++;
+ // Make a tree
+ make_tree(vmanip, p);
+ }
+ }
+ /*u32 tree_max = relative_area / 60;
+ //u32 count = myrand_range(0, tree_max);
for(u32 i=0; i<count; i++)
{
s16 x = myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1);
@@ -3078,7 +3282,7 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
v3s16 p(x,y+1,z);
// Make a tree
make_tree(vmanip, p);
- }
+ }*/
}
}//timer1
diff --git a/src/map.h b/src/map.h
index 048aeb9b6..4af529c21 100644
--- a/src/map.h
+++ b/src/map.h
@@ -537,6 +537,9 @@ private:
UnlimitedHeightmap *m_heightmap;
MapParams m_params;
PointAttributeDatabase m_padb;
+
+ // Seed used for all kinds of randomness
+ u64 m_seed;
std::string m_savedir;
bool m_map_saving_enabled;
diff --git a/src/mapnode.h b/src/mapnode.h
index 5392e7866..fcbb80fd1 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -1,6 +1,6 @@
/*
Minetest-c55
-Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/materials.cpp b/src/materials.cpp
index bc39619fc..7f1ba18bd 100644
--- a/src/materials.cpp
+++ b/src/materials.cpp
@@ -57,6 +57,9 @@ void initializeMaterialProperties()
g_material_properties[CONTENT_WOOD].setDiggingProperties("",
DiggingProperties(true, 1.0, 0));
+ g_material_properties[CONTENT_SAND].setDiggingProperties("",
+ DiggingProperties(true, 0.5, 0));
+
/*
Add MesePick to everything
*/