#include "mapgen.h"
#include "nodemetadata.h"
-extern "C" {
- #include "sqlite3.h"
-}
/*
SQLite format specification:
- Initially only replaces sectors/ and sectors2/
+
+ If map.sqlite does not exist in the save dir
+ or the block was not found in the database
+ the map will try to load from sectors folder.
+ In either case, map.sqlite will be created
+ and all future saves will save there.
+
+ Structure of map.sqlite:
+ Tables:
+ blocks
+ (PK) INT pos
+ BLOB data
*/
/*
else
{
/*// Turn mud into grass
- if(n.d == CONTENT_MUD)
+ if(n.getContent() == CONTENT_MUD)
{
- n.d = CONTENT_GRASS;
+ n.setContent(CONTENT_GRASS);
block->setNode(relpos, n);
modified_blocks.insert(blockpos, block);
}*/
/*
*/
void Map::addNodeAndUpdate(v3s16 p, MapNode n,
- core::map<v3s16, MapBlock*> &modified_blocks)
+ core::map<v3s16, MapBlock*> &modified_blocks, std::string &player_name)
{
/*PrintInfo(m_dout);
m_dout<<DTIME<<"Map::addNodeAndUpdate(): p=("
/*
If the new node is solid and there is grass below, change it to mud
*/
- if(content_features(n.d).walkable == true)
+ if(content_features(n).walkable == true)
{
try{
MapNode bottomnode = getNode(bottompos);
- if(bottomnode.d == CONTENT_GRASS
- || bottomnode.d == CONTENT_GRASS_FOOTSTEPS)
+ if(bottomnode.getContent() == CONTENT_GRASS
+ || bottomnode.getContent() == CONTENT_GRASS_FOOTSTEPS)
{
- bottomnode.d = CONTENT_MUD;
+ bottomnode.setContent(CONTENT_MUD);
setNode(bottompos, bottomnode);
}
}
If the new node is mud and it is under sunlight, change it
to grass
*/
- if(n.d == CONTENT_MUD && node_under_sunlight)
+ if(n.getContent() == CONTENT_MUD && node_under_sunlight)
{
- n.d = CONTENT_GRASS;
+ n.setContent(CONTENT_GRASS);
}
#endif
If node lets sunlight through and is under sunlight, it has
sunlight too.
*/
- if(node_under_sunlight && content_features(n.d).sunlight_propagates)
+ if(node_under_sunlight && content_features(n).sunlight_propagates)
{
n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
}
Add intial metadata
*/
- NodeMetadata *meta_proto = content_features(n.d).initial_metadata;
+ NodeMetadata *meta_proto = content_features(n).initial_metadata;
if(meta_proto)
{
NodeMetadata *meta = meta_proto->clone();
+ meta->setOwner(player_name);
setNodeMetadata(p, meta);
}
TODO: This could be optimized by mass-unlighting instead
of looping
*/
- if(node_under_sunlight && !content_features(n.d).sunlight_propagates)
+ if(node_under_sunlight && !content_features(n).sunlight_propagates)
{
s16 y = p.Y - 1;
for(;; y--){
v3s16 p2 = p + dirs[i];
MapNode n2 = getNode(p2);
- if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
+ if(content_liquid(n2.getContent()) || n2.getContent() == CONTENT_AIR)
{
m_transforming_liquid.push_back(p2);
}
v3s16 toppos = p + v3s16(0,1,0);
// Node will be replaced with this
- u8 replace_material = CONTENT_AIR;
+ content_t replace_material = CONTENT_AIR;
/*
If there is a node at top and it doesn't have sunlight,
*/
MapNode n;
- n.d = replace_material;
+ n.setContent(replace_material);
setNode(p, n);
for(s32 i=0; i<2; i++)
v3s16 p2 = p + dirs[i];
MapNode n2 = getNode(p2);
- if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
+ if(content_liquid(n2.getContent()) || n2.getContent() == CONTENT_AIR)
{
m_transforming_liquid.push_back(p2);
}
bool succeeded = true;
try{
core::map<v3s16, MapBlock*> modified_blocks;
- addNodeAndUpdate(p, n, modified_blocks);
+ std::string st = std::string("");
+ addNodeAndUpdate(p, n, modified_blocks, st);
// Copy modified_blocks to event
for(core::map<v3s16, MapBlock*>::Iterator
core::map<v2s16, MapSector*>::Iterator si;
+ beginSave();
si = m_sectors.getIterator();
for(; si.atEnd() == false; si++)
{
core::list<MapBlock*> blocks;
sector->getBlocks(blocks);
+
for(core::list<MapBlock*>::Iterator i = blocks.begin();
i != blocks.end(); i++)
{
sector_deletion_queue.push_back(si.getNode()->getKey());
}
}
+ endSave();
// Finally delete the empty sectors
deleteSectors(sector_deletion_queue);
/*if(initial_size != 0)
dstream<<"transformLiquids(): initial_size="<<initial_size<<std::endl;*/
+ // list of nodes that due to viscosity have not reached their max level height
+ UniqueQueue<v3s16> must_reflow;
+
+ // List of MapBlocks that will require a lighting update (due to lava)
+ core::map<v3s16, MapBlock*> lighting_modified_blocks;
+
while(m_transforming_liquid.size() != 0)
{
+ // This should be done here so that it is done when continue is used
+ if(loopcount >= initial_size * 3)
+ break;
+ loopcount++;
+
/*
Get a queued transforming liquid node
*/
v3s16 p0 = m_transforming_liquid.pop_front();
MapNode n0 = getNodeNoEx(p0);
-
+
/*
Collect information about current node
*/
s8 liquid_level = -1;
u8 liquid_kind = CONTENT_IGNORE;
- LiquidType liquid_type = content_features(n0.d).liquid_type;
+ LiquidType liquid_type = content_features(n0.getContent()).liquid_type;
switch (liquid_type) {
case LIQUID_SOURCE:
- liquid_level = 8;
- liquid_kind = content_features(n0.d).liquid_alternative_flowing;
+ liquid_level = LIQUID_LEVEL_SOURCE;
+ liquid_kind = content_features(n0.getContent()).liquid_alternative_flowing;
break;
case LIQUID_FLOWING:
liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
- liquid_kind = n0.d;
+ liquid_kind = n0.getContent();
break;
case LIQUID_NONE:
// if this is an air node, it *could* be transformed into a liquid. otherwise,
// continue with the next node.
- if (n0.d != CONTENT_AIR)
+ if (n0.getContent() != CONTENT_AIR)
continue;
liquid_kind = CONTENT_AIR;
break;
}
-
+
/*
Collect information about the environment
*/
- v3s16 dirs[6] = {
- v3s16( 0, 1, 0), // top
- v3s16( 0,-1, 0), // bottom
- v3s16( 1, 0, 0), // right
- v3s16(-1, 0, 0), // left
- v3s16( 0, 0, 1), // back
- v3s16( 0, 0,-1), // front
- };
+ const v3s16 *dirs = g_6dirs;
NodeNeighbor sources[6]; // surrounding sources
int num_sources = 0;
NodeNeighbor flows[6]; // surrounding flowing liquid nodes
for (u16 i = 0; i < 6; i++) {
NeighborType nt = NEIGHBOR_SAME_LEVEL;
switch (i) {
- case 0:
+ case 1:
nt = NEIGHBOR_UPPER;
break;
- case 1:
+ case 4:
nt = NEIGHBOR_LOWER;
break;
}
v3s16 npos = p0 + dirs[i];
NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
- switch (content_features(nb.n.d).liquid_type) {
+ switch (content_features(nb.n.getContent()).liquid_type) {
case LIQUID_NONE:
- if (nb.n.d == CONTENT_AIR) {
+ if (nb.n.getContent() == CONTENT_AIR) {
airs[num_airs++] = nb;
+ // if the current node is a water source the neighbor
+ // should be enqueded for transformation regardless of whether the
+ // current node changes or not.
+ if (nb.t != NEIGHBOR_UPPER && liquid_type != LIQUID_NONE)
+ m_transforming_liquid.push_back(npos);
// if the current node happens to be a flowing node, it will start to flow down here.
- if (nb.t == NEIGHBOR_LOWER)
+ if (nb.t == NEIGHBOR_LOWER) {
flowing_down = true;
+ }
} else {
neutrals[num_neutrals++] = nb;
}
case LIQUID_SOURCE:
// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
if (liquid_kind == CONTENT_AIR)
- liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
- if (content_features(nb.n.d).liquid_alternative_flowing !=liquid_kind) {
+ liquid_kind = content_features(nb.n.getContent()).liquid_alternative_flowing;
+ if (content_features(nb.n.getContent()).liquid_alternative_flowing !=liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
sources[num_sources++] = nb;
case LIQUID_FLOWING:
// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
if (liquid_kind == CONTENT_AIR)
- liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
- if (content_features(nb.n.d).liquid_alternative_flowing != liquid_kind) {
+ liquid_kind = content_features(nb.n.getContent()).liquid_alternative_flowing;
+ if (content_features(nb.n.getContent()).liquid_alternative_flowing != liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
flows[num_flows++] = nb;
break;
}
}
-
+
/*
decide on the type (and possibly level) of the current node
*/
- u8 new_node_content;
+ content_t new_node_content;
s8 new_node_level = -1;
+ s8 max_node_level = -1;
if (num_sources >= 2 || liquid_type == LIQUID_SOURCE) {
// liquid_kind will be set to either the flowing alternative of the node (if it's a liquid)
// or the flowing alternative of the first of the surrounding sources (if it's air), so
} else if (num_sources == 1 && sources[0].t != NEIGHBOR_LOWER) {
// liquid_kind is set properly, see above
new_node_content = liquid_kind;
- new_node_level = 7;
+ max_node_level = new_node_level = LIQUID_LEVEL_MAX;
} else {
// no surrounding sources, so get the maximum level that can flow into this node
for (u16 i = 0; i < num_flows; i++) {
u8 nb_liquid_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
switch (flows[i].t) {
case NEIGHBOR_UPPER:
- if (nb_liquid_level + WATER_DROP_BOOST > new_node_level) {
- new_node_level = 7;
- if (nb_liquid_level + WATER_DROP_BOOST < 7)
- new_node_level = nb_liquid_level + WATER_DROP_BOOST;
- }
+ if (nb_liquid_level + WATER_DROP_BOOST > max_node_level) {
+ max_node_level = LIQUID_LEVEL_MAX;
+ if (nb_liquid_level + WATER_DROP_BOOST < LIQUID_LEVEL_MAX)
+ max_node_level = nb_liquid_level + WATER_DROP_BOOST;
+ } else if (nb_liquid_level > max_node_level)
+ max_node_level = nb_liquid_level;
break;
case NEIGHBOR_LOWER:
break;
case NEIGHBOR_SAME_LEVEL:
if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK &&
- nb_liquid_level > 0 && nb_liquid_level - 1 > new_node_level) {
- new_node_level = nb_liquid_level - 1;
+ nb_liquid_level > 0 && nb_liquid_level - 1 > max_node_level) {
+ max_node_level = nb_liquid_level - 1;
}
break;
}
}
- // don't flow as far in open terrain - if there isn't at least one adjacent solid block,
- // substract another unit from the resulting water level.
- if (!flowing_down && new_node_level >= 1) {
- bool at_wall = false;
- for (u16 i = 0; i < num_neutrals; i++) {
- if (neutrals[i].t == NEIGHBOR_SAME_LEVEL) {
- at_wall = true;
- break;
- }
- }
- if (!at_wall)
- new_node_level -= 1;
- }
-
+
+ u8 viscosity = content_features(liquid_kind).liquid_viscosity;
+ if (viscosity > 1 && max_node_level != liquid_level) {
+ // amount to gain, limited by viscosity
+ // must be at least 1 in absolute value
+ s8 level_inc = max_node_level - liquid_level;
+ if (level_inc < -viscosity || level_inc > viscosity)
+ new_node_level = liquid_level + level_inc/viscosity;
+ else if (level_inc < 0)
+ new_node_level = liquid_level - 1;
+ else if (level_inc > 0)
+ new_node_level = liquid_level + 1;
+ if (new_node_level != max_node_level)
+ must_reflow.push_back(p0);
+ } else
+ new_node_level = max_node_level;
+
if (new_node_level >= 0)
new_node_content = liquid_kind;
else
new_node_content = CONTENT_AIR;
+
}
-
+
/*
check if anything has changed. if not, just continue with the next node.
*/
- if (new_node_content == n0.d && (content_features(n0.d).liquid_type != LIQUID_FLOWING ||
+ if (new_node_content == n0.getContent() && (content_features(n0.getContent()).liquid_type != LIQUID_FLOWING ||
((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level &&
((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)
== flowing_down)))
continue;
-
-
+
+
/*
update the current node
*/
bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
- n0.d = new_node_content;
- if (content_features(n0.d).liquid_type == LIQUID_FLOWING) {
+ if (content_features(new_node_content).liquid_type == LIQUID_FLOWING) {
// set level to last 3 bits, flowing down bit to 4th bit
n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK);
} else {
- n0.param2 = 0;
+ // set the liquid level and flow bit to 0
+ n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK);
}
+ n0.setContent(new_node_content);
setNode(p0, n0);
v3s16 blockpos = getNodeBlockPos(p0);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
- if(block != NULL)
+ if(block != NULL) {
modified_blocks.insert(blockpos, block);
-
+ // If node emits light, MapBlock requires lighting update
+ if(content_features(n0).light_source != 0)
+ lighting_modified_blocks[block->getPos()] = block;
+ }
+
/*
enqueue neighbors for update if neccessary
*/
- switch (content_features(n0.d).liquid_type) {
+ switch (content_features(n0.getContent()).liquid_type) {
case LIQUID_SOURCE:
+ case LIQUID_FLOWING:
// make sure source flows into all neighboring nodes
for (u16 i = 0; i < num_flows; i++)
if (flows[i].t != NEIGHBOR_UPPER)
for (u16 i = 0; i < num_flows; i++)
m_transforming_liquid.push_back(flows[i].p);
break;
- case LIQUID_FLOWING:
- for (u16 i = 0; i < num_flows; i++) {
- u8 flow_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
- // liquid_level is still the ORIGINAL level of this node.
- if (flows[i].t != NEIGHBOR_UPPER && ((flow_level < liquid_level || flow_level < new_node_level) ||
- flow_down_enabled))
- m_transforming_liquid.push_back(flows[i].p);
- }
- for (u16 i = 0; i < num_airs; i++) {
- if (airs[i].t != NEIGHBOR_UPPER && (airs[i].t == NEIGHBOR_LOWER || new_node_level > 0))
- m_transforming_liquid.push_back(airs[i].p);
- }
- break;
- }
-
- loopcount++;
- //if(loopcount >= 100000)
- if(loopcount >= initial_size * 10) {
- break;
}
}
//dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
+ while (must_reflow.size() > 0)
+ m_transforming_liquid.push_back(must_reflow.pop_front());
+ updateLighting(lighting_modified_blocks, modified_blocks);
}
NodeMetadata* Map::getNodeMetadata(v3s16 p)
ServerMap::ServerMap(std::string savedir):
Map(dout_server),
m_seed(0),
- m_map_metadata_changed(true)
+ m_map_metadata_changed(true),
+ m_database(NULL),
+ m_database_read(NULL),
+ m_database_write(NULL)
{
dstream<<__FUNCTION_NAME<<std::endl;
//m_chunksize = 8; // Takes a few seconds
- m_seed = (((u64)(myrand()%0xffff)<<0)
- + ((u64)(myrand()%0xffff)<<16)
- + ((u64)(myrand()%0xffff)<<32)
- + ((u64)(myrand()%0xffff)<<48));
+ if (g_settings.get("fixed_map_seed").empty())
+ {
+ m_seed = (((u64)(myrand()%0xffff)<<0)
+ + ((u64)(myrand()%0xffff)<<16)
+ + ((u64)(myrand()%0xffff)<<32)
+ + ((u64)(myrand()%0xffff)<<48));
+ }
+ else
+ {
+ m_seed = g_settings.getU64("fixed_map_seed");
+ }
/*
Experimental and debug stuff
<<", exception: "<<e.what()<<std::endl;
}
+ /*
+ Close database if it was opened
+ */
+ if(m_database_read)
+ sqlite3_finalize(m_database_read);
+ if(m_database_write)
+ sqlite3_finalize(m_database_write);
+ if(m_database)
+ sqlite3_close(m_database);
+
#if 0
/*
Free all MapChunks
void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
{
- /*dstream<<"initBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
- <<blockpos.Z<<")"<<std::endl;*/
+ bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+ if(enable_mapgen_debug_info)
+ dstream<<"initBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
+ <<blockpos.Z<<")"<<std::endl;
// Do nothing if not inside limits (+-1 because of neighbors)
if(blockpos_over_limit(blockpos - v3s16(1,1,1)) ||
for(s16 y=-1; y<=1; y++)
{
- //MapBlock *block = createBlock(blockpos);
+ v3s16 p(blockpos.X+x, blockpos.Y+y, blockpos.Z+z);
+ //MapBlock *block = createBlock(p);
// 1) get from memory, 2) load from disk
- MapBlock *block = emergeBlock(blockpos, false);
+ MapBlock *block = emergeBlock(p, false);
// 3) create a blank one
if(block == NULL)
- block = createBlock(blockpos);
+ {
+ block = createBlock(p);
+
+ /*
+ Block gets sunlight if this is true.
+
+ Refer to the map generator heuristics.
+ */
+ bool ug = mapgen::block_is_underground(data->seed, p);
+ block->setIsUnderground(ug);
+ }
// Lighting will not be valid after make_chunk is called
block->setLightingExpired(true);
// Lighting will be calculated
//block->setLightingExpired(false);
-
- /*
- Block gets sunlight if this is true.
-
- This should be set to true when the top side of a block
- is completely exposed to the sky.
- */
- block->setIsUnderground(false);
}
}
}
assert(block);
/*
- Set is_underground flag for lighting with sunlight
+ Set is_underground flag for lighting with sunlight.
+
+ Refer to map generator heuristics.
+
+ NOTE: This is done in initChunkMake
*/
+ //block->setIsUnderground(mapgen::block_is_underground(data->seed, blockpos));
- block->setIsUnderground(mapgen::block_is_underground(data->seed, blockpos));
/*
Add sunlight to central block.
#if 1
// Center block
lighting_update_blocks.insert(block->getPos(), block);
+
+ /*{
+ s16 x = 0;
+ s16 z = 0;
+ v3s16 p = block->getPos()+v3s16(x,1,z);
+ lighting_update_blocks[p] = getBlockNoCreateNoEx(p);
+ }*/
#endif
#if 0
// All modified blocks
lighting_update_blocks.insert(i.getNode()->getKey(),
i.getNode()->getValue());
}
+ /*// Also force-add all the upmost blocks for proper sunlight
+ for(s16 x=-1; x<=1; x++)
+ for(s16 z=-1; z<=1; z++)
+ {
+ v3s16 p = block->getPos()+v3s16(x,1,z);
+ lighting_update_blocks[p] = getBlockNoCreateNoEx(p);
+ }*/
#endif
updateLighting(lighting_update_blocks, changed_blocks);
+
+ /*
+ Set lighting to non-expired state in all of them.
+ This is cheating, but it is not fast enough if all of them
+ would actually be updated.
+ */
+ for(s16 x=-1; x<=1; x++)
+ for(s16 y=-1; y<=1; y++)
+ for(s16 z=-1; z<=1; z++)
+ {
+ v3s16 p = block->getPos()+v3s16(x,y,z);
+ getBlockNoCreateNoEx(p)->setLightingExpired(false);
+ }
if(enable_mapgen_debug_info == false)
t.stop(true); // Hide output
/*dstream<<"finishBlockMake() done for ("<<blockpos.X<<","<<blockpos.Y<<","
<<blockpos.Z<<")"<<std::endl;*/
-
+#if 0
+ if(enable_mapgen_debug_info)
+ {
+ /*
+ Analyze resulting blocks
+ */
+ for(s16 x=-1; x<=1; x++)
+ for(s16 y=-1; y<=1; y++)
+ for(s16 z=-1; z<=1; z++)
+ {
+ v3s16 p = block->getPos()+v3s16(x,y,z);
+ MapBlock *block = getBlockNoCreateNoEx(p);
+ char spos[20];
+ snprintf(spos, 20, "(%2d,%2d,%2d)", x, y, z);
+ dstream<<"Generated "<<spos<<": "
+ <<analyze_block(block)<<std::endl;
+ }
+ }
+#endif
+
return block;
}
/*
Try to load metadata from disk
*/
+#if 0
if(loadSectorMeta(p2d) == true)
{
ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d);
}
return sector;
}
-
+#endif
/*
Do not create over-limit
*/
{
v3s16 p(x0,y0,z0);
MapNode n = block->getNode(p);
- if(n.d == CONTENT_IGNORE)
+ if(n.getContent() == CONTENT_IGNORE)
{
dstream<<"CONTENT_IGNORE at "
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
{
MapNode n;
if(y0%2==0)
- n.d = CONTENT_AIR;
+ n.setContent(CONTENT_AIR);
else
- n.d = CONTENT_STONE;
+ n.setContent(CONTENT_STONE);
block->setNode(v3s16(x0,y0,z0), n);
}
}
{
MapBlock *block = getBlockNoCreateNoEx(p);
- if(block)
+ if(block && block->isDummy() == false)
return block;
}
for(; p.Y>min; p.Y--)
{
MapNode n = getNodeNoEx(p);
- if(n.d != CONTENT_IGNORE)
+ if(n.getContent() != CONTENT_IGNORE)
break;
}
if(p.Y == min)
goto plan_b;
// If this node is not air, go to plan b
- if(getNodeNoEx(p).d != CONTENT_AIR)
+ if(getNodeNoEx(p).getContent() != CONTENT_AIR)
goto plan_b;
// Search existing walkable and return it
for(; p.Y>min; p.Y--)
{
MapNode n = getNodeNoEx(p);
- if(content_walkable(n.d) && n.d != CONTENT_IGNORE)
+ if(content_walkable(n.d) && n.getContent() != CONTENT_IGNORE)
return p.Y;
}
//return (s16)level;
}
+void ServerMap::createDatabase() {
+ int e;
+ assert(m_database);
+ e = sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `blocks` ("
+ "`pos` INT NOT NULL PRIMARY KEY,"
+ "`data` BLOB"
+ ");"
+ , NULL, NULL, NULL);
+ if(e == SQLITE_ABORT)
+ throw FileNotGoodException("Could not create database structure");
+ else
+ dstream<<"Server: Database structure was created";
+}
+
+void ServerMap::verifyDatabase() {
+ if(m_database)
+ return;
+
+ {
+ std::string dbp = m_savedir + "/map.sqlite";
+ bool needs_create = false;
+ int d;
+
+ /*
+ Open the database connection
+ */
+
+ createDirs(m_savedir);
+
+ if(!fs::PathExists(dbp))
+ needs_create = true;
+
+ d = sqlite3_open_v2(dbp.c_str(), &m_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
+ if(d != SQLITE_OK) {
+ dstream<<"WARNING: Database failed to open: "<<sqlite3_errmsg(m_database)<<std::endl;
+ throw FileNotGoodException("Cannot open database file");
+ }
+
+ if(needs_create)
+ createDatabase();
+
+ d = sqlite3_prepare(m_database, "SELECT `data` FROM `blocks` WHERE `pos`=? LIMIT 1", -1, &m_database_read, NULL);
+ if(d != SQLITE_OK) {
+ dstream<<"WARNING: Database read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
+ throw FileNotGoodException("Cannot prepare read statement");
+ }
+
+ d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?)", -1, &m_database_write, NULL);
+ if(d != SQLITE_OK) {
+ dstream<<"WARNING: Database write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
+ throw FileNotGoodException("Cannot prepare write statement");
+ }
+
+ dstream<<"Server: Database opened"<<std::endl;
+ }
+}
+
+bool ServerMap::loadFromFolders() {
+ if(!m_database && !fs::PathExists(m_savedir + "/map.sqlite"))
+ return true;
+ return false;
+}
+
+sqlite3_int64 ServerMap::getBlockAsInteger(const v3s16 pos) {
+ return (sqlite3_int64)pos.Z*16777216 +
+ (sqlite3_int64)pos.Y*4096 + (sqlite3_int64)pos.X;
+}
+
void ServerMap::createDirs(std::string path)
{
if(fs::CreateAllDirs(path) == false)
u32 block_count = 0;
u32 block_count_all = 0; // Number of blocks in memory
+ beginSave();
core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
for(; i.atEnd() == false; i++)
{
core::list<MapBlock*> blocks;
sector->getBlocks(blocks);
core::list<MapBlock*>::Iterator j;
+
+ //sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL);
for(j=blocks.begin(); j!=blocks.end(); j++)
{
MapBlock *block = *j;
<<block->getPos().Z<<")"
<<std::endl;*/
}
+ //sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL);
}
}
+ endSave();
/*
Only print if something happened or saved whole map
}
#endif
+void ServerMap::beginSave() {
+ verifyDatabase();
+ if(sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK)
+ dstream<<"WARNING: beginSave() failed, saving might be slow.";
+}
+
+void ServerMap::endSave() {
+ verifyDatabase();
+ if(sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
+ dstream<<"WARNING: endSave() failed, map might not have saved.";
+}
+
void ServerMap::saveBlock(MapBlock *block)
{
DSTACK(__FUNCTION_NAME);
// Get destination
v3s16 p3d = block->getPos();
+
+#if 0
v2s16 p2d(p3d.X, p3d.Z);
std::string sectordir = getSectorDir(p2d);
std::ofstream o(fullpath.c_str(), std::ios_base::binary);
if(o.good() == false)
throw FileNotGoodException("Cannot open block data");
-
+#endif
/*
[0] u8 serialization version
[1] data
*/
+
+ verifyDatabase();
+
+ std::ostringstream o(std::ios_base::binary);
+
o.write((char*)&version, 1);
// Write basic data
// Write extra data stored on disk
block->serializeDiskExtra(o, version);
-
+
+ // Write block to database
+
+ std::string tmp = o.str();
+ const char *bytes = tmp.c_str();
+
+ if(sqlite3_bind_int64(m_database_write, 1, getBlockAsInteger(p3d)) != SQLITE_OK)
+ dstream<<"WARNING: Block position failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
+ if(sqlite3_bind_blob(m_database_write, 2, (void *)bytes, o.tellp(), NULL) != SQLITE_OK) // TODO this mught not be the right length
+ dstream<<"WARNING: Block data failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
+ int written = sqlite3_step(m_database_write);
+ if(written != SQLITE_DONE)
+ dstream<<"WARNING: Block failed to save ("<<p3d.X<<", "<<p3d.Y<<", "<<p3d.Z<<") "
+ <<sqlite3_errmsg(m_database)<<std::endl;
+ // Make ready for later reuse
+ sqlite3_reset(m_database_write);
+
// We just wrote it to the disk so clear modified flag
block->resetModified();
}
if(version < SER_FMT_VER_HIGHEST || save_after_load)
{
saveBlock(block);
+
+ // Should be in database now, so delete the old file
+ fs::RecursiveDelete(fullpath);
}
// We just loaded it from the disk, so it's up-to-date.
}
}
+void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ try {
+ std::istringstream is(*blob, std::ios_base::binary);
+
+ u8 version = SER_FMT_VER_INVALID;
+ is.read((char*)&version, 1);
+
+ if(is.fail())
+ throw SerializationError("ServerMap::loadBlock(): Failed"
+ " to read MapBlock version");
+
+ /*u32 block_size = MapBlock::serializedLength(version);
+ SharedBuffer<u8> data(block_size);
+ is.read((char*)*data, block_size);*/
+
+ // This will always return a sector because we're the server
+ //MapSector *sector = emergeSector(p2d);
+
+ MapBlock *block = NULL;
+ bool created_new = false;
+ block = sector->getBlockNoCreateNoEx(p3d.Y);
+ if(block == NULL)
+ {
+ block = sector->createBlankBlockNoInsert(p3d.Y);
+ created_new = true;
+ }
+
+ // Read basic data
+ block->deSerialize(is, version);
+
+ // Read extra data stored on disk
+ block->deSerializeDiskExtra(is, version);
+
+ // If it's a new block, insert it to the map
+ if(created_new)
+ sector->insertBlock(block);
+
+ /*
+ Save blocks loaded in old format in new format
+ */
+
+ if(version < SER_FMT_VER_HIGHEST || save_after_load)
+ {
+ saveBlock(block);
+ }
+
+ // We just loaded it from, so it's up-to-date.
+ block->resetModified();
+
+ }
+ catch(SerializationError &e)
+ {
+ dstream<<"WARNING: Invalid block data in database "
+ <<" (SerializationError). "
+ <<"what()="<<e.what()
+ <<std::endl;
+ //" Ignoring. A new one will be generated.
+ assert(0);
+
+ // TODO: Copy to a backup database.
+ }
+}
+
MapBlock* ServerMap::loadBlock(v3s16 blockpos)
{
DSTACK(__FUNCTION_NAME);
v2s16 p2d(blockpos.X, blockpos.Z);
+ if(!loadFromFolders()) {
+ verifyDatabase();
+
+ if(sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK)
+ dstream<<"WARNING: Could not bind block position for load: "
+ <<sqlite3_errmsg(m_database)<<std::endl;
+ if(sqlite3_step(m_database_read) == SQLITE_ROW) {
+ /*
+ Make sure sector is loaded
+ */
+ MapSector *sector = createSector(p2d);
+
+ /*
+ Load block
+ */
+ const char * data = (const char *)sqlite3_column_blob(m_database_read, 0);
+ size_t len = sqlite3_column_bytes(m_database_read, 0);
+
+ std::string datastr(data, len);
+
+ loadBlock(&datastr, blockpos, sector, false);
+
+ sqlite3_step(m_database_read);
+ // We should never get more than 1 row, so ok to reset
+ sqlite3_reset(m_database_read);
+
+ return getBlockNoCreateNoEx(blockpos);
+ }
+ sqlite3_reset(m_database_read);
+
+ // Not found in database, try the files
+ }
+
// The directory layout we're going to load from.
// 1 - original sectors/xxxxzzzz/
// 2 - new sectors2/xxx/zzz/
return NULL;
/*
- Load block
+ Load block and save it to the database
*/
- loadBlock(sectordir, blockfilename, sector, loadlayout != 2);
+ loadBlock(sectordir, blockfilename, sector, true);
return getBlockNoCreateNoEx(blockpos);
}
i = m_loaded_blocks.getIterator();
i.atEnd() == false; i++)
{
+ v3s16 p = i.getNode()->getKey();
bool existed = i.getNode()->getValue();
if(existed == false)
+ {
+ // The Great Bug was found using this
+ /*dstream<<"ManualMapVoxelManipulator::blitBackAll: "
+ <<"Inexistent ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<std::endl;*/
continue;
- v3s16 p = i.getNode()->getKey();
+ }
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
if(block == NULL)
{