/* Minetest-c55 Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "map.h" #include "mapsector.h" #include "mapblock.h" #include "main.h" #include "client.h" #include "filesys.h" #include "utility.h" #include "voxel.h" #include "porting.h" #include "mapgen.h" #include "nodemetadata.h" extern "C" { #include "sqlite3.h" } /* SQLite format specification: - Initially only replaces sectors/ and sectors2/ */ /* Map */ Map::Map(std::ostream &dout): m_dout(dout), m_sector_cache(NULL) { /*m_sector_mutex.Init(); assert(m_sector_mutex.IsInitialized());*/ } Map::~Map() { /* Free all MapSectors */ core::map::Iterator i = m_sectors.getIterator(); for(; i.atEnd() == false; i++) { MapSector *sector = i.getNode()->getValue(); delete sector; } } void Map::addEventReceiver(MapEventReceiver *event_receiver) { m_event_receivers.insert(event_receiver, false); } void Map::removeEventReceiver(MapEventReceiver *event_receiver) { if(m_event_receivers.find(event_receiver) == NULL) return; m_event_receivers.remove(event_receiver); } void Map::dispatchEvent(MapEditEvent *event) { for(core::map::Iterator i = m_event_receivers.getIterator(); i.atEnd()==false; i++) { MapEventReceiver* event_receiver = i.getNode()->getKey(); event_receiver->onMapEditEvent(event); } } MapSector * Map::getSectorNoGenerateNoExNoLock(v2s16 p) { if(m_sector_cache != NULL && p == m_sector_cache_p){ MapSector * sector = m_sector_cache; return sector; } core::map::Node *n = m_sectors.find(p); if(n == NULL) return NULL; MapSector *sector = n->getValue(); // Cache the last result m_sector_cache_p = p; m_sector_cache = sector; return sector; } MapSector * Map::getSectorNoGenerateNoEx(v2s16 p) { return getSectorNoGenerateNoExNoLock(p); } MapSector * Map::getSectorNoGenerate(v2s16 p) { MapSector *sector = getSectorNoGenerateNoEx(p); if(sector == NULL) throw InvalidPositionException(); return sector; } MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d) { 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::getBlockNoCreate(v3s16 p3d) { MapBlock *block = getBlockNoCreateNoEx(p3d); if(block == NULL) throw InvalidPositionException(); return block; } bool Map::isNodeUnderground(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); try{ MapBlock * block = getBlockNoCreate(blockpos); return block->getIsUnderground(); } catch(InvalidPositionException &e) { return false; } } 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. Al/* Minetest-c55 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef GETTIME_HEADER #define GETTIME_HEADER #include "common_irrlicht.h" /* Get a millisecond counter value. Precision depends on implementation. Overflows at any value above 10000000. Implementation of this is done in: Normal build: main.cpp Server build: servermain.cpp */ extern u32 getTimeMs(); /* Timestamp stuff */ #include <time.h> #include <string> inline std::string getTimestamp() { time_t t = time(NULL); // This is not really thread-safe but it won't break anything // except its own output, so just go with it. struct tm *tm = localtime(&t); char cs[20]; strftime(cs, 20, "%H:%M:%S", tm); return cs; } #endif = false) { // If the block is not found in modified_blocks, add. if(modified_blocks.find(blockpos) == NULL) { modified_blocks.insert(blockpos, block); } block_checked_in_modified = true; } } catch(InvalidPositionException &e) { continue; } } } /*dstream<<"unspreadLight(): Changed block " < 0) unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks); } /* A single-node wrapper of the above */ void Map::unLightNeighbors(enum LightBank bank, v3s16 pos, u8 lightwas, core::map & light_sources, core::map & modified_blocks) { core::map from_nodes; from_nodes.insert(pos, lightwas); unspreadLight(bank, from_nodes, light_sources, modified_blocks); } /* Lights neighbors of from_nodes, collects all them and then goes on recursively. */ void Map::spreadLight(enum LightBank bank, core::map & from_nodes, core::map & modified_blocks) { const 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 }; if(from_nodes.size() == 0) return; u32 blockchangecount = 0; core::map lighted_nodes; core::map::Iterator j; j = from_nodes.getIterator(); /* Initialize block cache */ v3s16 blockpos_last; MapBlock *block = NULL; // Cache this a bit, too bool block_checked_in_modified = false; for(; j.atEnd() == false; j++) //for(; j != from_nodes.end(); j++) { v3s16 pos = j.getNode()->getKey(); //v3s16 pos = *j; //dstream<<"pos=("<isDummy()) continue; // Calculate relative position in block v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE; // Get node straight from the block MapNode n = block->getNode(relpos); u8 oldlight = n.getLight(bank); u8 newlight = diminish_light(oldlight); // Loop through 6 neighbors for(u16 i=0; i<6; i++){ // Get the position of the neighbor node v3s16 n2pos = pos + dirs[i]; // Get the block where the node is located v3s16 blockpos = getNodeBlockPos(n2pos); try { // Only fetch a new block if the block position has changed try{ if(block == NULL || blockpos != blockpos_last){ block = getBlockNoCreate(blockpos); blockpos_last = blockpos; block_checked_in_modified = false; blockchangecount++; } } catch(InvalidPositionException &e) { continue; } // Calculate relative position in block v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE; // Get node straight from the block MapNode n2 = block->getNode(relpos); bool changed = false; /* If the neighbor is brighter than the current node, add to list (it will light up this node on its turn) */ if(n2.getLight(bank) > undiminish_light(oldlight)) { lighted_nodes.insert(n2pos, true); //lighted_nodes.push_back(n2pos); changed = true; } /* If the neighbor is dimmer than how much light this node would spread on it, add to list */ if(n2.getLight(bank) < newlight) { if(n2.light_propagates()) { n2.setLight(bank, newlight); block->setNode(relpos, n2); lighted_nodes.insert(n2pos, true); //lighted_nodes.push_back(n2pos); changed = true; } } // Add to modified_blocks if(changed == true && block_checked_in_modified == false) { // If the block is not found in modified_blocks, add. if(modified_blocks.find(blockpos) == NULL) { modified_blocks.insert(blockpos, block); } block_checked_in_modified = true; } } catch(InvalidPositionException &e) { continue; } } } /*dstream<<"spreadLight(): Changed block " < 0) spreadLight(bank, lighted_nodes, modified_blocks); } /* A single-node source variation of the above. */ void Map::lightNeighbors(enum LightBank bank, v3s16 pos, core::map & modified_blocks) { core::map from_nodes; from_nodes.insert(pos, true); spreadLight(bank, from_nodes, modified_blocks); } v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p) { 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 }; u8 brightest_light = 0; v3s16 brightest_pos(0,0,0); bool found_something = false; // Loop through 6 neighbors for(u16 i=0; i<6; i++){ // Get the position of the neighbor node v3s16 n2pos = p + dirs[i]; MapNode n2; try{ n2 = getNode(n2pos); } catch(InvalidPositionException &e) { continue; } if(n2.getLight(bank) > brightest_light || found_something == false){ brightest_light = n2.getLight(bank); brightest_pos = n2pos; found_something = true; } } if(found_something == false) throw InvalidPositionException(); return brightest_pos; } /* Propagates sunlight down from a node. Starting point gets sunlight. Returns the lowest y value of where the sunlight went. Mud is turned into grass in where the sunlight stops. */ s16 Map::propagateSunlight(v3s16 start, core::map & modified_blocks) { s16 y = start.Y; for(; ; y--) { v3s16 pos(start.X, y, start.Z); v3s16 blockpos = getNodeBlockPos(pos); MapBlock *block; try{ block = getBlockNoCreate(blockpos); } catch(InvalidPositionException &e) { break; } v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE; MapNode n = block->getNode(relpos); if(n.sunlight_propagates()) { n.setLight(LIGHTBANK_DAY, LIGHT_SUN); block->setNode(relpos, n); modified_blocks.insert(blockpos, block); } else { /*// Turn mud into grass if(n.d == CONTENT_MUD) { n.d = CONTENT_GRASS; block->setNode(relpos, n); modified_blocks.insert(blockpos, block); }*/ // Sunlight goes no further break; } } return y + 1; } void Map::updateLighting(enum LightBank bank, core::map & a_blocks, core::map & modified_blocks) { /*m_dout< blocks_to_update; core::map light_sources; core::map unlight_from; core::map::Iterator i; i = a_blocks.getIterator(); for(; i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); for(;;) { // Don't bother with dummy blocks. if(block->isDummy()) break; v3s16 pos = block->getPos(); modified_blocks.insert(pos, block); blocks_to_update.insert(pos, block); /* Clear all light from block */ for(s16 z=0; zgetNode(v3s16(x,y,z)); u8 oldlight = n.getLight(bank); n.setLight(bank, 0); block->setNode(v3s16(x,y,z), n); // Collect borders for unlighting if(x==0 || x == MAP_BLOCKSIZE-1 || y==0 || y == MAP_BLOCKSIZE-1 || z==0 || z == MAP_BLOCKSIZE-1) { v3s16 p_map = p + v3s16( MAP_BLOCKSIZE*pos.X, MAP_BLOCKSIZE*pos.Y, MAP_BLOCKSIZE*pos.Z); unlight_from.insert(p_map, oldlight); } } catch(InvalidPositionException &e) { /* This would happen when dealing with a dummy block. */ //assert(0); dstream<<"updateLighting(): InvalidPositionException" <propagateSunlight(light_sources); // If bottom is valid, we're done. if(bottom_valid) break; } else if(bank == LIGHTBANK_NIGHT) { // For night lighting, sunlight is not propagated break; } else { // Invalid lighting bank assert(0); } /*dstream<<"Bottom for sunlight-propagated block (" <::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 { TimeTaker timer("unspreadLight"); unspreadLight(bank, unlight_from, light_sources, modified_blocks); } if(debug) { u32 diff = modified_blocks.size() - count_was; count_was = modified_blocks.size(); dstream<<"unspreadLight modified "<::Iterator i; i = blocks_to_update.getIterator(); for(; i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); v3s16 p = block->getPos(); // Add all surrounding blocks vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1)); /* Add all surrounding blocks that have up-to-date lighting NOTE: This doesn't quite do the job (not everything appropriate is lighted) */ /*for(s16 z=-1; z<=1; z++) for(s16 y=-1; y<=1; y++) for(s16 x=-1; x<=1; x++) { v3s16 p(x,y,z); MapBlock *block = getBlockNoCreateNoEx(p); if(block == NULL) continue; if(block->isDummy()) continue; if(block->getLightingExpired()) continue; vmanip.initialEmerge(p, p); }*/ // Lighting of block will be updated completely block->setLightingExpired(false); } { //TimeTaker timer("unSpreadLight"); vmanip.unspreadLight(bank, unlight_from, light_sources); } { //TimeTaker timer("spreadLight"); vmanip.spreadLight(bank, light_sources); } { //TimeTaker timer("blitBack"); vmanip.blitBack(modified_blocks); } /*dstream<<"emerge_time="< & a_blocks, core::map & modified_blocks) { updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks); updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks); /* Update information about whether day and night light differ */ for(core::map::Iterator i = modified_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); block->updateDayNightDiff(); } } /* */ void Map::addNodeAndUpdate(v3s16 p, MapNode n, core::map &modified_blocks) { /*PrintInfo(m_dout); m_dout< light_sources; /* If there is a node at top and it doesn't have sunlight, there has not been any sunlight going down. Otherwise there probably is. */ try{ MapNode topnode = getNode(toppos); if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN) node_under_sunlight = false; } catch(InvalidPositionException &e) { } #if 0 /* If the new node is solid and there is grass below, change it to mud */ if(content_features(n.d).walkable == true) { try{ MapNode bottomnode = getNode(bottompos); if(bottomnode.d == CONTENT_GRASS || bottomnode.d == CONTENT_GRASS_FOOTSTEPS) { bottomnode.d = CONTENT_MUD; setNode(bottompos, bottomnode); } } catch(InvalidPositionException &e) { } } #endif #if 0 /* If the new node is mud and it is under sunlight, change it to grass */ if(n.d == CONTENT_MUD && node_under_sunlight) { n.d = CONTENT_GRASS; } #endif /* Remove all light that has come out of this node */ enum LightBank banks[] = { LIGHTBANK_DAY, LIGHTBANK_NIGHT }; for(s32 i=0; i<2; i++) { enum LightBank bank = banks[i]; u8 lightwas = getNode(p).getLight(bank); // Add the block of the added node to modified_blocks v3s16 blockpos = getNodeBlockPos(p); MapBlock * block = getBlockNoCreate(blockpos); assert(block != NULL); modified_blocks.insert(blockpos, block); assert(isValidPosition(p)); // Unlight neighbours of node. // This means setting light of all consequent dimmer nodes // to 0. // This also collects the nodes at the border which will spread // light again into this. unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks); n.setLight(bank, 0); } /* If node lets sunlight through and is under sunlight, it has sunlight too. */ if(node_under_sunlight && content_features(n.d).sunlight_propagates) { n.setLight(LIGHTBANK_DAY, LIGHT_SUN); } /* Set the node on the map */ setNode(p, n); /* Add intial metadata */ NodeMetadata *meta_proto = content_features(n.d).initial_metadata; if(meta_proto) { NodeMetadata *meta = meta_proto->clone(); setNodeMetadata(p, meta); } /* If node is under sunlight and doesn't let sunlight through, take all sunlighted nodes under it and clear light from them and from where the light has been spread. TODO: This could be optimized by mass-unlighting instead of looping */ if(node_under_sunlight && !content_features(n.d).sunlight_propagates) { s16 y = p.Y - 1; for(;; y--){ //m_dout<::Iterator i = modified_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); block->updateDayNightDiff(); } /* Add neighboring liquid nodes and the node itself if it is liquid (=water node was added) to transform queue. */ v3s16 dirs[7] = { v3s16(0,0,0), // self 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<7; i++) { try { v3s16 p2 = p + dirs[i]; MapNode n2 = getNode(p2); if(content_liquid(n2.d) || n2.d == CONTENT_AIR) { m_transforming_liquid.push_back(p2); } }catch(InvalidPositionException &e) { } } } /* */ void Map::removeNodeAndUpdate(v3s16 p, core::map &modified_blocks) { /*PrintInfo(m_dout); m_dout< light_sources; enum LightBank banks[] = { LIGHTBANK_DAY, LIGHTBANK_NIGHT }; for(s32 i=0; i<2; i++) { enum LightBank bank = banks[i]; /* Unlight neighbors (in case the node is a light source) */ unLightNeighbors(bank, p, getNode(p).getLight(bank), light_sources, modified_blocks); } /* Remove node metadata */ removeNodeMetadata(p); /* Remove the node. This also clears the lighting. */ MapNode n; n.d = replace_material; setNode(p, n); for(s32 i=0; i<2; i++) { enum LightBank bank = banks[i]; /* Recalculate lighting */ spreadLight(bank, light_sources, modified_blocks); } // Add the block of the removed node to modified_blocks v3s16 blockpos = getNodeBlockPos(p); MapBlock * block = getBlockNoCreate(blockpos); assert(block != NULL); modified_blocks.insert(blockpos, block); /* If the removed node was under sunlight, propagate the sunlight down from it and then light all neighbors of the propagated blocks. */ if(node_under_sunlight) { s16 ybottom = propagateSunlight(p, modified_blocks); /*m_dout< ybottom="<= ybottom; y--) { v3s16 p2(p.X, y, p.Z); /*m_dout<::Iterator i = modified_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); block->updateDayNightDiff(); } /* Add neighboring liquid nodes and this node to transform queue. (it's vital for the node itself to get updated last.) */ 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<7; i++) { try { v3s16 p2 = p + dirs[i]; MapNode n2 = getNode(p2); if(content_liquid(n2.d) || n2.d == CONTENT_AIR) { m_transforming_liquid.push_back(p2); } }catch(InvalidPositionException &e) { } } } bool Map::addNodeWithEvent(v3s16 p, MapNode n) { MapEditEvent event; event.type = MEET_ADDNODE; event.p = p; event.n = n; bool succeeded = true; try{ core::map modified_blocks; addNodeAndUpdate(p, n, modified_blocks); // Copy modified_blocks to event for(core::map::Iterator i = modified_blocks.getIterator(); i.atEnd()==false; i++) { event.modified_blocks.insert(i.getNode()->getKey(), false); } } catch(InvalidPositionException &e){ succeeded = false; } dispatchEvent(&event); return succeeded; } bool Map::removeNodeWithEvent(v3s16 p) { MapEditEvent event; event.type = MEET_REMOVENODE; event.p = p; bool succeeded = true; try{ core::map modified_blocks; removeNodeAndUpdate(p, modified_blocks); // Copy modified_blocks to event for(core::map::Iterator i = modified_blocks.getIterator(); i.atEnd()==false; i++) { event.modified_blocks.insert(i.getNode()->getKey(), false); } } catch(InvalidPositionException &e){ succeeded = false; } dispatchEvent(&event); return succeeded; } bool Map::dayNightDiffed(v3s16 blockpos) { try{ v3s16 p = blockpos + v3s16(0,0,0); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} // Leading edges try{ v3s16 p = blockpos + v3s16(-1,0,0); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} try{ v3s16 p = blockpos + v3s16(0,-1,0); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} try{ v3s16 p = blockpos + v3s16(0,0,-1); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} // Trailing edges try{ v3s16 p = blockpos + v3s16(1,0,0); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} try{ v3s16 p = blockpos + v3s16(0,1,0); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} try{ v3s16 p = blockpos + v3s16(0,0,1); MapBlock *b = getBlockNoCreate(p); if(b->dayNightDiffed()) return true; } catch(InvalidPositionException &e){} return false; } /* Updates usage timers */ void Map::timerUpdate(float dtime, float unload_timeout, core::list *unloaded_blocks) { bool save_before_unloading = (mapType() == MAPTYPE_SERVER); core::list sector_deletion_queue; u32 deleted_blocks_count = 0; u32 saved_blocks_count = 0; core::map::Iterator si; si = m_sectors.getIterator(); for(; si.atEnd() == false; si++) { MapSector *sector = si.getNode()->getValue(); bool all_blocks_deleted = true; core::list blocks; sector->getBlocks(blocks); for(core::list::Iterator i = blocks.begin(); i != blocks.end(); i++) { 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()); } } // Finally delete the empty sectors deleteSectors(sector_deletion_queue); if(deleted_blocks_count != 0) { PrintInfo(dstream); // ServerMap/ClientMap: dstream<<"Unloaded "< &list) { core::list::Iterator j; for(j=list.begin(); j!=list.end(); j++) { MapSector *sector = m_sectors[*j]; // 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 0 void Map::unloadUnusedData(float timeout, core::list *deleted_blocks) { core::list sector_deletion_queue; u32 deleted_blocks_count = 0; u32 saved_blocks_count = 0; core::map::Iterator si = m_sectors.getIterator(); for(; si.atEnd() == false; si++) { MapSector *sector = si.getNode()->getValue(); bool all_blocks_deleted = true; core::list blocks; sector->getBlocks(blocks); for(core::list::Iterator i = blocks.begin(); i != blocks.end(); i++) { MapBlock *block = (*i); if(block->getUsageTimer() > timeout) { // Save if modified if(block->getModified() != MOD_STATE_CLEAN) { saveBlock(block); saved_blocks_count++; } // Delete from memory sector->deleteBlock(block); deleted_blocks_count++; } else { all_blocks_deleted = false; } } if(all_blocks_deleted) { sector_deletion_queue.push_back(si.getNode()->getKey()); } } deleteSectors(sector_deletion_queue); dstream<<"Map: Unloaded "< & modified_blocks) { DSTACK(__FUNCTION_NAME); //TimeTaker timer("transformLiquids()"); u32 loopcount = 0; u32 initial_size = m_transforming_liquid.size(); /*if(initial_size != 0) dstream<<"transformLiquids(): initial_size="<= 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; new_node_level = 7; } 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; } 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; } 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; } 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 || ((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) { // 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; } setNode(p0, n0); v3s16 blockpos = getNodeBlockPos(p0); MapBlock *block = getBlockNoCreateNoEx(blockpos); if(block != NULL) modified_blocks.insert(blockpos, block); /* enqueue neighbors for update if neccessary */ switch (content_features(n0.d).liquid_type) { case LIQUID_SOURCE: // 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; 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="<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" <m_node_metadata.set(p_rel, meta); } void Map::removeNodeMetadata(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); if(block == NULL) { dstream<<"WARNING: Map::removeNodeMetadata(): Block not found" <m_node_metadata.remove(p_rel); } void Map::nodeMetadataStep(float dtime, core::map &changed_blocks) { /* NOTE: Currently there is no way to ensure that all the necessary blocks are loaded when this is run. (They might get unloaded) NOTE: ^- Actually, that might not be so. In a quick test it reloaded a block with a furnace when I walked back to it from a distance. */ core::map::Iterator si; si = m_sectors.getIterator(); for(; si.atEnd() == false; si++) { MapSector *sector = si.getNode()->getValue(); core::list< MapBlock * > sectorblocks; sector->getBlocks(sectorblocks); core::list< MapBlock * >::Iterator i; for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) { MapBlock *block = *i; bool changed = block->m_node_metadata.step(dtime); if(changed) changed_blocks[block->getPos()] = block; } } } /* ServerMap */ ServerMap::ServerMap(std::string savedir): Map(dout_server), m_seed(0), m_map_metadata_changed(true) { dstream<<__FUNCTION_NAME<::Iterator i = m_chunks.getIterator(); for(; i.atEnd() == false; i++) { MapChunk *chunk = i.getNode()->getValue(); delete chunk; } #endif } void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos) { /*dstream<<"initBlockMake(): ("<no_op = true; return; } data->no_op = false; data->seed = m_seed; data->blockpos = blockpos; /* Create the whole area of this and the neighboring blocks */ { //TimeTaker timer("initBlockMake() create area"); for(s16 x=-1; x<=1; x++) for(s16 z=-1; z<=1; z++) { v2s16 sectorpos(blockpos.X+x, blockpos.Z+z); // Sector metadata is loaded from disk if not already loaded. ServerMapSector *sector = createSector(sectorpos); assert(sector); for(s16 y=-1; y<=1; y++) { //MapBlock *block = createBlock(blockpos); // 1) get from memory, 2) load from disk MapBlock *block = emergeBlock(blockpos, false); // 3) create a blank one if(block == NULL) block = createBlock(blockpos); // 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); } } } /* Now we have a big empty area. Make a ManualMapVoxelManipulator that contains this and the 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); data->vmanip = new ManualMapVoxelManipulator(this); //data->vmanip->setMap(this); // Add the area { //TimeTaker timer("initBlockMake() initialEmerge"); data->vmanip->initialEmerge(bigarea_blocks_min, bigarea_blocks_max); } // Data is ready now. } MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data, core::map &changed_blocks) { v3s16 blockpos = data->blockpos; /*dstream<<"finishBlockMake(): ("<no_op) { //dstream<<"finishBlockMake(): no-op"<vmanip.print(dstream);*/ /* Blit generated stuff to map NOTE: blitBackAll adds nearly everything to changed_blocks */ { // 70ms @cs=8 //TimeTaker timer("finishBlockMake() blitBackAll"); data->vmanip->blitBackAll(&changed_blocks); } if(enable_mapgen_debug_info) dstream<<"finishBlockMake: changed_blocks.size()=" <transforming_liquid.size() > 0) { v3s16 p = data->transforming_liquid.pop_front(); m_transforming_liquid.push_back(p); } /* Get central block */ MapBlock *block = getBlockNoCreateNoEx(data->blockpos); assert(block); /* Set is_underground flag for lighting with sunlight */ block->setIsUnderground(mapgen::block_is_underground(data->seed, blockpos)); /* Add sunlight to central block. This makes in-dark-spawning monsters to not flood the whole thing. Do not spread the light, though. */ /*core::map light_sources; bool black_air_left = false; block->propagateSunlight(light_sources, true, &black_air_left);*/ /* 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, 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 lighting_update_blocks; #if 1 // Center block lighting_update_blocks.insert(block->getPos(), block); #endif #if 0 // 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::Iterator i = changed_blocks.getIterator(); i.atEnd() == false; i++) { lighting_update_blocks.insert(i.getNode()->getKey(), i.getNode()->getValue()); } #endif updateLighting(lighting_update_blocks, changed_blocks); if(enable_mapgen_debug_info == false) t.stop(true); // Hide output } /* Add random objects to block */ mapgen::add_random_objects(block); /* Go through changed blocks */ for(core::map::Iterator i = changed_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); assert(block); /* Update day/night difference cache of the MapBlocks */ block->updateDayNightDiff(); /* Set block as modified */ block->raiseModified(MOD_STATE_WRITE_NEEDED); } /* Set central block as generated */ block->setGenerated(true); /* Save changed parts of map NOTE: Will be saved later. */ //save(true); /*dstream<<"finishBlockMake() done for ("< MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) throw InvalidPositionException("createSector(): pos. over limit"); /* Generate blank sector */ sector = new ServerMapSector(this, p2d); // Sector position on map in nodes v2s16 nodepos2d = p2d * MAP_BLOCKSIZE; /* Insert to container */ m_sectors.insert(p2d, sector); return sector; } /* This is a quick-hand function for calling makeBlock(). */ MapBlock * ServerMap::generateBlock( v3s16 p, core::map &modified_blocks ) { DSTACKF("%s: p=(%d,%d,%d)", __FUNCTION_NAME, p.X, p.Y, p.Z); /*dstream<<"generateBlock(): " <<"("<getNode(p); if(n.d == CONTENT_IGNORE) { dstream<<"CONTENT_IGNORE at " <<"("<setNode(v3s16(x0,y0,z0), n); } } } #endif if(enable_mapgen_debug_info == false) timer.stop(true); // Hide output return block; } MapBlock * ServerMap::createBlock(v3s16 p) { DSTACKF("%s: p=(%d,%d,%d)", __FUNCTION_NAME, p.X, p.Y, p.Z); /* Do not create 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("createBlock(): 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. NOTE: On old save formats, this will be slow, as it generates lighting on blocks for them. */ ServerMapSector *sector; try{ sector = (ServerMapSector*)createSector(p2d); assert(sector->getId() == MAPSECTOR_SERVER); } catch(InvalidPositionException &e) { dstream<<"createBlock: createSector() failed"<getBlockNoCreateNoEx(block_y); if(block) { if(block->isDummy()) block->unDummify(); return block; } // Create blank block = sector->createBlankBlock(block_y); return block; } MapBlock * ServerMap::emergeBlock(v3s16 p, bool allow_generate) { DSTACKF("%s: p=(%d,%d,%d), allow_generate=%d", __FUNCTION_NAME, p.X, p.Y, p.Z, allow_generate); { MapBlock *block = getBlockNoCreateNoEx(p); if(block) return block; } { MapBlock *block = loadBlock(p); if(block) return block; } if(allow_generate) { core::map modified_blocks; MapBlock *block = generateBlock(p, modified_blocks); if(block) { MapEditEvent event; event.type = MEET_OTHER; event.p = p; // Copy modified_blocks to event for(core::map::Iterator i = modified_blocks.getIterator(); i.atEnd()==false; i++) { event.modified_blocks.insert(i.getNode()->getKey(), false); } // Queue event dispatchEvent(&event); return block; } } return NULL; } #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: " <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"<insertBlock(block); } // Done. return block; } //dstream<<"Not found on disk, generating."< making one"< 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) { lighting_invalidated_blocks[block->getPos()] = block; } if(bottom_invalid) { lighting_invalidated_blocks[block->getPos()] = block; } } #endif return block; } #endif s16 ServerMap::findGroundLevel(v2s16 p2d) { #if 0 /* Uh, just do something random... */ // Find existing map from top to down s16 max=63; s16 min=-64; v3s16 p(p2d.X, max, p2d.Y); for(; p.Y>min; p.Y--) { MapNode n = getNodeNoEx(p); if(n.d != 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) 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) return p.Y; } // Move to plan b plan_b: #endif /* Determine from map generator noise functions */ 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; } void ServerMap::createDirs(std::string path) { if(fs::CreateAllDirs(path) == false) { m_dout<::Iterator i = m_sectors.getIterator(); for(; i.atEnd() == false; i++) { ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue(); assert(sector->getId() == MAPSECTOR_SERVER); if(sector->differs_from_disk || only_changed == false) { saveSectorMeta(sector); sector_meta_count++; } core::list blocks; sector->getBlocks(blocks); core::list::Iterator j; for(j=blocks.begin(); j!=blocks.end(); j++) { MapBlock *block = *j; block_count_all++; if(block->getModified() >= MOD_STATE_WRITE_NEEDED || only_changed == false) { saveBlock(block); block_count++; /*dstream<<"ServerMap: Written block (" <getPos().X<<"," <getPos().Y<<"," <getPos().Z<<")" <serialize(o, version); sector->differs_from_disk = false; } MapSector* ServerMap::loadSectorMeta(std::string sectordir, bool save_after_load) { DSTACK(__FUNCTION_NAME); // Get destination v2s16 p2d = getSectorPos(sectordir); ServerMapSector *sector = NULL; std::string fullpath = sectordir + "/meta"; std::ifstream is(fullpath.c_str(), std::ios_base::binary); if(is.good() == false) { // If the directory exists anyway, it probably is in some old // format. Just go ahead and create the sector. if(fs::PathExists(sectordir)) { /*dstream<<"ServerMap::loadSectorMeta(): Sector metafile " <differs_from_disk = false; return sector; } bool ServerMap::loadSectorMeta(v2s16 p2d) { DSTACK(__FUNCTION_NAME); MapSector *sector = NULL; // The directory layout we're going to load from. // 1 - original sectors/xxxxzzzz/ // 2 - new sectors2/xxx/zzz/ // If we load from anything but the latest structure, we will // immediately save to the new one, and remove the old. int loadlayout = 1; std::string sectordir1 = getSectorDir(p2d, 1); std::string sectordir; if(fs::PathExists(sectordir1)) { sectordir = sectordir1; } else { loadlayout = 2; sectordir = getSectorDir(p2d, 2); } try{ sector = loadSectorMeta(sectordir, loadlayout != 2); } catch(InvalidFilenameException &e) { return false; } catch(FileNotGoodException &e) { return false; } catch(std::exception &e) { return false; } return true; } #if 0 bool ServerMap::loadSectorFull(v2s16 p2d) { DSTACK(__FUNCTION_NAME); MapSector *sector = NULL; // The directory layout we're going to load from. // 1 - original sectors/xxxxzzzz/ // 2 - new sectors2/xxx/zzz/ // If we load from anything but the latest structure, we will // immediately save to the new one, and remove the old. int loadlayout = 1; std::string sectordir1 = getSectorDir(p2d, 1); std::string sectordir; if(fs::PathExists(sectordir1)) { sectordir = sectordir1; } else { loadlayout = 2; sectordir = getSectorDir(p2d, 2); } try{ sector = loadSectorMeta(sectordir, loadlayout != 2); } catch(InvalidFilenameException &e) { return false; } catch(FileNotGoodException &e) { return false; } catch(std::exception &e) { return false; } /* Load blocks */ std::vector list2 = fs::GetDirListing (sectordir); std::vector::iterator i2; for(i2=list2.begin(); i2!=list2.end(); i2++) { // We want files if(i2->dir) continue; try{ loadBlock(sectordir, i2->name, sector, loadlayout != 2); } catch(InvalidFilenameException &e) { // This catches unknown crap in directory } } if(loadlayout != 2) { dstream<<"Sector converted to new layout - deleting "<< sectordir1<isDummy()) { /*v3s16 p = block->getPos(); dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block " <<"("<getPos(); v2s16 p2d(p3d.X, p3d.Z); std::string sectordir = getSectorDir(p2d); createDirs(sectordir); std::string fullpath = sectordir+"/"+getBlockFilename(p3d); std::ofstream o(fullpath.c_str(), std::ios_base::binary); if(o.good() == false) throw FileNotGoodException("Cannot open block data"); /* [0] u8 serialization version [1] data */ o.write((char*)&version, 1); // Write basic data block->serialize(o, version); // Write extra data stored on disk block->serializeDiskExtra(o, version); // We just wrote it to the disk so clear modified flag block->resetModified(); } void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector, bool save_after_load) { DSTACK(__FUNCTION_NAME); std::string fullpath = sectordir+"/"+blockfile; try{ std::ifstream is(fullpath.c_str(), std::ios_base::binary); if(is.good() == false) throw FileNotGoodException("Cannot open block file"); v3s16 p3d = getBlockPos(sectordir, blockfile); v2s16 p2d(p3d.X, p3d.Z); assert(sector->getPos() == p2d); 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 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 the disk, so it's up-to-date. block->resetModified(); } catch(SerializationError &e) { dstream<<"WARNING: Invalid block data on disk " <<"fullpath="<(-BS*1000000,-BS*1000000,-BS*1000000, BS*1000000,BS*1000000,BS*1000000); } ClientMap::~ClientMap() { /*JMutexAutoLock lock(mesh_mutex); if(mesh != NULL) { mesh->drop(); mesh = NULL; }*/ } MapSector * ClientMap::emergeSector(v2s16 p2d) { DSTACK(__FUNCTION_NAME); // Check that it doesn't exist already try{ return getSectorNoGenerate(p2d); } catch(InvalidPositionException &e) { } // Create a sector ClientMapSector *sector = new ClientMapSector(this, p2d); { //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out m_sectors.insert(p2d, sector); } return sector; } #if 0 void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is) { DSTACK(__FUNCTION_NAME); ClientMapSector *sector = NULL; //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out core::map::Node *n = m_sectors.find(p2d); if(n != NULL) { sector = (ClientMapSector*)n->getValue(); assert(sector->getId() == MAPSECTOR_CLIENT); } else { sector = new ClientMapSector(this, p2d); { //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out m_sectors.insert(p2d, sector); } } sector->deSerialize(is); } #endif void ClientMap::OnRegisterSceneNode() { if(IsVisible) { SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); } ISceneNode::OnRegisterSceneNode(); } void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) { //m_dout<getDayNightRatio(); m_camera_mutex.Lock(); v3f camera_position = m_camera_position; v3f camera_direction = m_camera_direction; 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 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 v3s16 p_blocks_min( p_nodes_min.X / MAP_BLOCKSIZE - 2, p_nodes_min.Y / MAP_BLOCKSIZE - 2, p_nodes_min.Z / MAP_BLOCKSIZE - 2); v3s16 p_blocks_max( 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; // For limiting number of mesh updates per frame u32 mesh_update_count = 0; u32 blocks_would_have_drawn = 0; u32 blocks_drawn = 0; int timecheck_counter = 0; core::map::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." <getValue(); v2s16 sp = sector->getPos(); if(m_control.range_all == false) { if(sp.X < p_blocks_min.X || sp.X > p_blocks_max.X || sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z) continue; } core::list< MapBlock * > sectorblocks; sector->getBlocks(sectorblocks); /* Draw blocks */ u32 sector_blocks_drawn = 0; core::list< MapBlock * >::Iterator i; for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) { MapBlock *block = *i; /* Compare block position to camera position, skip if not seen on display */ 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) { continue; } // Okay, this block will be drawn. Reset usage timer. block->resetUsageTimer(); // This is ugly (spherical distance limit?) /*if(m_control.range_all == false && d - 0.5*BS*MAP_BLOCKSIZE > range) continue;*/ #if 1 /* Update expired mesh (used for day/night change) It doesn't work exactly like it should now with the tasked mesh update but whatever. */ bool mesh_expired = false; { JMutexAutoLock lock(block->mesh_mutex); mesh_expired = block->getMeshExpired(); // Mesh has not been expired and there is no mesh: // block has no content if(block->mesh == NULL && mesh_expired == false) continue; } f32 faraway = BS*50; //f32 faraway = m_control.wanted_range * BS; /* This has to be done with the mesh_mutex unlocked */ // Pretty random but this should work somewhat nicely if(mesh_expired && ( (mesh_update_count < 3 && (d < faraway || mesh_update_count < 2) ) || (m_control.range_all && mesh_update_count < 20) ) ) /*if(mesh_expired && mesh_update_count < 6 && (d < faraway || mesh_update_count < 3))*/ { mesh_update_count++; // Mesh has been expired: generate new mesh //block->updateMesh(daynight_ratio); m_client->addUpdateMeshTask(block->getPos()); mesh_expired = false; } #endif /* Draw the faces of the block */ { 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) continue; blocks_drawn++; sector_blocks_drawn++; u32 c = mesh->getMeshBufferCount(); for(u32 i=0; igetMeshBuffer(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(); } } } } // foreach sectorblocks if(sector_blocks_drawn != 0) { m_last_drawn_sectors[sp] = true; } } m_control.blocks_drawn = blocks_drawn; m_control.blocks_would_have_drawn = blocks_would_have_drawn; /*dstream<<"renderMap(): is_transparent_pass="< *affected_blocks) { bool changed = false; /* Add it to all blocks touching it */ v3s16 dirs[7] = { v3s16(0,0,0), // this 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<7; i++) { v3s16 p2 = p + dirs[i]; // Block position of neighbor (or requested) node v3s16 blockpos = getNodeBlockPos(p2); MapBlock * blockref = getBlockNoCreateNoEx(blockpos); if(blockref == NULL) continue; // Relative position of requested node v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; if(blockref->setTempMod(relpos, mod)) { changed = true; } } if(changed && affected_blocks!=NULL) { for(u16 i=0; i<7; i++) { v3s16 p2 = p + dirs[i]; // Block position of neighbor (or requested) node v3s16 blockpos = getNodeBlockPos(p2); MapBlock * blockref = getBlockNoCreateNoEx(blockpos); if(blockref == NULL) continue; affected_blocks->insert(blockpos, blockref); } } return changed; } bool ClientMap::clearTempMod(v3s16 p, core::map *affected_blocks) { bool changed = false; v3s16 dirs[7] = { v3s16(0,0,0), // this 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<7; i++) { v3s16 p2 = p + dirs[i]; // Block position of neighbor (or requested) node v3s16 blockpos = getNodeBlockPos(p2); MapBlock * blockref = getBlockNoCreateNoEx(blockpos); if(blockref == NULL) continue; // Relative position of requested node v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; if(blockref->clearTempMod(relpos)) { changed = true; } } if(changed && affected_blocks!=NULL) { for(u16 i=0; i<7; i++) { v3s16 p2 = p + dirs[i]; // Block position of neighbor (or requested) node v3s16 blockpos = getNodeBlockPos(p2); MapBlock * blockref = getBlockNoCreateNoEx(blockpos); if(blockref == NULL) continue; affected_blocks->insert(blockpos, blockref); } } return changed; } void ClientMap::expireMeshes(bool only_daynight_diffed) { TimeTaker timer("expireMeshes()"); core::map::Iterator si; si = m_sectors.getIterator(); for(; si.atEnd() == false; si++) { MapSector *sector = si.getNode()->getValue(); core::list< MapBlock * > sectorblocks; sector->getBlocks(sectorblocks); core::list< MapBlock * >::Iterator i; for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) { MapBlock *block = *i; if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false) { continue; } { JMutexAutoLock lock(block->mesh_mutex); if(block->mesh != NULL) { /*block->mesh->drop(); block->mesh = NULL;*/ block->setMeshExpired(true); } } } } } void ClientMap::updateMeshes(v3s16 blockpos, u32 daynight_ratio) { assert(mapType() == MAPTYPE_CLIENT); try{ v3s16 p = blockpos + v3s16(0,0,0); MapBlock *b = getBlockNoCreate(p); b->updateMesh(daynight_ratio); //b->setMeshExpired(true); } catch(InvalidPositionException &e){} // Leading edge try{ v3s16 p = blockpos + v3s16(-1,0,0); MapBlock *b = getBlockNoCreate(p); b->updateMesh(daynight_ratio); //b->setMeshExpired(true); } catch(InvalidPositionException &e){} try{ v3s16 p = blockpos + v3s16(0,-1,0); MapBlock *b = getBlockNoCreate(p); b->updateMesh(daynight_ratio); //b->setMeshExpired(true); } catch(InvalidPositionException &e){} try{ v3s16 p = blockpos + v3s16(0,0,-1); MapBlock *b = getBlockNoCreate(p); b->updateMesh(daynight_ratio); //b->setMeshExpired(true); } catch(InvalidPositionException &e){} } #if 0 /* Update mesh of block in which the node is, and if the node is at the leading edge, update the appropriate leading blocks too. */ void ClientMap::updateNodeMeshes(v3s16 nodepos, u32 daynight_ratio) { v3s16 dirs[4] = { v3s16(0,0,0), v3s16(-1,0,0), v3s16(0,-1,0), v3s16(0,0,-1), }; v3s16 blockposes[4]; for(u32 i=0; i<4; i++) { v3s16 np = nodepos + dirs[i]; blockposes[i] = getNodeBlockPos(np); // Don't update mesh of block if it has been done already bool already_updated = false; for(u32 j=0; jupdateMesh(daynight_ratio); } } #endif void ClientMap::PrintInfo(std::ostream &out) { out<<"ClientMap: "; } #endif // !SERVER /* MapVoxelManipulator */ MapVoxelManipulator::MapVoxelManipulator(Map *map) { m_map = map; } MapVoxelManipulator::~MapVoxelManipulator() { /*dstream<<"MapVoxelManipulator: blocks: "<::Node *n; n = m_loaded_blocks.find(p); if(n != NULL) continue; bool block_data_inexistent = false; try { TimeTaker timer1("emerge load", &emerge_load_time); /*dstream<<"Loading block (caller_id="<getBlockNoCreate(p); if(block->isDummy()) block_data_inexistent = true; else block->copyTo(*this); } catch(InvalidPositionException &e) { block_data_inexistent = true; } if(block_data_inexistent) { VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); // Fill with VOXELFLAG_INEXISTENT for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++) for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++) { s32 i = m_area.index(a.MinEdge.X,y,z); memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE); } } m_loaded_blocks.insert(p, !block_data_inexistent); } //dstream<<"emerge done"< & modified_blocks) { if(m_area.getExtent() == v3s16(0,0,0)) return; //TimeTaker timer1("blitBack"); /*dstream<<"blitBack(): m_loaded_blocks.size()=" <getBlockNoCreate(blockpos); blockpos_last = blockpos; block_checked_in_modified = false; } // Calculate relative position in block v3s16 relpos = p - blockpos * MAP_BLOCKSIZE; // Don't continue if nothing has changed here if(block->getNode(relpos) == n) continue; //m_map->setNode(m_area.MinEdge + p, n); block->setNode(relpos, n); /* Make sure block is in modified_blocks */ if(block_checked_in_modified == false) { modified_blocks[blockpos] = block; block_checked_in_modified = true; } } catch(InvalidPositionException &e) { } } } ManualMapVoxelManipulator::ManualMapVoxelManipulator(Map *map): MapVoxelManipulator(map), m_create_area(false) { } ManualMapVoxelManipulator::~ManualMapVoxelManipulator() { } void ManualMapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) { // Just create the area so that it can be pointed to VoxelManipulator::emerge(a, caller_id); } void ManualMapVoxelManipulator::initialEmerge( v3s16 blockpos_min, v3s16 blockpos_max) { TimeTaker timer1("initialEmerge", &emerge_time); // Units of these are MapBlocks v3s16 p_min = blockpos_min; v3s16 p_max = blockpos_max; VoxelArea block_area_nodes (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); u32 size_MB = block_area_nodes.getVolume()*4/1000000; if(size_MB >= 1) { dstream<<"initialEmerge: area: "; block_area_nodes.print(dstream); dstream<<" ("<::Node *n; n = m_loaded_blocks.find(p); if(n != NULL) continue; bool block_data_inexistent = false; try { TimeTaker timer1("emerge load", &emerge_load_time); MapBlock *block = m_map->getBlockNoCreate(p); if(block->isDummy()) block_data_inexistent = true; else block->copyTo(*this); } catch(InvalidPositionException &e) { block_data_inexistent = true; } if(block_data_inexistent) { /* Mark area inexistent */ VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); // Fill with VOXELFLAG_INEXISTENT for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++) for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++) { s32 i = m_area.index(a.MinEdge.X,y,z); memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE); } } m_loaded_blocks.insert(p, !block_data_inexistent); } } void ManualMapVoxelManipulator::blitBackAll( core::map * modified_blocks) { if(m_area.getExtent() == v3s16(0,0,0)) return; /* Copy data of all blocks */ for(core::map::Iterator i = m_loaded_blocks.getIterator(); i.atEnd() == false; i++) { bool existed = i.getNode()->getValue(); if(existed == false) continue; v3s16 p = i.getNode()->getKey(); MapBlock *block = m_map->getBlockNoCreateNoEx(p); if(block == NULL) { dstream<<"WARNING: "<<__FUNCTION_NAME <<": got NULL block " <<"("<copyFrom(*this); if(modified_blocks) modified_blocks->insert(p, block); } } //END