*/
#include "map.h"
+#include "mapsector.h"
+#include "mapblock.h"
#include "main.h"
-#include "jmutexautolock.h"
+#ifndef SERVER
#include "client.h"
+#endif
#include "filesys.h"
#include "utility.h"
#include "voxel.h"
#include "porting.h"
-#include "mineral.h"
-#include "noise.h"
-#include "serverobject.h"
-#include "content_mapnode.h"
#include "mapgen.h"
+#include "nodemetadata.h"
+#include "content_mapnode.h"
+#ifndef SERVER
+#include <IMaterialRenderer.h>
+#endif
+#include "settings.h"
+#include "log.h"
+#include "profiler.h"
+
+#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
-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
*/
/*
return sector;
}
-MapBlock * Map::getBlockNoCreate(v3s16 p3d)
-{
- v2s16 p2d(p3d.X, p3d.Z);
- MapSector * sector = getSectorNoGenerate(p2d);
-
- MapBlock *block = sector->getBlockNoCreate(p3d.Y);
-
- return block;
-}
-
MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
{
- try
- {
- v2s16 p2d(p3d.X, p3d.Z);
- MapSector * sector = getSectorNoGenerate(p2d);
- MapBlock *block = sector->getBlockNoCreate(p3d.Y);
- return block;
- }
- catch(InvalidPositionException &e)
- {
+ v2s16 p2d(p3d.X, p3d.Z);
+ MapSector * sector = getSectorNoGenerateNoEx(p2d);
+ if(sector == NULL)
return NULL;
- }
+ MapBlock *block = sector->getBlockNoCreateNoEx(p3d.Y);
+ return block;
}
-/*MapBlock * Map::getBlockCreate(v3s16 p3d)
-{
- v2s16 p2d(p3d.X, p3d.Z);
- MapSector * sector = getSectorCreate(p2d);
- assert(sector);
- MapBlock *block = sector->getBlockNoCreate(p3d.Y);
- if(block)
- return block;
- block = sector->createBlankBlock(p3d.Y);
+MapBlock * Map::getBlockNoCreate(v3s16 p3d)
+{
+ MapBlock *block = getBlockNoCreateNoEx(p3d);
+ if(block == NULL)
+ throw InvalidPositionException();
return block;
-}*/
+}
bool Map::isNodeUnderground(v3s16 p)
{
}
}
+bool Map::isValidPosition(v3s16 p)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *block = getBlockNoCreate(blockpos);
+ return (block != NULL);
+}
+
+// Returns a CONTENT_IGNORE node if not found
+MapNode Map::getNodeNoEx(v3s16 p)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *block = getBlockNoCreateNoEx(blockpos);
+ if(block == NULL)
+ return MapNode(CONTENT_IGNORE);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ return block->getNodeNoCheck(relpos);
+}
+
+// throws InvalidPositionException if not found
+MapNode Map::getNode(v3s16 p)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *block = getBlockNoCreateNoEx(blockpos);
+ if(block == NULL)
+ throw InvalidPositionException();
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ return block->getNodeNoCheck(relpos);
+}
+
+// throws InvalidPositionException if not found
+void Map::setNode(v3s16 p, MapNode & n)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *block = getBlockNoCreate(blockpos);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ block->setNodeNoCheck(relpos, n);
+}
+
+
/*
Goes recursively through the neighbours of the node.
*/
/*if(light_sources.find(n2pos))
{
- std::cout<<"Removed from light_sources"<<std::endl;
+ infostream<<"Removed from light_sources"<<std::endl;
light_sources.remove(n2pos);
}*/
}
}
}
- /*dstream<<"unspreadLight(): Changed block "
+ /*infostream<<"unspreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
{
v3s16 pos = j.getNode()->getKey();
//v3s16 pos = *j;
- //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
+ //infostream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
v3s16 blockpos = getNodeBlockPos(pos);
// Only fetch a new block if the block position has changed
}
}
- /*dstream<<"spreadLight(): Changed block "
+ /*infostream<<"spreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
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);
}*/
dummy block.
*/
//assert(0);
- dstream<<"updateLighting(): InvalidPositionException"
+ infostream<<"updateLighting(): InvalidPositionException"
<<std::endl;
}
}
assert(0);
}
- /*dstream<<"Bottom for sunlight-propagated block ("
+ /*infostream<<"Bottom for sunlight-propagated block ("
<<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
<<std::endl;*/
}
}
+
+ /*
+ Enable this to disable proper lighting for speeding up map
+ generation for testing or whatever
+ */
+#if 0
+ //if(g_settings->get(""))
+ {
+ core::map<v3s16, MapBlock*>::Iterator i;
+ i = blocks_to_update.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+ v3s16 p = block->getPos();
+ block->setLightingExpired(false);
+ }
+ return;
+ }
+#endif
#if 0
{
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
- dstream<<"unspreadLight modified "<<diff<<std::endl;
+ infostream<<"unspreadLight modified "<<diff<<std::endl;
}
{
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
- dstream<<"spreadLight modified "<<diff<<std::endl;
+ infostream<<"spreadLight modified "<<diff<<std::endl;
}
#endif
//TimeTaker timer("blitBack");
vmanip.blitBack(modified_blocks);
}
- /*dstream<<"emerge_time="<<emerge_time<<std::endl;
+ /*infostream<<"emerge_time="<<emerge_time<<std::endl;
emerge_time = 0;*/
}
/*
*/
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 1
+#if 0
/*
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))
+ 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++)
}
/*
- Add neighboring liquid nodes to transform queue.
+ Add neighboring liquid nodes and this node to transform queue.
+ (it's vital for the node itself to get updated last.)
*/
- v3s16 dirs[6] = {
+ v3s16 dirs[7] = {
v3s16(0,0,1), // back
v3s16(0,1,0), // top
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left
+ v3s16(0,0,0), // self
};
- for(u16 i=0; i<6; i++)
+ for(u16 i=0; i<7; i++)
{
try
{
v3s16 p2 = p + dirs[i];
MapNode n2 = getNode(p2);
- if(content_liquid(n2.d))
+ 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
/*
Updates usage timers
*/
-void Map::timerUpdate(float dtime)
+void Map::timerUpdate(float dtime, float unload_timeout,
+ core::list<v3s16> *unloaded_blocks)
{
- //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
+ bool save_before_unloading = (mapType() == MAPTYPE_SERVER);
+
+ core::list<v2s16> sector_deletion_queue;
+ u32 deleted_blocks_count = 0;
+ u32 saved_blocks_count = 0;
core::map<v2s16, MapSector*>::Iterator si;
+ beginSave();
si = m_sectors.getIterator();
for(; si.atEnd() == false; si++)
{
MapSector *sector = si.getNode()->getValue();
+ bool all_blocks_deleted = true;
+
core::list<MapBlock*> blocks;
sector->getBlocks(blocks);
+
for(core::list<MapBlock*>::Iterator i = blocks.begin();
i != blocks.end(); i++)
{
- (*i)->incrementUsageTimer(dtime);
+ MapBlock *block = (*i);
+
+ block->incrementUsageTimer(dtime);
+
+ if(block->getUsageTimer() > unload_timeout)
+ {
+ v3s16 p = block->getPos();
+
+ // Save if modified
+ if(block->getModified() != MOD_STATE_CLEAN
+ && save_before_unloading)
+ {
+ saveBlock(block);
+ saved_blocks_count++;
+ }
+
+ // Delete from memory
+ sector->deleteBlock(block);
+
+ if(unloaded_blocks)
+ unloaded_blocks->push_back(p);
+
+ deleted_blocks_count++;
+ }
+ else
+ {
+ all_blocks_deleted = false;
+ }
+ }
+
+ if(all_blocks_deleted)
+ {
+ sector_deletion_queue.push_back(si.getNode()->getKey());
}
}
+ endSave();
+
+ // Finally delete the empty sectors
+ deleteSectors(sector_deletion_queue);
+
+ if(deleted_blocks_count != 0)
+ {
+ PrintInfo(infostream); // ServerMap/ClientMap:
+ infostream<<"Unloaded "<<deleted_blocks_count
+ <<" blocks from memory";
+ if(save_before_unloading)
+ infostream<<", of which "<<saved_blocks_count<<" were written";
+ infostream<<"."<<std::endl;
+ }
}
-void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
+void Map::deleteSectors(core::list<v2s16> &list)
{
core::list<v2s16>::Iterator j;
for(j=list.begin(); j!=list.end(); j++)
{
MapSector *sector = m_sectors[*j];
- if(only_blocks)
- {
- sector->deleteBlocks();
- }
- else
- {
- /*
- If sector is in sector cache, remove it from there
- */
- if(m_sector_cache == sector)
- {
- m_sector_cache = NULL;
- }
- /*
- Remove from map and delete
- */
- m_sectors.remove(*j);
- delete sector;
- }
+ // If sector is in sector cache, remove it from there
+ if(m_sector_cache == sector)
+ m_sector_cache = NULL;
+ // Remove from map and delete
+ m_sectors.remove(*j);
+ delete sector;
}
}
-u32 Map::unloadUnusedData(float timeout, bool only_blocks,
+#if 0
+void Map::unloadUnusedData(float timeout,
core::list<v3s16> *deleted_blocks)
{
core::list<v2s16> sector_deletion_queue;
+ u32 deleted_blocks_count = 0;
+ u32 saved_blocks_count = 0;
core::map<v2s16, MapSector*>::Iterator si = m_sectors.getIterator();
for(; si.atEnd() == false; si++)
i != blocks.end(); i++)
{
MapBlock *block = (*i);
-
+
if(block->getUsageTimer() > timeout)
{
// Save if modified
if(block->getModified() != MOD_STATE_CLEAN)
+ {
saveBlock(block);
- // Unload
- sector->removeBlock(block);
- delete block;
+ saved_blocks_count++;
+ }
+ // Delete from memory
+ sector->deleteBlock(block);
+ deleted_blocks_count++;
}
else
{
}
}
-#if 0
- core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
- for(; i.atEnd() == false; i++)
- {
- MapSector *sector = i.getNode()->getValue();
- /*
- Delete sector from memory if it hasn't been used in a long time
- */
- if(sector->usage_timer > timeout)
- {
- sector_deletion_queue.push_back(i.getNode()->getKey());
+ deleteSectors(sector_deletion_queue);
- if(deleted_blocks != NULL)
- {
- // Collect positions of blocks of sector
- MapSector *sector = i.getNode()->getValue();
- core::list<MapBlock*> blocks;
- sector->getBlocks(blocks);
- for(core::list<MapBlock*>::Iterator i = blocks.begin();
- i != blocks.end(); i++)
- {
- deleted_blocks->push_back((*i)->getPos());
- }
- }
- }
- }
-#endif
+ infostream<<"Map: Unloaded "<<deleted_blocks_count<<" blocks from memory"
+ <<", of which "<<saved_blocks_count<<" were wr."
+ <<std::endl;
- deleteSectors(sector_deletion_queue, only_blocks);
- return sector_deletion_queue.getSize();
+ //return sector_deletion_queue.getSize();
+ //return deleted_blocks_count;
}
+#endif
void Map::PrintInfo(std::ostream &out)
{
#define WATER_DROP_BOOST 4
+enum NeighborType {
+ NEIGHBOR_UPPER,
+ NEIGHBOR_SAME_LEVEL,
+ NEIGHBOR_LOWER
+};
+struct NodeNeighbor {
+ MapNode n;
+ NeighborType t;
+ v3s16 p;
+};
+
void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
{
DSTACK(__FUNCTION_NAME);
u32 initial_size = m_transforming_liquid.size();
/*if(initial_size != 0)
- dstream<<"transformLiquids(): initial_size="<<initial_size<<std::endl;*/
+ infostream<<"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 = getNode(p0);
-
- // Don't deal with non-liquids
- if(content_liquid(n0.d) == false)
- continue;
-
- bool is_source = !content_flowing_liquid(n0.d);
-
- u8 liquid_level = 8;
- if(is_source == false)
- liquid_level = n0.param2 & 0x0f;
-
- // Turn possible source into non-source
- u8 nonsource_c = make_liquid_flowing(n0.d);
+ MapNode n0 = getNodeNoEx(p0);
/*
- If not source, check that some node flows into this one
- and what is the level of liquid in this one
- */
- if(is_source == false)
- {
- s8 new_liquid_level_max = -1;
-
- v3s16 dirs_from[5] = {
- v3s16(0,1,0), // top
- v3s16(0,0,1), // back
- v3s16(1,0,0), // right
- v3s16(0,0,-1), // front
- v3s16(-1,0,0), // left
- };
- for(u16 i=0; i<5; i++)
- {
- try
- {
-
- bool from_top = (i==0);
-
- v3s16 p2 = p0 + dirs_from[i];
- MapNode n2 = getNode(p2);
+ Collect information about current node
+ */
+ s8 liquid_level = -1;
+ u8 liquid_kind = CONTENT_IGNORE;
+ LiquidType liquid_type = content_features(n0.getContent()).liquid_type;
+ switch (liquid_type) {
+ case LIQUID_SOURCE:
+ 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.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.getContent() != CONTENT_AIR)
+ continue;
+ liquid_kind = CONTENT_AIR;
+ break;
+ }
- if(content_liquid(n2.d))
- {
- u8 n2_nonsource_c = make_liquid_flowing(n2.d);
- // Check that the liquids are the same type
- if(n2_nonsource_c != nonsource_c)
- {
- dstream<<"WARNING: Not handling: different liquids"
- " collide"<<std::endl;
- continue;
+ /*
+ Collect information about the environment
+ */
+ const v3s16 *dirs = g_6dirs;
+ NodeNeighbor sources[6]; // surrounding sources
+ int num_sources = 0;
+ NodeNeighbor flows[6]; // surrounding flowing liquid nodes
+ int num_flows = 0;
+ NodeNeighbor airs[6]; // surrounding air
+ int num_airs = 0;
+ NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid
+ int num_neutrals = 0;
+ bool flowing_down = false;
+ for (u16 i = 0; i < 6; i++) {
+ NeighborType nt = NEIGHBOR_SAME_LEVEL;
+ switch (i) {
+ case 1:
+ nt = NEIGHBOR_UPPER;
+ break;
+ case 4:
+ nt = NEIGHBOR_LOWER;
+ break;
+ }
+ v3s16 npos = p0 + dirs[i];
+ NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
+ switch (content_features(nb.n.getContent()).liquid_type) {
+ case LIQUID_NONE:
+ 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) {
+ flowing_down = true;
+ }
+ } else {
+ neutrals[num_neutrals++] = nb;
}
- bool n2_is_source = !content_flowing_liquid(n2.d);
- s8 n2_liquid_level = 8;
- if(n2_is_source == false)
- n2_liquid_level = n2.param2 & 0x07;
-
- s8 new_liquid_level = -1;
- if(from_top)
- {
- //new_liquid_level = 7;
- if(n2_liquid_level >= 7 - WATER_DROP_BOOST)
- new_liquid_level = 7;
- else
- new_liquid_level = n2_liquid_level + WATER_DROP_BOOST;
+ break;
+ 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.getContent()).liquid_alternative_flowing;
+ if (content_features(nb.n.getContent()).liquid_alternative_flowing !=liquid_kind) {
+ neutrals[num_neutrals++] = nb;
+ } else {
+ sources[num_sources++] = nb;
}
- else if(n2_liquid_level > 0)
- {
- new_liquid_level = n2_liquid_level - 1;
+ break;
+ 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.getContent()).liquid_alternative_flowing;
+ if (content_features(nb.n.getContent()).liquid_alternative_flowing != liquid_kind) {
+ neutrals[num_neutrals++] = nb;
+ } else {
+ flows[num_flows++] = nb;
+ if (nb.t == NEIGHBOR_LOWER)
+ flowing_down = true;
}
+ break;
+ }
+ }
- if(new_liquid_level > new_liquid_level_max)
- new_liquid_level_max = new_liquid_level;
- }
-
- }catch(InvalidPositionException &e)
- {
- }
- } //for
-
- /*
- If liquid level should be something else, update it and
- add all the neighboring water nodes to the transform queue.
- */
- if(new_liquid_level_max != liquid_level)
- {
- if(new_liquid_level_max == -1)
- {
- // Remove water alltoghether
- n0.d = CONTENT_AIR;
- n0.param2 = 0;
- setNode(p0, n0);
- }
- else
- {
- n0.param2 = new_liquid_level_max;
- setNode(p0, n0);
+ /*
+ decide on the type (and possibly level) of the current node
+ */
+ 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
+ // it's perfectly safe to use liquid_kind here to determine the new node content.
+ new_node_content = content_features(liquid_kind).liquid_alternative_source;
+ } else if (num_sources == 1 && sources[0].t != NEIGHBOR_LOWER) {
+ // liquid_kind is set properly, see above
+ new_node_content = liquid_kind;
+ 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 > 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 > max_node_level) {
+ max_node_level = nb_liquid_level - 1;
+ }
+ break;
}
+ }
- // Block has been modified
- {
- v3s16 blockpos = getNodeBlockPos(p0);
- MapBlock *block = getBlockNoCreateNoEx(blockpos);
- if(block != NULL)
- modified_blocks.insert(blockpos, block);
- }
+ 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;
- /*
- Add neighboring non-source liquid nodes to transform queue.
- */
- v3s16 dirs[6] = {
- v3s16(0,0,1), // back
- v3s16(0,1,0), // top
- v3s16(1,0,0), // right
- v3s16(0,0,-1), // front
- v3s16(0,-1,0), // bottom
- v3s16(-1,0,0), // left
- };
- for(u16 i=0; i<6; i++)
- {
- try
- {
+ }
- v3s16 p2 = p0 + dirs[i];
+ /*
+ check if anything has changed. if not, just continue with the next node.
+ */
+ 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;
- MapNode n2 = getNode(p2);
- if(content_flowing_liquid(n2.d))
- {
- m_transforming_liquid.push_back(p2);
- }
- }catch(InvalidPositionException &e)
- {
- }
- }
- }
+ /*
+ update the current node
+ */
+ bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
+ 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 {
+ // 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) {
+ 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;
}
-
- // Get a new one from queue if the node has turned into non-water
- if(content_liquid(n0.d) == false)
- continue;
/*
- Flow water from this node
- */
- v3s16 dirs_to[5] = {
- v3s16(0,-1,0), // bottom
- v3s16(0,0,1), // back
- v3s16(1,0,0), // right
- v3s16(0,0,-1), // front
- v3s16(-1,0,0), // left
- };
- for(u16 i=0; i<5; i++)
- {
- try
- {
+ enqueue neighbors for update if neccessary
+ */
+ 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)
+ m_transforming_liquid.push_back(flows[i].p);
+ for (u16 i = 0; i < num_airs; i++)
+ if (airs[i].t != NEIGHBOR_UPPER)
+ m_transforming_liquid.push_back(airs[i].p);
+ break;
+ case LIQUID_NONE:
+ // this flow has turned to air; neighboring flows might need to do the same
+ for (u16 i = 0; i < num_flows; i++)
+ m_transforming_liquid.push_back(flows[i].p);
+ break;
+ }
+ }
+ //infostream<<"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);
+}
- bool to_bottom = (i == 0);
+NodeMetadata* Map::getNodeMetadata(v3s16 p)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
+ MapBlock *block = getBlockNoCreateNoEx(blockpos);
+ if(!block){
+ infostream<<"Map::getNodeMetadata(): Need to emerge "
+ <<PP(blockpos)<<std::endl;
+ block = emergeBlock(blockpos, false);
+ }
+ if(!block)
+ {
+ infostream<<"WARNING: Map::getNodeMetadata(): Block not found"
+ <<std::endl;
+ return NULL;
+ }
+ NodeMetadata *meta = block->m_node_metadata.get(p_rel);
+ return meta;
+}
- // If liquid is at lowest possible height, it's not going
- // anywhere except down
- if(liquid_level == 0 && to_bottom == false)
- continue;
-
- u8 liquid_next_level = 0;
- // If going to bottom
- if(to_bottom)
- {
- //liquid_next_level = 7;
- if(liquid_level >= 7 - WATER_DROP_BOOST)
- liquid_next_level = 7;
- else
- liquid_next_level = liquid_level + WATER_DROP_BOOST;
- }
- else
- liquid_next_level = liquid_level - 1;
-
- bool n2_changed = false;
- bool flowed = false;
-
- v3s16 p2 = p0 + dirs_to[i];
-
- MapNode n2 = getNode(p2);
- //dstream<<"[1] n2.param="<<(int)n2.param<<std::endl;
-
- if(content_liquid(n2.d))
- {
- u8 n2_nonsource_c = make_liquid_flowing(n2.d);
- // Check that the liquids are the same type
- if(n2_nonsource_c != nonsource_c)
- {
- dstream<<"WARNING: Not handling: different liquids"
- " collide"<<std::endl;
- continue;
- }
- bool n2_is_source = !content_flowing_liquid(n2.d);
- u8 n2_liquid_level = 8;
- if(n2_is_source == false)
- n2_liquid_level = n2.param2 & 0x07;
-
- if(to_bottom)
- {
- flowed = true;
- }
-
- if(n2_is_source)
- {
- // Just flow into the source, nothing changes.
- // n2_changed is not set because destination didn't change
- flowed = true;
- }
- else
- {
- if(liquid_next_level > liquid_level)
- {
- n2.param2 = liquid_next_level;
- setNode(p2, n2);
-
- n2_changed = true;
- flowed = true;
- }
- }
- }
- else if(n2.d == CONTENT_AIR)
- {
- n2.d = nonsource_c;
- n2.param2 = liquid_next_level;
- setNode(p2, n2);
-
- n2_changed = true;
- flowed = true;
- }
-
- //dstream<<"[2] n2.param="<<(int)n2.param<<std::endl;
-
- if(n2_changed)
- {
- m_transforming_liquid.push_back(p2);
-
- v3s16 blockpos = getNodeBlockPos(p2);
- MapBlock *block = getBlockNoCreateNoEx(blockpos);
- if(block != NULL)
- modified_blocks.insert(blockpos, block);
- }
-
- // If n2_changed to bottom, don't flow anywhere else
- if(to_bottom && flowed && !is_source)
- break;
-
- }catch(InvalidPositionException &e)
- {
- }
- }
-
- loopcount++;
- //if(loopcount >= 100000)
- if(loopcount >= initial_size * 1)
- break;
- }
- //dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
-}
-
-NodeMetadata* Map::getNodeMetadata(v3s16 p)
-{
- v3s16 blockpos = getNodeBlockPos(p);
- v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
- MapBlock *block = getBlockNoCreateNoEx(blockpos);
- if(block == NULL)
- {
- dstream<<"WARNING: Map::setNodeMetadata(): Block not found"
- <<std::endl;
- return NULL;
- }
- NodeMetadata *meta = block->m_node_metadata.get(p_rel);
- return meta;
-}
-
-void Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
-{
- v3s16 blockpos = getNodeBlockPos(p);
- v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
- MapBlock *block = getBlockNoCreateNoEx(blockpos);
- if(block == NULL)
- {
- dstream<<"WARNING: Map::setNodeMetadata(): Block not found"
- <<std::endl;
- return;
- }
- block->m_node_metadata.set(p_rel, meta);
-}
+void Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
+ MapBlock *block = getBlockNoCreateNoEx(blockpos);
+ if(!block){
+ infostream<<"Map::setNodeMetadata(): Need to emerge "
+ <<PP(blockpos)<<std::endl;
+ block = emergeBlock(blockpos, false);
+ }
+ if(!block)
+ {
+ infostream<<"WARNING: Map::setNodeMetadata(): Block not found"
+ <<std::endl;
+ return;
+ }
+ block->m_node_metadata.set(p_rel, meta);
+}
void Map::removeNodeMetadata(v3s16 p)
{
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if(block == NULL)
{
- dstream<<"WARNING: Map::removeNodeMetadata(): Block not found"
+ infostream<<"WARNING: Map::removeNodeMetadata(): Block not found"
<<std::endl;
return;
}
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;
+ infostream<<__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
// If directory is empty, it is safe to save into it.
if(fs::GetDirListing(m_savedir).size() == 0)
{
- dstream<<DTIME<<"Server: Empty save directory is valid."
+ infostream<<"Server: Empty save directory is valid."
<<std::endl;
m_map_saving_enabled = true;
}
loadMapMeta();
}
catch(FileNotGoodException &e){
- dstream<<DTIME<<"WARNING: Could not load map metadata"
+ infostream<<"WARNING: Could not load map metadata"
//<<" Disabling chunk-based generator."
<<std::endl;
//m_chunksize = 0;
loadChunkMeta();
}
catch(FileNotGoodException &e){
- dstream<<DTIME<<"WARNING: Could not load chunk metadata."
+ infostream<<"WARNING: Could not load chunk metadata."
<<" Disabling chunk-based generator."
<<std::endl;
m_chunksize = 0;
}*/
- /*dstream<<DTIME<<"Server: Successfully loaded chunk "
+ /*infostream<<"Server: Successfully loaded chunk "
"metadata and sector (0,0) from "<<savedir<<
", assuming valid save directory."
<<std::endl;*/
- dstream<<DTIME<<"INFO: Server: Successfully loaded map "
+ infostream<<"Server: Successfully loaded map "
<<"and chunk metadata from "<<savedir
<<", assuming valid save directory."
<<std::endl;
}
catch(std::exception &e)
{
- dstream<<DTIME<<"WARNING: Server: Failed to load map from "<<savedir
+ infostream<<"WARNING: Server: Failed to load map from "<<savedir
<<", exception: "<<e.what()<<std::endl;
- dstream<<"Please remove the map or fix it."<<std::endl;
- dstream<<"WARNING: Map saving will be disabled."<<std::endl;
+ infostream<<"Please remove the map or fix it."<<std::endl;
+ infostream<<"WARNING: Map saving will be disabled."<<std::endl;
}
- dstream<<DTIME<<"INFO: Initializing new map."<<std::endl;
+ infostream<<"Initializing new map."<<std::endl;
// Create zero sector
emergeSector(v2s16(0,0));
ServerMap::~ServerMap()
{
- dstream<<__FUNCTION_NAME<<std::endl;
+ infostream<<__FUNCTION_NAME<<std::endl;
try
{
if(m_map_saving_enabled)
{
- //save(false);
// Save only changed parts
save(true);
- dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
+ infostream<<"Server: saved map to "<<m_savedir<<std::endl;
}
else
{
- dstream<<DTIME<<"Server: map not saved"<<std::endl;
+ infostream<<"Server: map not saved"<<std::endl;
}
}
catch(std::exception &e)
{
- dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
+ infostream<<"Server: Failed to save map to "<<m_savedir
<<", 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)
+ infostream<<"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)) ||
+ blockpos_over_limit(blockpos + v3s16(1,1,1)))
+ {
+ data->no_op = true;
+ return;
+ }
+
data->no_op = false;
data->seed = m_seed;
data->blockpos = blockpos;
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(p, false);
+ // 3) create a blank one
+ if(block == NULL)
+ {
+ block = createBlock(p);
- // Lighting won't be calculated
+ /*
+ 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);
}
}
}
neighboring blocks
*/
+ // The area that contains this block and it's neighbors
v3s16 bigarea_blocks_min = blockpos - v3s16(1,1,1);
v3s16 bigarea_blocks_max = blockpos + v3s16(1,1,1);
core::map<v3s16, MapBlock*> &changed_blocks)
{
v3s16 blockpos = data->blockpos;
- /*dstream<<"finishBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
+ /*infostream<<"finishBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
<<blockpos.Z<<")"<<std::endl;*/
if(data->no_op)
{
- dstream<<"finishBlockMake(): no-op"<<std::endl;
+ //infostream<<"finishBlockMake(): no-op"<<std::endl;
return NULL;
}
- /*dstream<<"Resulting vmanip:"<<std::endl;
- data->vmanip.print(dstream);*/
+ bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
+
+ /*infostream<<"Resulting vmanip:"<<std::endl;
+ data->vmanip.print(infostream);*/
/*
Blit generated stuff to map
//TimeTaker timer("finishBlockMake() blitBackAll");
data->vmanip->blitBackAll(&changed_blocks);
}
-#if 1
- dstream<<"finishBlockMake: changed_blocks.size()="
- <<changed_blocks.size()<<std::endl;
-#endif
+
+ if(enable_mapgen_debug_info)
+ infostream<<"finishBlockMake: changed_blocks.size()="
+ <<changed_blocks.size()<<std::endl;
+
/*
Copy transforming liquid information
*/
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.
/*
NOTE: Lighting and object adding shouldn't really be here, but
lighting is a bit tricky to move properly to makeBlock.
- TODO: Do this the right way anyway.
+ TODO: Do this the right way anyway, that is, move it to makeBlock.
+ - There needs to be some way for makeBlock to report back if
+ the lighting update is going further down because of the
+ new block blocking light
*/
/*
Update lighting
+ NOTE: This takes ~60ms, TODO: Investigate why
*/
+ {
+ TimeTaker t("finishBlockMake lighting update");
- core::map<v3s16, MapBlock*> lighting_update_blocks;
- // Center block
- lighting_update_blocks.insert(block->getPos(), block);
+ core::map<v3s16, MapBlock*> lighting_update_blocks;
+#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
- for(core::map<v3s16, MapBlock*>::Iterator
- i = changed_blocks.getIterator();
- i.atEnd() == false; i++)
- {
- lighting_update_blocks.insert(i.getNode()->getKey(),
- i.getNode()->getValue());
- }
+ // All modified blocks
+ // NOTE: Should this be done? If this is not done, then the lighting
+ // of the others will be updated in a different place, one by one, i
+ // think... or they might not? Well, at least they are left marked as
+ // "lighting expired"; it seems that is not handled at all anywhere,
+ // so enabling this will slow it down A LOT because otherwise it
+ // would not do this at all. This causes the black trees.
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = changed_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ 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);
-
+ 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
+ }
+
/*
Add random objects to block
*/
*/
//save(true);
- /*dstream<<"finishBlockMake() done for ("<<blockpos.X<<","<<blockpos.Y<<","
+ /*infostream<<"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);
+ infostream<<"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);
if(sector == NULL)
{
- dstream<<"ServerMap::createSector(): loadSectorFull didn't make a sector"<<std::endl;
+ infostream<<"ServerMap::createSector(): loadSectorFull didn't make a sector"<<std::endl;
throw InvalidPositionException("");
}
return sector;
}
-
+#endif
/*
Do not create over-limit
*/
{
DSTACKF("%s: p=(%d,%d,%d)", __FUNCTION_NAME, p.X, p.Y, p.Z);
- /*dstream<<"generateBlock(): "
+ /*infostream<<"generateBlock(): "
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
<<std::endl;*/
+ bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
+
TimeTaker timer("generateBlock");
//MapBlock *block = original_dummy;
*/
if(blockpos_over_limit(p))
{
- dstream<<__FUNCTION_NAME<<": Block position over limit"<<std::endl;
+ infostream<<__FUNCTION_NAME<<": Block position over limit"<<std::endl;
throw InvalidPositionException("generateBlock(): pos. over limit");
}
{
TimeTaker t("mapgen::make_block()");
mapgen::make_block(&data);
+
+ if(enable_mapgen_debug_info == false)
+ t.stop(true); // Hide output
}
/*
Get central block
*/
MapBlock *block = getBlockNoCreateNoEx(p);
- assert(block);
#if 0
/*
Check result
*/
- bool erroneus_content = false;
- for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
- for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
- for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+ if(block)
{
- v3s16 p(x0,y0,z0);
- MapNode n = block->getNode(p);
- if(n.d == CONTENT_IGNORE)
+ bool erroneus_content = false;
+ for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+ for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+ for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+ {
+ v3s16 p(x0,y0,z0);
+ MapNode n = block->getNode(p);
+ if(n.getContent() == CONTENT_IGNORE)
+ {
+ infostream<<"CONTENT_IGNORE at "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<std::endl;
+ erroneus_content = true;
+ assert(0);
+ }
+ }
+ if(erroneus_content)
{
- dstream<<"CONTENT_IGNORE at "
- <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
- <<std::endl;
- erroneus_content = true;
assert(0);
}
}
- if(erroneus_content)
- {
- assert(0);
- }
#endif
#if 0
/*
Generate a completely empty block
*/
- for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
- for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+ if(block)
{
- for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+ for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+ for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
{
- MapNode n;
- if(y0%2==0)
- n.d = CONTENT_AIR;
- else
- n.d = CONTENT_STONE;
- block->setNode(v3s16(x0,y0,z0), n);
+ for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+ {
+ MapNode n;
+ if(y0%2==0)
+ n.setContent(CONTENT_AIR);
+ else
+ n.setContent(CONTENT_STONE);
+ block->setNode(v3s16(x0,y0,z0), n);
+ }
}
}
#endif
+ if(enable_mapgen_debug_info == false)
+ timer.stop(true); // Hide output
+
return block;
}
}
catch(InvalidPositionException &e)
{
- dstream<<"createBlock: createSector() failed"<<std::endl;
+ infostream<<"createBlock: createSector() failed"<<std::endl;
throw e;
}
/*
*/
/*catch(std::exception &e)
{
- dstream<<"createBlock: createSector() failed: "
+ infostream<<"createBlock: createSector() failed: "
<<e.what()<<std::endl;
throw e;
}*/
return block;
}
-#if 0
-MapBlock * ServerMap::emergeBlock(
- v3s16 p,
- bool only_from_disk,
- core::map<v3s16, MapBlock*> &changed_blocks,
- core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
-)
+MapBlock * ServerMap::emergeBlock(v3s16 p, bool allow_generate)
{
- DSTACKF("%s: p=(%d,%d,%d), only_from_disk=%d",
+ DSTACKF("%s: p=(%d,%d,%d), allow_generate=%d",
__FUNCTION_NAME,
- p.X, p.Y, p.Z, only_from_disk);
-
- // This has to be redone or removed
- assert(0);
- return NULL;
-}
-#endif
-
-#if 0
- /*
- Do not generate over-limit
- */
- if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
- || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
- || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
- || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
- || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
- || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
- throw InvalidPositionException("emergeBlock(): pos. over limit");
-
- v2s16 p2d(p.X, p.Z);
- s16 block_y = p.Y;
- /*
- This will create or load a sector if not found in memory.
- If block exists on disk, it will be loaded.
- */
- ServerMapSector *sector;
- try{
- sector = createSector(p2d);
- //sector = emergeSector(p2d, changed_blocks);
- }
- catch(InvalidPositionException &e)
- {
- dstream<<"emergeBlock: createSector() failed: "
- <<e.what()<<std::endl;
- dstream<<"Path to failed sector: "<<getSectorDir(p2d)
- <<std::endl
- <<"You could try to delete it."<<std::endl;
- throw e;
- }
- catch(VersionMismatchException &e)
- {
- dstream<<"emergeBlock: createSector() failed: "
- <<e.what()<<std::endl;
- dstream<<"Path to failed sector: "<<getSectorDir(p2d)
- <<std::endl
- <<"You could try to delete it."<<std::endl;
- throw e;
- }
-
- /*
- Try to get a block from the sector
- */
-
- bool does_not_exist = false;
- bool lighting_expired = false;
- MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
-
- // If not found, try loading from disk
- if(block == NULL)
- {
- block = loadBlock(p);
- }
-
- // Handle result
- if(block == NULL)
- {
- does_not_exist = true;
- }
- else if(block->isDummy() == true)
- {
- does_not_exist = true;
- }
- else if(block->getLightingExpired())
- {
- lighting_expired = true;
- }
- else
- {
- // Valid block
- //dstream<<"emergeBlock(): Returning already valid block"<<std::endl;
- return block;
- }
+ p.X, p.Y, p.Z, allow_generate);
- /*
- If block was not found on disk and not going to generate a
- new one, make sure there is a dummy block in place.
- */
- if(only_from_disk && (does_not_exist || lighting_expired))
- {
- //dstream<<"emergeBlock(): Was not on disk but not generating"<<std::endl;
-
- if(block == NULL)
- {
- // Create dummy block
- block = new MapBlock(this, p, true);
-
- // Add block to sector
- sector->insertBlock(block);
- }
- // Done.
- return block;
- }
-
- //dstream<<"Not found on disk, generating."<<std::endl;
- // 0ms
- //TimeTaker("emergeBlock() generate");
-
- //dstream<<"emergeBlock(): Didn't find valid block -> making one"<<std::endl;
-
- /*
- If the block doesn't exist, generate the block.
- */
- if(does_not_exist)
{
- block = generateBlock(p, block, sector, changed_blocks,
- lighting_invalidated_blocks);
+ MapBlock *block = getBlockNoCreateNoEx(p);
+ if(block && block->isDummy() == false)
+ return block;
}
- if(lighting_expired)
{
- lighting_invalidated_blocks.insert(p, block);
+ MapBlock *block = loadBlock(p);
+ if(block)
+ return block;
}
-#if 0
- /*
- Initially update sunlight
- */
+ if(allow_generate)
{
- core::map<v3s16, bool> light_sources;
- bool black_air_left = false;
- bool bottom_invalid =
- block->propagateSunlight(light_sources, true,
- &black_air_left);
-
- // If sunlight didn't reach everywhere and part of block is
- // above ground, lighting has to be properly updated
- //if(black_air_left && some_part_underground)
- if(black_air_left)
+ core::map<v3s16, MapBlock*> modified_blocks;
+ MapBlock *block = generateBlock(p, modified_blocks);
+ if(block)
{
- lighting_invalidated_blocks[block->getPos()] = block;
- }
+ MapEditEvent event;
+ event.type = MEET_OTHER;
+ event.p = p;
+
+ // Copy modified_blocks to event
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = modified_blocks.getIterator();
+ i.atEnd()==false; i++)
+ {
+ event.modified_blocks.insert(i.getNode()->getKey(), false);
+ }
- if(bottom_invalid)
- {
- lighting_invalidated_blocks[block->getPos()] = block;
+ // Queue event
+ dispatchEvent(&event);
+
+ return block;
}
}
-#endif
-
- return block;
+
+ return NULL;
}
-#endif
s16 ServerMap::findGroundLevel(v2s16 p2d)
{
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;
}
s16 level = mapgen::find_ground_level_from_noise(m_seed, p2d, 1);
return level;
- //double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT;
- //return (s16)level;
+ //double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT;
+ //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
+ infostream<<"Server: Database structure was created";
+}
+
+void ServerMap::verifyDatabase() {
+ if(m_database)
+ return;
+
+ {
+ std::string dbp = m_savedir + DIR_DELIM + "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) {
+ infostream<<"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) {
+ infostream<<"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) {
+ infostream<<"WARNING: Database write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
+ throw FileNotGoodException("Cannot prepare write statement");
+ }
+
+ d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL);
+ if(d != SQLITE_OK) {
+ infostream<<"WARNING: Database list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
+ throw FileNotGoodException("Cannot prepare read statement");
+ }
+
+ infostream<<"Server: Database opened"<<std::endl;
+ }
+}
+
+bool ServerMap::loadFromFolders() {
+ if(!m_database && !fs::PathExists(m_savedir + DIR_DELIM + "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)
(unsigned int)pos.X&0xffff,
(unsigned int)pos.Y&0xffff);
- return m_savedir + "/sectors/" + cc;
+ return m_savedir + DIR_DELIM + "sectors" + DIR_DELIM + cc;
case 2:
- snprintf(cc, 9, "%.3x/%.3x",
+ snprintf(cc, 9, "%.3x" DIR_DELIM "%.3x",
(unsigned int)pos.X&0xfff,
(unsigned int)pos.Y&0xfff);
- return m_savedir + "/sectors2/" + cc;
+ return m_savedir + DIR_DELIM + "sectors2" + DIR_DELIM + cc;
default:
assert(false);
}
{
unsigned int x, y;
int r;
- size_t spos = dirname.rfind('/') + 1;
+ size_t spos = dirname.rfind(DIR_DELIM_C) + 1;
assert(spos != std::string::npos);
if(dirname.size() - spos == 8)
{
else if(dirname.size() - spos == 3)
{
// New layout
- r = sscanf(dirname.substr(spos-4).c_str(), "%3x/%3x", &x, &y);
+ r = sscanf(dirname.substr(spos-4).c_str(), "%3x" DIR_DELIM "%3x", &x, &y);
// Sign-extend the 12 bit values up to 16 bits...
if(x&0x800) x|=0xF000;
if(y&0x800) y|=0xF000;
DSTACK(__FUNCTION_NAME);
if(m_map_saving_enabled == false)
{
- dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
+ infostream<<"WARNING: Not saving map, saving disabled."<<std::endl;
return;
}
if(only_changed == false)
- dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
+ infostream<<"ServerMap: Saving whole map, this can take time."
<<std::endl;
if(only_changed == false || m_map_metadata_changed)
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;
saveBlock(block);
block_count++;
- /*dstream<<"ServerMap: Written block ("
+ /*infostream<<"ServerMap: Written block ("
<<block->getPos().X<<","
<<block->getPos().Y<<","
<<block->getPos().Z<<")"
<<std::endl;*/
}
+ //sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL);
}
}
+ endSave();
/*
Only print if something happened or saved whole map
if(only_changed == false || sector_meta_count != 0
|| block_count != 0)
{
- dstream<<DTIME<<"ServerMap: Written: "
+ infostream<<"ServerMap: Written: "
<<sector_meta_count<<" sector metadata files, "
<<block_count<<" block files"
<<", "<<block_count_all<<" blocks in memory."
}
}
+static s32 unsignedToSigned(s32 i, s32 max_positive)
+{
+ if(i < max_positive)
+ return i;
+ else
+ return i - 2*max_positive;
+}
+
+// modulo of a negative number does not work consistently in C
+static sqlite3_int64 pythonmodulo(sqlite3_int64 i, sqlite3_int64 mod)
+{
+ if(i >= 0)
+ return i % mod;
+ return mod - ((-i) % mod);
+}
+
+v3s16 ServerMap::getIntegerAsBlock(sqlite3_int64 i)
+{
+ s32 x = unsignedToSigned(pythonmodulo(i, 4096), 2048);
+ i = (i - x) / 4096;
+ s32 y = unsignedToSigned(pythonmodulo(i, 4096), 2048);
+ i = (i - y) / 4096;
+ s32 z = unsignedToSigned(pythonmodulo(i, 4096), 2048);
+ return v3s16(x,y,z);
+}
+
+void ServerMap::listAllLoadableBlocks(core::list<v3s16> &dst)
+{
+ if(loadFromFolders()){
+ errorstream<<"Map::listAllLoadableBlocks(): Result will be missing "
+ <<"all blocks that are stored in flat files"<<std::endl;
+ }
+
+ {
+ verifyDatabase();
+
+ while(sqlite3_step(m_database_list) == SQLITE_ROW)
+ {
+ sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
+ v3s16 p = getIntegerAsBlock(block_i);
+ //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
+ dst.push_back(p);
+ }
+ }
+}
+
void ServerMap::saveMapMeta()
{
DSTACK(__FUNCTION_NAME);
- dstream<<"INFO: ServerMap::saveMapMeta(): "
+ infostream<<"ServerMap::saveMapMeta(): "
<<"seed="<<m_seed
<<std::endl;
createDirs(m_savedir);
- std::string fullpath = m_savedir + "/map_meta.txt";
+ std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt";
std::ofstream os(fullpath.c_str(), std::ios_base::binary);
if(os.good() == false)
{
- dstream<<"ERROR: ServerMap::saveMapMeta(): "
+ infostream<<"ERROR: ServerMap::saveMapMeta(): "
<<"could not open"<<fullpath<<std::endl;
throw FileNotGoodException("Cannot open chunk metadata");
}
{
DSTACK(__FUNCTION_NAME);
- dstream<<"INFO: ServerMap::loadMapMeta(): Loading map metadata"
+ infostream<<"ServerMap::loadMapMeta(): Loading map metadata"
<<std::endl;
- std::string fullpath = m_savedir + "/map_meta.txt";
+ std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt";
std::ifstream is(fullpath.c_str(), std::ios_base::binary);
if(is.good() == false)
{
- dstream<<"ERROR: ServerMap::loadMapMeta(): "
+ infostream<<"ERROR: ServerMap::loadMapMeta(): "
<<"could not open"<<fullpath<<std::endl;
throw FileNotGoodException("Cannot open map metadata");
}
m_seed = params.getU64("seed");
- dstream<<"INFO: ServerMap::loadMapMeta(): "
- <<"seed="<<m_seed
- <<std::endl;
+ infostream<<"ServerMap::loadMapMeta(): "<<"seed="<<m_seed<<std::endl;
}
void ServerMap::saveSectorMeta(ServerMapSector *sector)
std::string dir = getSectorDir(pos);
createDirs(dir);
- std::string fullpath = dir + "/meta";
+ std::string fullpath = dir + DIR_DELIM + "meta";
std::ofstream o(fullpath.c_str(), std::ios_base::binary);
if(o.good() == false)
throw FileNotGoodException("Cannot open sector metafile");
ServerMapSector *sector = NULL;
- std::string fullpath = sectordir + "/meta";
+ std::string fullpath = sectordir + DIR_DELIM + "meta";
std::ifstream is(fullpath.c_str(), std::ios_base::binary);
if(is.good() == false)
{
// format. Just go ahead and create the sector.
if(fs::PathExists(sectordir))
{
- dstream<<"ServerMap::loadSectorMeta(): Sector metafile "
+ /*infostream<<"ServerMap::loadSectorMeta(): Sector metafile "
<<fullpath<<" doesn't exist but directory does."
<<" Continuing with a sector with no metadata."
- <<std::endl;
+ <<std::endl;*/
sector = new ServerMapSector(this, p2d);
m_sectors.insert(p2d, sector);
}
if(loadlayout != 2)
{
- dstream<<"Sector converted to new layout - deleting "<<
+ infostream<<"Sector converted to new layout - deleting "<<
sectordir1<<std::endl;
fs::RecursiveDelete(sectordir1);
}
}
#endif
+void ServerMap::beginSave() {
+ verifyDatabase();
+ if(sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK)
+ infostream<<"WARNING: beginSave() failed, saving might be slow.";
+}
+
+void ServerMap::endSave() {
+ verifyDatabase();
+ if(sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
+ infostream<<"WARNING: endSave() failed, map might not have saved.";
+}
+
void ServerMap::saveBlock(MapBlock *block)
{
DSTACK(__FUNCTION_NAME);
if(block->isDummy())
{
/*v3s16 p = block->getPos();
- dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
+ infostream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
return;
}
// Get destination
v3s16 p3d = block->getPos();
+
+#if 0
v2s16 p2d(p3d.X, p3d.Z);
std::string sectordir = getSectorDir(p2d);
createDirs(sectordir);
- std::string fullpath = sectordir+"/"+getBlockFilename(p3d);
+ std::string fullpath = sectordir+DIR_DELIM+getBlockFilename(p3d);
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)
+ infostream<<"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
+ infostream<<"WARNING: Block data failed to bind: "<<sqlite3_errmsg(m_database)<<std::endl;
+ int written = sqlite3_step(m_database_write);
+ if(written != SQLITE_DONE)
+ infostream<<"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();
}
{
DSTACK(__FUNCTION_NAME);
- std::string fullpath = sectordir+"/"+blockfile;
+ std::string fullpath = sectordir+DIR_DELIM+blockfile;
try{
std::ifstream is(fullpath.c_str(), std::ios_base::binary);
MapBlock *block = NULL;
bool created_new = false;
- try{
- block = sector->getBlockNoCreate(p3d.Y);
- }
- catch(InvalidPositionException &e)
+ block = sector->getBlockNoCreateNoEx(p3d.Y);
+ if(block == NULL)
{
block = sector->createBlankBlockNoInsert(p3d.Y);
created_new = true;
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.
}
catch(SerializationError &e)
{
- dstream<<"WARNING: Invalid block data on disk "
+ infostream<<"WARNING: Invalid block data on disk "
<<"fullpath="<<fullpath
<<" (SerializationError). "
<<"what()="<<e.what()
}
}
+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)
+ {
+ infostream<<"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)
+ infostream<<"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/
*/
std::string blockfilename = getBlockFilename(blockpos);
- if(fs::PathExists(sectordir+"/"+blockfilename) == false)
+ if(fs::PathExists(sectordir+DIR_DELIM+blockfilename) == false)
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());
return sector;
}
+#if 0
void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
{
DSTACK(__FUNCTION_NAME);
sector->deSerialize(is);
}
+#endif
void ClientMap::OnRegisterSceneNode()
{
ISceneNode::OnRegisterSceneNode();
}
+static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac,
+ float start_off, float end_off, u32 needed_count)
+{
+ float d0 = (float)BS * p0.getDistanceFrom(p1);
+ v3s16 u0 = p1 - p0;
+ v3f uf = v3f(u0.X, u0.Y, u0.Z) * BS;
+ uf.normalize();
+ v3f p0f = v3f(p0.X, p0.Y, p0.Z) * BS;
+ u32 count = 0;
+ for(float s=start_off; s<d0+end_off; s+=step){
+ v3f pf = p0f + uf * s;
+ v3s16 p = floatToInt(pf, BS);
+ MapNode n = map->getNodeNoEx(p);
+ bool is_transparent = false;
+ ContentFeatures &f = content_features(n);
+ if(f.solidness == 0)
+ is_transparent = (f.visual_solidness != 2);
+ else
+ is_transparent = (f.solidness != 2);
+ if(!is_transparent){
+ count++;
+ if(count >= needed_count)
+ return true;
+ }
+ step *= stepfac;
+ }
+ return false;
+}
+
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
{
//m_dout<<DTIME<<"Rendering map..."<<std::endl;
bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
+ std::string prefix;
+ if(pass == scene::ESNRP_SOLID)
+ prefix = "CM: solid: ";
+ else
+ prefix = "CM: transparent: ";
+
/*
This is called two times per frame, reset on the non-transparent one
*/
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();
/*
Get all blocks and draw all visible ones
*/
- v3s16 cam_pos_nodes(
- camera_position.X / BS,
- camera_position.Y / BS,
- camera_position.Z / BS);
-
+ v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+
v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
// Take a fair amount as we will be dropping more out later
+ // Umm... these additions are a bit strange but they are needed.
v3s16 p_blocks_min(
- p_nodes_min.X / MAP_BLOCKSIZE - 1,
- p_nodes_min.Y / MAP_BLOCKSIZE - 1,
- p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+ p_nodes_min.X / MAP_BLOCKSIZE - 3,
+ p_nodes_min.Y / MAP_BLOCKSIZE - 3,
+ p_nodes_min.Z / MAP_BLOCKSIZE - 3);
v3s16 p_blocks_max(
- p_nodes_max.X / MAP_BLOCKSIZE,
- p_nodes_max.Y / MAP_BLOCKSIZE,
- p_nodes_max.Z / MAP_BLOCKSIZE);
+ p_nodes_max.X / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Z / MAP_BLOCKSIZE + 1);
u32 vertex_count = 0;
+ u32 meshbuffer_count = 0;
// For limiting number of mesh updates per frame
u32 mesh_update_count = 0;
+ // Number of blocks in rendering range
+ u32 blocks_in_range = 0;
+ // Number of blocks occlusion culled
+ u32 blocks_occlusion_culled = 0;
+ // Number of blocks in rendering range but don't have a mesh
+ u32 blocks_in_range_without_mesh = 0;
+ // Blocks that had mesh that would have been drawn according to
+ // rendering range (if max blocks limit didn't kick in)
u32 blocks_would_have_drawn = 0;
+ // Blocks that were drawn and had a mesh
u32 blocks_drawn = 0;
+ // Blocks which had a corresponding meshbuffer for this pass
+ u32 blocks_had_pass_meshbuf = 0;
+ // Blocks from which stuff was actually drawn
+ u32 blocks_without_stuff = 0;
+
+ /*
+ Collect a set of blocks for drawing
+ */
+
+ core::map<v3s16, MapBlock*> drawset;
- int timecheck_counter = 0;
- core::map<v2s16, MapSector*>::Iterator si;
- si = m_sectors.getIterator();
- for(; si.atEnd() == false; si++)
{
- {
- timecheck_counter++;
- if(timecheck_counter > 50)
- {
- timecheck_counter = 0;
- int time2 = time(0);
- if(time2 > time1 + 4)
- {
- dstream<<"ClientMap::renderMap(): "
- "Rendering takes ages, returning."
- <<std::endl;
- return;
- }
- }
- }
+ ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG);
+ for(core::map<v2s16, MapSector*>::Iterator
+ si = m_sectors.getIterator();
+ si.atEnd() == false; si++)
+ {
MapSector *sector = si.getNode()->getValue();
v2s16 sp = sector->getPos();
sector->getBlocks(sectorblocks);
/*
- Draw blocks
+ Loop through blocks in sector
*/
-
- u32 sector_blocks_drawn = 0;
+ u32 sector_blocks_drawn = 0;
+
core::list< MapBlock * >::Iterator i;
for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
{
float range = 100000 * BS;
if(m_control.range_all == false)
range = m_control.wanted_range * BS;
-
+
float d = 0.0;
if(isBlockInSight(block->getPos(), camera_position,
- camera_direction, range, &d) == false)
+ camera_direction, camera_fov,
+ range, &d) == false)
{
continue;
}
-
+
// This is ugly (spherical distance limit?)
/*if(m_control.range_all == false &&
d - 0.5*BS*MAP_BLOCKSIZE > range)
continue;*/
+ blocks_in_range++;
+
+ // This block is in range. Reset usage timer.
+ block->resetUsageTimer();
+
#if 1
/*
Update expired mesh (used for day/night change)
// Mesh has not been expired and there is no mesh:
// block has no content
- if(block->mesh == NULL && mesh_expired == false)
+ if(block->mesh == NULL && mesh_expired == false){
+ blocks_in_range_without_mesh++;
continue;
+ }
}
f32 faraway = BS*50;
mesh_expired = false;
}
-
#endif
+
+ /*
+ Occlusion culling
+ */
+
+ v3s16 cpn = block->getPos() * MAP_BLOCKSIZE;
+ cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2);
+ float step = BS*1;
+ float stepfac = 1.1;
+ float startoff = BS*1;
+ float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42;
+ v3s16 spn = cam_pos_nodes + v3s16(0,0,0);
+ s16 bs2 = MAP_BLOCKSIZE/2 + 1;
+ u32 needed_count = 1;
+ if(
+ isOccluded(this, spn, cpn + v3s16(0,0,0),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2),
+ step, stepfac, startoff, endoff, needed_count) &&
+ isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2),
+ step, stepfac, startoff, endoff, needed_count)
+ )
+ {
+ blocks_occlusion_culled++;
+ continue;
+ }
+
/*
- Draw the faces of the block
+ Ignore if mesh doesn't exist
*/
{
JMutexAutoLock lock(block->mesh_mutex);
scene::SMesh *mesh = block->mesh;
-
- if(mesh == NULL)
- continue;
- blocks_would_have_drawn++;
- if(blocks_drawn >= m_control.wanted_max_blocks
- && m_control.range_all == false
- && d > m_control.wanted_min_range * BS)
+ if(mesh == NULL){
+ blocks_in_range_without_mesh++;
continue;
+ }
+ }
+
+ // Limit block count in case of a sudden increase
+ blocks_would_have_drawn++;
+ if(blocks_drawn >= m_control.wanted_max_blocks
+ && m_control.range_all == false
+ && d > m_control.wanted_min_range * BS)
+ continue;
+
+ // Add to set
+ drawset[block->getPos()] = block;
+
+ sector_blocks_drawn++;
+ blocks_drawn++;
+
+ } // foreach sectorblocks
- blocks_drawn++;
- sector_blocks_drawn++;
+ if(sector_blocks_drawn != 0)
+ m_last_drawn_sectors[sp] = true;
+ }
+ } // ScopeProfiler
+
+ /*
+ Draw the selected MapBlocks
+ */
- u32 c = mesh->getMeshBufferCount();
+ {
+ ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);
- for(u32 i=0; i<c; i++)
+ int timecheck_counter = 0;
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = drawset.getIterator();
+ i.atEnd() == false; i++)
+ {
+ {
+ timecheck_counter++;
+ if(timecheck_counter > 50)
+ {
+ timecheck_counter = 0;
+ int time2 = time(0);
+ if(time2 > time1 + 4)
{
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
- const video::SMaterial& material = buf->getMaterial();
- video::IMaterialRenderer* rnd =
- driver->getMaterialRenderer(material.MaterialType);
- bool transparent = (rnd && rnd->isTransparent());
- // Render transparent on transparent pass and likewise.
- if(transparent == is_transparent_pass)
- {
- /*
- This *shouldn't* hurt too much because Irrlicht
- doesn't change opengl textures if the old
- material is set again.
- */
- driver->setMaterial(buf->getMaterial());
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
- }
+ infostream<<"ClientMap::renderMap(): "
+ "Rendering takes ages, returning."
+ <<std::endl;
+ return;
}
}
- } // foreach sectorblocks
+ }
+
+ MapBlock *block = i.getNode()->getValue();
- if(sector_blocks_drawn != 0)
+ /*
+ Draw the faces of the block
+ */
{
- m_last_drawn_sectors[sp] = true;
+ JMutexAutoLock lock(block->mesh_mutex);
+
+ scene::SMesh *mesh = block->mesh;
+ assert(mesh);
+
+ u32 c = mesh->getMeshBufferCount();
+ bool stuff_actually_drawn = false;
+ for(u32 i=0; i<c; i++)
+ {
+ scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+ const video::SMaterial& material = buf->getMaterial();
+ video::IMaterialRenderer* rnd =
+ driver->getMaterialRenderer(material.MaterialType);
+ bool transparent = (rnd && rnd->isTransparent());
+ // Render transparent on transparent pass and likewise.
+ if(transparent == is_transparent_pass)
+ {
+ if(buf->getVertexCount() == 0)
+ errorstream<<"Block ["<<analyze_block(block)
+ <<"] contains an empty meshbuf"<<std::endl;
+ /*
+ This *shouldn't* hurt too much because Irrlicht
+ doesn't change opengl textures if the old
+ material has the same texture.
+ */
+ driver->setMaterial(buf->getMaterial());
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getVertexCount();
+ meshbuffer_count++;
+ stuff_actually_drawn = true;
+ }
+ }
+ if(stuff_actually_drawn)
+ blocks_had_pass_meshbuf++;
+ else
+ blocks_without_stuff++;
}
}
+ } // ScopeProfiler
+
+ // Log only on solid pass because values are the same
+ if(pass == scene::ESNRP_SOLID){
+ g_profiler->avg("CM: blocks in range", blocks_in_range);
+ g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
+ if(blocks_in_range != 0)
+ g_profiler->avg("CM: blocks in range without mesh (frac)",
+ (float)blocks_in_range_without_mesh/blocks_in_range);
+ g_profiler->avg("CM: blocks drawn", blocks_drawn);
+ }
+ g_profiler->avg(prefix+"vertices drawn", vertex_count);
+ if(blocks_had_pass_meshbuf != 0)
+ g_profiler->avg(prefix+"meshbuffers per block",
+ (float)meshbuffer_count / (float)blocks_had_pass_meshbuf);
+ if(blocks_drawn != 0)
+ g_profiler->avg(prefix+"empty blocks (frac)",
+ (float)blocks_without_stuff / blocks_drawn);
+
m_control.blocks_drawn = blocks_drawn;
m_control.blocks_would_have_drawn = blocks_would_have_drawn;
- /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
+ /*infostream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
<<", 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)
{
MapVoxelManipulator::~MapVoxelManipulator()
{
- /*dstream<<"MapVoxelManipulator: blocks: "<<m_loaded_blocks.size()
+ /*infostream<<"MapVoxelManipulator: blocks: "<<m_loaded_blocks.size()
<<std::endl;*/
}
{
TimeTaker timer1("emerge load", &emerge_load_time);
- /*dstream<<"Loading block (caller_id="<<caller_id<<")"
+ /*infostream<<"Loading block (caller_id="<<caller_id<<")"
<<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
<<" wanted area: ";
- a.print(dstream);
- dstream<<std::endl;*/
+ a.print(infostream);
+ infostream<<std::endl;*/
MapBlock *block = m_map->getBlockNoCreate(p);
if(block->isDummy())
m_loaded_blocks.insert(p, !block_data_inexistent);
}
- //dstream<<"emerge done"<<std::endl;
+ //infostream<<"emerge done"<<std::endl;
}
/*
//TimeTaker timer1("blitBack");
- /*dstream<<"blitBack(): m_loaded_blocks.size()="
+ /*infostream<<"blitBack(): m_loaded_blocks.size()="
<<m_loaded_blocks.size()<<std::endl;*/
/*
u32 size_MB = block_area_nodes.getVolume()*4/1000000;
if(size_MB >= 1)
{
- dstream<<"initialEmerge: area: ";
- block_area_nodes.print(dstream);
- dstream<<" ("<<size_MB<<"MB)";
- dstream<<std::endl;
+ infostream<<"initialEmerge: area: ";
+ block_area_nodes.print(infostream);
+ infostream<<" ("<<size_MB<<"MB)";
+ infostream<<std::endl;
}
addArea(block_area_nodes);
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
+ /*infostream<<"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)
{
- dstream<<"WARNING: "<<__FUNCTION_NAME
+ infostream<<"WARNING: "<<__FUNCTION_NAME
<<": got NULL block "
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
<<std::endl;