#include "porting.h"
#include "mapgen.h"
#include "nodemetadata.h"
+#include "content_mapnode.h"
+#ifndef SERVER
+#include <IMaterialRenderer.h>
+#endif
+#include "settings.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
*/
/*
generation for testing or whatever
*/
#if 0
- //if(g_settings.get(""))
+ //if(g_settings->get(""))
{
core::map<v3s16, MapBlock*>::Iterator i;
i = blocks_to_update.getIterator();
/*
*/
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(meta_proto)
{
NodeMetadata *meta = meta_proto->clone();
+ meta->setOwner(player_name);
setNodeMetadata(p, meta);
}
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);
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;
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)
{
- bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+ 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;
return NULL;
}
- bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+ bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
/*dstream<<"Resulting vmanip:"<<std::endl;
data->vmanip.print(dstream);*/
/*
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
*/
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
<<std::endl;*/
- bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+ bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
TimeTaker timer("generateBlock");
//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);
}
m_client(client),
m_control(control),
m_camera_position(0,0,0),
- m_camera_direction(0,0,1)
+ m_camera_direction(0,0,1),
+ m_camera_fov(PI)
{
m_camera_mutex.Init();
assert(m_camera_mutex.IsInitialized());
m_camera_mutex.Lock();
v3f camera_position = m_camera_position;
v3f camera_direction = m_camera_direction;
+ f32 camera_fov = m_camera_fov;
m_camera_mutex.Unlock();
/*
float d = 0.0;
if(isBlockInSight(block->getPos(), camera_position,
- camera_direction, range, &d) == false)
+ camera_direction, camera_fov,
+ range, &d) == false)
{
continue;
}
<<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
}
+void ClientMap::renderPostFx()
+{
+ // Sadly ISceneManager has no "post effects" render pass, in that case we
+ // could just register for that and handle it in renderMap().
+
+ m_camera_mutex.Lock();
+ v3f camera_position = m_camera_position;
+ m_camera_mutex.Unlock();
+
+ MapNode n = getNodeNoEx(floatToInt(camera_position, BS));
+
+ // - If the player is in a solid node, make everything black.
+ // - If the player is in liquid, draw a semi-transparent overlay.
+ ContentFeatures& features = content_features(n);
+ video::SColor post_effect_color = features.post_effect_color;
+ if(features.solidness == 2 && g_settings->getBool("free_move") == false)
+ {
+ post_effect_color = video::SColor(255, 0, 0, 0);
+ }
+ if (post_effect_color.getAlpha() != 0)
+ {
+ // Draw a full-screen rectangle
+ video::IVideoDriver* driver = SceneManager->getVideoDriver();
+ v2u32 ss = driver->getScreenSize();
+ core::rect<s32> rect(0,0, ss.X, ss.Y);
+ driver->draw2DRectangle(post_effect_color, rect);
+ }
+}
+
bool ClientMap::setTempMod(v3s16 p, NodeMod mod,
core::map<v3s16, MapBlock*> *affected_blocks)
{