added sand to map generator

This commit is contained in:
Perttu Ahola 2011-02-04 14:32:30 +02:00
parent 6545ea12e9
commit 7f2aa30bf2
9 changed files with 315 additions and 100 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -39,6 +39,7 @@ configure_file(
) )
set(common_SRCS set(common_SRCS
noise.cpp
mineral.cpp mineral.cpp
porting.cpp porting.cpp
materials.cpp materials.cpp

@ -32,5 +32,7 @@ typedef core::vector2d<s32> v2s32;
typedef core::vector2d<u32> v2u32; typedef core::vector2d<u32> v2u32;
typedef core::vector2d<f32> v2f32; typedef core::vector2d<f32> v2f32;
typedef unsigned long long u64;
#endif #endif

@ -315,43 +315,45 @@ FEATURE: The map could be generated procedually:
- Simulate rock falling from cliffs when water has removed - Simulate rock falling from cliffs when water has removed
enough solid rock from the bottom enough solid rock from the bottom
Doing now: Doing now (most important at the top):
---------- --------------------------------------
# maybe done # maybe done
* not 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 * Remove all kinds of systems that are made redundant by the new map
generator generator
- Sector heightmaps? At least they should be made redundant. - Sector heightmaps? At least they should be made redundant.
- Sector objects - Sector objects
* Do something about AttributeDatabase/List being too slow * Fix the strange mineral occurences
* Save chunk metadata on disk - Do they appear anymore?
* Change water side textures so that buggy water doesn't look bad
* Make server find the spawning place from the real map data, not from * Make server find the spawning place from the real map data, not from
the heightmap 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 * Make the generator to run in background and not blocking block
placement and transfer 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 * Add some kind of erosion and other stuff that now is possible
* Make client to fetch stuff asynchronously * Make client to fetch stuff asynchronously
- Needs method SyncProcessData - Needs method SyncProcessData
* What is the problem with the server constantly saving one or a few * What is the problem with the server constantly saving one or a few
blocks? List the first saved block, maybe it explains. 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 * 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
====================================================================== ======================================================================

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "voxel.h" #include "voxel.h"
#include "porting.h" #include "porting.h"
#include "mineral.h" #include "mineral.h"
#include "noise.h"
/* /*
Map Map
@ -1731,7 +1732,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp): ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
Map(dout_server), Map(dout_server),
m_heightmap(NULL) m_heightmap(NULL),
m_seed(0)
{ {
//m_chunksize = 64; //m_chunksize = 64;
@ -1740,6 +1742,12 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
//m_chunksize = 4; //m_chunksize = 4;
//m_chunksize = 2; //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 Experimental and debug stuff
*/ */
@ -1979,6 +1987,8 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
dstream<<DTIME<<"Initializing new map."<<std::endl; dstream<<DTIME<<"Initializing new map."<<std::endl;
// Create master heightmap // Create master heightmap
// NOTE: Yes, that is a magic number down there. It specifies
// the size of the internal FixedHeightmaps.
m_heightmap = new UnlimitedHeightmap m_heightmap = new UnlimitedHeightmap
(32, &m_padb); (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) s16 find_ground_level(VoxelManipulator &vmanip, v2s16 p2d)
@ -2053,6 +2063,29 @@ s16 find_ground_level(VoxelManipulator &vmanip, v2s16 p2d)
return y_nodes_min; 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) void make_tree(VoxelManipulator &vmanip, v3s16 p0)
{ {
MapNode treenode(CONTENT_TREE); 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, MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
core::map<v3s16, MapBlock*> &changed_blocks) 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 // Relative values to control amount of stuff in one chunk
u32 relative_area = (u32)sectorpos_base_size*MAP_BLOCKSIZE /*u32 relative_area = (u32)sectorpos_base_size*MAP_BLOCKSIZE
*(u32)sectorpos_base_size*MAP_BLOCKSIZE; *(u32)sectorpos_base_size*MAP_BLOCKSIZE;*/
u32 relative_volume = (u32)sectorpos_base_size*MAP_BLOCKSIZE u32 relative_volume = (u32)sectorpos_base_size*MAP_BLOCKSIZE
*(u32)sectorpos_base_size*MAP_BLOCKSIZE *(u32)sectorpos_base_size*MAP_BLOCKSIZE
*(u32)h_blocks*MAP_BLOCKSIZE; *(u32)h_blocks*MAP_BLOCKSIZE;
@ -2234,12 +2310,6 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
} }
} }
/*
Clear all light emitted
*/
core::map<v3s16, u8> unlight_from;
/* /*
Now we have a big empty area. Now we have a big empty area.
@ -2281,22 +2351,17 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
// Ground height at this point // Ground height at this point
float surface_y_f = 0.0; float surface_y_f = 0.0;
/*
A hack to get the ground height from the sector. // Use perlin noise for ground height
Do this better. surface_y_f = base_rock_level_2d(m_seed, p2d);
*/
// Experimental stuff
{ {
v2s16 sectorpos = getContainerPos(p2d, MAP_BLOCKSIZE); float a = highlands_level_2d(m_seed, p2d);
v2s16 sector_relpos = p2d - sectorpos*MAP_BLOCKSIZE; if(a > surface_y_f)
MapSector *sector = getSectorNoGenerate(sectorpos); surface_y_f = a;
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;
} }
// Convert to integer // Convert to integer
s16 surface_y = (s16)surface_y_f; 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)); stone_obstacle_amount = myrand_range(0, myrand_range(20, 150));
else else
stone_obstacle_amount = myrand_range(0, myrand_range(20, 50)); stone_obstacle_amount = myrand_range(0, myrand_range(20, 50));
//u32 stone_obstacle_amount = //u32 stone_obstacle_amount =
// myrand_range(0, myrand_range(20, myrand_range(80,150))); // 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(0, maxheight_randomized),
myrand_range(5, stone_obstacle_max_size) myrand_range(5, stone_obstacle_max_size)
); );
// Don't make stupid small rectable bumps
if(ob_size.Y < 5)
continue;
/*v2s16 ob_place( /*v2s16 ob_place(
myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1), myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1),
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 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 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++) 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; s16 max_tunnel_diameter = 6;
u16 tunnel_routepoints = 15; u16 tunnel_routepoints = 15;
@ -2519,6 +2592,11 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
s16 route_y_min = 0; s16 route_y_min = 0;
//s16 route_y_max = ar.Y-1; //s16 route_y_max = ar.Y-1;
s16 route_y_max = -of.Y + stone_surface_max_y + max_tunnel_diameter/2; 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); route_y_max = rangelim(route_y_max, 0, ar.Y-1);
if(bruise_surface) if(bruise_surface)
@ -2773,7 +2851,8 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
Add mud to the central chunk 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 x=0; x<sectorpos_base_size*MAP_BLOCKSIZE; x++)
for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++) for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++)
@ -2781,6 +2860,11 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
// Node position in 2d // Node position in 2d
v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z); 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 // Find ground level
s16 surface_y = find_ground_level(vmanip, p2d); s16 surface_y = find_ground_level(vmanip, p2d);
@ -2806,11 +2890,12 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y)); u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
for(s16 y=y_start; y<=y_nodes_max; y++) for(s16 y=y_start; y<=y_nodes_max; y++)
{ {
if(mudcount >= mud_add_amount)
break;
MapNode &n = vmanip.m_data[i]; MapNode &n = vmanip.m_data[i];
n.d = CONTENT_MUD; n.d = CONTENT_MUD;
mudcount++; mudcount++;
if(mudcount >= mud_add_amount)
break;
vmanip.m_area.add_y(em, i, 1); vmanip.m_area.add_y(em, i, 1);
} }
@ -2828,7 +2913,7 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
*/ */
// Iterate a few times // 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; /*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)); u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
s16 y=y_nodes_max; s16 y=y_nodes_max;
for(;;) for(;; y--)
{ {
MapNode *n = NULL; MapNode *n = NULL;
// Find mud // Find mud
@ -2890,6 +2975,20 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS) if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS)
continue;*/ 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 // Make it exactly mud
n->d = CONTENT_MUD; n->d = CONTENT_MUD;
@ -2904,13 +3003,14 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
}; };
// Theck that upper is air or doesn't exist. // Theck that upper is air or doesn't exist.
// Only drop mud if upper doesn't contain anything that // Cancel dropping if upper keeps it in place
// would keep the mud in place.
u32 i3 = i; u32 i3 = i;
vmanip.m_area.add_y(em, i3, 1); vmanip.m_area.add_y(em, i3, 1);
if(vmanip.m_area.contains(i3) == false if(vmanip.m_area.contains(i3) == true
|| content_walkable(vmanip.m_data[i3].d) == false) && content_walkable(vmanip.m_data[i3].d) == true)
{ {
continue;
}
// Drop mud on side // Drop mud on side
@ -2956,10 +3056,6 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
break; break;
} }
} }
// Continue from next y
y--;
}
} }
} }
@ -3054,6 +3150,67 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
} // Aging loop } // 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 // 1ms @cs=8
//TimeTaker timer1("plant trees"); //TimeTaker timer1("plant trees");
@ -3062,9 +3219,56 @@ MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos,
Plant some trees Plant some trees
*/ */
{ {
u32 tree_max = relative_area / 60; // Divide area into parts
s16 div = 8;
u32 count = myrand_range(0, tree_max); 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++) for(u32 i=0; i<count; i++)
{ {
s16 x = myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1); 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); v3s16 p(x,y+1,z);
// Make a tree // Make a tree
make_tree(vmanip, p); make_tree(vmanip, p);
} }*/
} }
}//timer1 }//timer1

@ -538,6 +538,9 @@ private:
MapParams m_params; MapParams m_params;
PointAttributeDatabase m_padb; PointAttributeDatabase m_padb;
// Seed used for all kinds of randomness
u64 m_seed;
std::string m_savedir; std::string m_savedir;
bool m_map_saving_enabled; bool m_map_saving_enabled;

@ -1,6 +1,6 @@
/* /*
Minetest-c55 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 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 it under the terms of the GNU General Public License as published by

@ -57,6 +57,9 @@ void initializeMaterialProperties()
g_material_properties[CONTENT_WOOD].setDiggingProperties("", g_material_properties[CONTENT_WOOD].setDiggingProperties("",
DiggingProperties(true, 1.0, 0)); DiggingProperties(true, 1.0, 0));
g_material_properties[CONTENT_SAND].setDiggingProperties("",
DiggingProperties(true, 0.5, 0));
/* /*
Add MesePick to everything Add MesePick to everything
*/ */