3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "mapsector.h"
27 #include "serialization.h"
28 #include "nodemetadata.h"
34 #include "util/directiontables.h"
35 #include "util/mathconstants.h"
36 #include "rollback_interface.h"
37 #include "environment.h"
39 #include "mapgen_v6.h"
44 #include "database-dummy.h"
45 #include "database-sqlite3.h"
48 #include "database-leveldb.h"
51 #include "database-redis.h"
54 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
61 Map::Map(std::ostream &dout, IGameDef *gamedef):
65 m_transforming_liquid_loop_count_multiplier(1.0f),
66 m_unprocessed_count(0),
67 m_inc_trending_up_start_time(0),
68 m_queue_size_timer_started(false)
77 for(std::map<v2s16, MapSector*>::iterator i = m_sectors.begin();
78 i != m_sectors.end(); ++i)
84 void Map::addEventReceiver(MapEventReceiver *event_receiver)
86 m_event_receivers.insert(event_receiver);
89 void Map::removeEventReceiver(MapEventReceiver *event_receiver)
91 m_event_receivers.erase(event_receiver);
94 void Map::dispatchEvent(MapEditEvent *event)
96 for(std::set<MapEventReceiver*>::iterator
97 i = m_event_receivers.begin();
98 i != m_event_receivers.end(); ++i)
100 (*i)->onMapEditEvent(event);
104 MapSector * Map::getSectorNoGenerateNoExNoLock(v2s16 p)
106 if(m_sector_cache != NULL && p == m_sector_cache_p){
107 MapSector * sector = m_sector_cache;
111 std::map<v2s16, MapSector*>::iterator n = m_sectors.find(p);
113 if(n == m_sectors.end())
116 MapSector *sector = n->second;
118 // Cache the last result
119 m_sector_cache_p = p;
120 m_sector_cache = sector;
125 MapSector * Map::getSectorNoGenerateNoEx(v2s16 p)
127 return getSectorNoGenerateNoExNoLock(p);
130 MapSector * Map::getSectorNoGenerate(v2s16 p)
132 MapSector *sector = getSectorNoGenerateNoEx(p);
134 throw InvalidPositionException();
139 MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
141 v2s16 p2d(p3d.X, p3d.Z);
142 MapSector * sector = getSectorNoGenerateNoEx(p2d);
145 MapBlock *block = sector->getBlockNoCreateNoEx(p3d.Y);
149 MapBlock * Map::getBlockNoCreate(v3s16 p3d)
151 MapBlock *block = getBlockNoCreateNoEx(p3d);
153 throw InvalidPositionException();
157 bool Map::isNodeUnderground(v3s16 p)
159 v3s16 blockpos = getNodeBlockPos(p);
161 MapBlock * block = getBlockNoCreate(blockpos);
162 return block->getIsUnderground();
164 catch(InvalidPositionException &e)
170 bool Map::isValidPosition(v3s16 p)
172 v3s16 blockpos = getNodeBlockPos(p);
173 MapBlock *block = getBlockNoCreate(blockpos);
174 return (block != NULL);
177 // Returns a CONTENT_IGNORE node if not found
178 MapNode Map::getNodeNoEx(v3s16 p, bool *is_valid_position)
180 v3s16 blockpos = getNodeBlockPos(p);
181 MapBlock *block = getBlockNoCreateNoEx(blockpos);
183 if (is_valid_position != NULL)
184 *is_valid_position = false;
185 return MapNode(CONTENT_IGNORE);
188 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
190 MapNode node = block->getNodeNoCheck(relpos, &is_valid_p);
191 if (is_valid_position != NULL)
192 *is_valid_position = is_valid_p;
198 // throws InvalidPositionException if not found
199 // TODO: Now this is deprecated, getNodeNoEx should be renamed
200 MapNode Map::getNode(v3s16 p)
202 v3s16 blockpos = getNodeBlockPos(p);
203 MapBlock *block = getBlockNoCreateNoEx(blockpos);
205 throw InvalidPositionException();
206 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
207 bool is_valid_position;
208 MapNode node = block->getNodeNoCheck(relpos, &is_valid_position);
209 if (!is_valid_position)
210 throw InvalidPositionException();
215 // throws InvalidPositionException if not found
216 void Map::setNode(v3s16 p, MapNode & n)
218 v3s16 blockpos = getNodeBlockPos(p);
219 MapBlock *block = getBlockNoCreate(blockpos);
220 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
221 // Never allow placing CONTENT_IGNORE, it fucks up stuff
222 if(n.getContent() == CONTENT_IGNORE){
224 errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE"
225 <<" while trying to replace \""
226 <<m_gamedef->ndef()->get(block->getNodeNoCheck(relpos, &temp_bool)).name
227 <<"\" at "<<PP(p)<<" (block "<<PP(blockpos)<<")"<<std::endl;
228 debug_stacks_print_to(infostream);
231 block->setNodeNoCheck(relpos, n);
236 Goes recursively through the neighbours of the node.
238 Alters only transparent nodes.
240 If the lighting of the neighbour is lower than the lighting of
241 the node was (before changing it to 0 at the step before), the
242 lighting of the neighbour is set to 0 and then the same stuff
243 repeats for the neighbour.
245 The ending nodes of the routine are stored in light_sources.
246 This is useful when a light is removed. In such case, this
247 routine can be called for the light node and then again for
248 light_sources to re-light the area without the removed light.
250 values of from_nodes are lighting values.
252 void Map::unspreadLight(enum LightBank bank,
253 std::map<v3s16, u8> & from_nodes,
254 std::set<v3s16> & light_sources,
255 std::map<v3s16, MapBlock*> & modified_blocks)
257 INodeDefManager *nodemgr = m_gamedef->ndef();
260 v3s16(0,0,1), // back
262 v3s16(1,0,0), // right
263 v3s16(0,0,-1), // front
264 v3s16(0,-1,0), // bottom
265 v3s16(-1,0,0), // left
268 if(from_nodes.empty())
271 u32 blockchangecount = 0;
273 std::map<v3s16, u8> unlighted_nodes;
276 Initialize block cache
279 MapBlock *block = NULL;
280 // Cache this a bit, too
281 bool block_checked_in_modified = false;
283 for(std::map<v3s16, u8>::iterator j = from_nodes.begin();
284 j != from_nodes.end(); ++j)
286 v3s16 pos = j->first;
287 v3s16 blockpos = getNodeBlockPos(pos);
289 // Only fetch a new block if the block position has changed
291 if(block == NULL || blockpos != blockpos_last){
292 block = getBlockNoCreate(blockpos);
293 blockpos_last = blockpos;
295 block_checked_in_modified = false;
299 catch(InvalidPositionException &e)
307 // Calculate relative position in block
308 //v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
310 // Get node straight from the block
311 //MapNode n = block->getNode(relpos);
313 u8 oldlight = j->second;
315 // Loop through 6 neighbors
316 for(u16 i=0; i<6; i++)
318 // Get the position of the neighbor node
319 v3s16 n2pos = pos + dirs[i];
321 // Get the block where the node is located
322 v3s16 blockpos, relpos;
323 getNodeBlockPosWithOffset(n2pos, blockpos, relpos);
325 // Only fetch a new block if the block position has changed
327 if(block == NULL || blockpos != blockpos_last){
328 block = getBlockNoCreate(blockpos);
329 blockpos_last = blockpos;
331 block_checked_in_modified = false;
335 catch(InvalidPositionException &e) {
339 // Get node straight from the block
340 bool is_valid_position;
341 MapNode n2 = block->getNode(relpos, &is_valid_position);
342 if (!is_valid_position)
345 bool changed = false;
347 //TODO: Optimize output by optimizing light_sources?
350 If the neighbor is dimmer than what was specified
351 as oldlight (the light of the previous node)
353 if(n2.getLight(bank, nodemgr) < oldlight)
356 And the neighbor is transparent and it has some light
358 if(nodemgr->get(n2).light_propagates
359 && n2.getLight(bank, nodemgr) != 0)
362 Set light to 0 and add to queue
365 u8 current_light = n2.getLight(bank, nodemgr);
366 n2.setLight(bank, 0, nodemgr);
367 block->setNode(relpos, n2);
369 unlighted_nodes[n2pos] = current_light;
373 Remove from light_sources if it is there
374 NOTE: This doesn't happen nearly at all
376 /*if(light_sources.find(n2pos))
378 infostream<<"Removed from light_sources"<<std::endl;
379 light_sources.remove(n2pos);
384 if(light_sources.find(n2pos) != NULL)
385 light_sources.remove(n2pos);*/
388 light_sources.insert(n2pos);
391 // Add to modified_blocks
392 if(changed == true && block_checked_in_modified == false)
394 // If the block is not found in modified_blocks, add.
395 if(modified_blocks.find(blockpos) == modified_blocks.end())
397 modified_blocks[blockpos] = block;
399 block_checked_in_modified = true;
404 /*infostream<<"unspreadLight(): Changed block "
405 <<blockchangecount<<" times"
406 <<" for "<<from_nodes.size()<<" nodes"
409 if(!unlighted_nodes.empty())
410 unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
414 A single-node wrapper of the above
416 void Map::unLightNeighbors(enum LightBank bank,
417 v3s16 pos, u8 lightwas,
418 std::set<v3s16> & light_sources,
419 std::map<v3s16, MapBlock*> & modified_blocks)
421 std::map<v3s16, u8> from_nodes;
422 from_nodes[pos] = lightwas;
424 unspreadLight(bank, from_nodes, light_sources, modified_blocks);
428 Lights neighbors of from_nodes, collects all them and then
431 void Map::spreadLight(enum LightBank bank,
432 std::set<v3s16> & from_nodes,
433 std::map<v3s16, MapBlock*> & modified_blocks)
435 INodeDefManager *nodemgr = m_gamedef->ndef();
437 const v3s16 dirs[6] = {
438 v3s16(0,0,1), // back
440 v3s16(1,0,0), // right
441 v3s16(0,0,-1), // front
442 v3s16(0,-1,0), // bottom
443 v3s16(-1,0,0), // left
446 if(from_nodes.empty())
449 u32 blockchangecount = 0;
451 std::set<v3s16> lighted_nodes;
454 Initialize block cache
457 MapBlock *block = NULL;
458 // Cache this a bit, too
459 bool block_checked_in_modified = false;
461 for(std::set<v3s16>::iterator j = from_nodes.begin();
462 j != from_nodes.end(); ++j)
465 v3s16 blockpos, relpos;
467 getNodeBlockPosWithOffset(pos, blockpos, relpos);
469 // Only fetch a new block if the block position has changed
471 if(block == NULL || blockpos != blockpos_last){
472 block = getBlockNoCreate(blockpos);
473 blockpos_last = blockpos;
475 block_checked_in_modified = false;
479 catch(InvalidPositionException &e) {
486 // Get node straight from the block
487 bool is_valid_position;
488 MapNode n = block->getNode(relpos, &is_valid_position);
490 u8 oldlight = is_valid_position ? n.getLight(bank, nodemgr) : 0;
491 u8 newlight = diminish_light(oldlight);
493 // Loop through 6 neighbors
494 for(u16 i=0; i<6; i++){
495 // Get the position of the neighbor node
496 v3s16 n2pos = pos + dirs[i];
498 // Get the block where the node is located
499 v3s16 blockpos, relpos;
500 getNodeBlockPosWithOffset(n2pos, blockpos, relpos);
502 // Only fetch a new block if the block position has changed
504 if(block == NULL || blockpos != blockpos_last){
505 block = getBlockNoCreate(blockpos);
506 blockpos_last = blockpos;
508 block_checked_in_modified = false;
512 catch(InvalidPositionException &e) {
516 // Get node straight from the block
517 MapNode n2 = block->getNode(relpos, &is_valid_position);
518 if (!is_valid_position)
521 bool changed = false;
523 If the neighbor is brighter than the current node,
524 add to list (it will light up this node on its turn)
526 if(n2.getLight(bank, nodemgr) > undiminish_light(oldlight))
528 lighted_nodes.insert(n2pos);
532 If the neighbor is dimmer than how much light this node
533 would spread on it, add to list
535 if(n2.getLight(bank, nodemgr) < newlight)
537 if(nodemgr->get(n2).light_propagates)
539 n2.setLight(bank, newlight, nodemgr);
540 block->setNode(relpos, n2);
541 lighted_nodes.insert(n2pos);
546 // Add to modified_blocks
547 if(changed == true && block_checked_in_modified == false)
549 // If the block is not found in modified_blocks, add.
550 if(modified_blocks.find(blockpos) == modified_blocks.end())
552 modified_blocks[blockpos] = block;
554 block_checked_in_modified = true;
559 /*infostream<<"spreadLight(): Changed block "
560 <<blockchangecount<<" times"
561 <<" for "<<from_nodes.size()<<" nodes"
564 if(!lighted_nodes.empty())
565 spreadLight(bank, lighted_nodes, modified_blocks);
569 A single-node source variation of the above.
571 void Map::lightNeighbors(enum LightBank bank,
573 std::map<v3s16, MapBlock*> & modified_blocks)
575 std::set<v3s16> from_nodes;
576 from_nodes.insert(pos);
577 spreadLight(bank, from_nodes, modified_blocks);
580 v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
582 INodeDefManager *nodemgr = m_gamedef->ndef();
585 v3s16(0,0,1), // back
587 v3s16(1,0,0), // right
588 v3s16(0,0,-1), // front
589 v3s16(0,-1,0), // bottom
590 v3s16(-1,0,0), // left
593 u8 brightest_light = 0;
594 v3s16 brightest_pos(0,0,0);
595 bool found_something = false;
597 // Loop through 6 neighbors
598 for(u16 i=0; i<6; i++){
599 // Get the position of the neighbor node
600 v3s16 n2pos = p + dirs[i];
602 bool is_valid_position;
603 n2 = getNodeNoEx(n2pos, &is_valid_position);
604 if (!is_valid_position)
607 if(n2.getLight(bank, nodemgr) > brightest_light || found_something == false){
608 brightest_light = n2.getLight(bank, nodemgr);
609 brightest_pos = n2pos;
610 found_something = true;
614 if(found_something == false)
615 throw InvalidPositionException();
617 return brightest_pos;
621 Propagates sunlight down from a node.
622 Starting point gets sunlight.
624 Returns the lowest y value of where the sunlight went.
626 Mud is turned into grass in where the sunlight stops.
628 s16 Map::propagateSunlight(v3s16 start,
629 std::map<v3s16, MapBlock*> & modified_blocks)
631 INodeDefManager *nodemgr = m_gamedef->ndef();
636 v3s16 pos(start.X, y, start.Z);
638 v3s16 blockpos = getNodeBlockPos(pos);
641 block = getBlockNoCreate(blockpos);
643 catch(InvalidPositionException &e)
648 v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
649 bool is_valid_position;
650 MapNode n = block->getNode(relpos, &is_valid_position);
651 if (!is_valid_position)
654 if(nodemgr->get(n).sunlight_propagates)
656 n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
657 block->setNode(relpos, n);
659 modified_blocks[blockpos] = block;
663 // Sunlight goes no further
670 void Map::updateLighting(enum LightBank bank,
671 std::map<v3s16, MapBlock*> & a_blocks,
672 std::map<v3s16, MapBlock*> & modified_blocks)
674 INodeDefManager *nodemgr = m_gamedef->ndef();
676 /*m_dout<<DTIME<<"Map::updateLighting(): "
677 <<a_blocks.size()<<" blocks."<<std::endl;*/
679 //TimeTaker timer("updateLighting");
683 //u32 count_was = modified_blocks.size();
685 //std::map<v3s16, MapBlock*> blocks_to_update;
687 std::set<v3s16> light_sources;
689 std::map<v3s16, u8> unlight_from;
691 int num_bottom_invalid = 0;
694 //TimeTaker t("first stuff");
696 for(std::map<v3s16, MapBlock*>::iterator i = a_blocks.begin();
697 i != a_blocks.end(); ++i)
699 MapBlock *block = i->second;
703 // Don't bother with dummy blocks.
707 v3s16 pos = block->getPos();
708 v3s16 posnodes = block->getPosRelative();
709 modified_blocks[pos] = block;
710 //blocks_to_update[pos] = block;
713 Clear all light from block
715 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
716 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
717 for(s16 y=0; y<MAP_BLOCKSIZE; y++)
720 bool is_valid_position;
721 MapNode n = block->getNode(p, &is_valid_position);
722 if (!is_valid_position) {
723 /* This would happen when dealing with a
726 infostream<<"updateLighting(): InvalidPositionException"
730 u8 oldlight = n.getLight(bank, nodemgr);
731 n.setLight(bank, 0, nodemgr);
732 block->setNode(p, n);
734 // If node sources light, add to list
735 u8 source = nodemgr->get(n).light_source;
737 light_sources.insert(p + posnodes);
739 // Collect borders for unlighting
740 if((x==0 || x == MAP_BLOCKSIZE-1
741 || y==0 || y == MAP_BLOCKSIZE-1
742 || z==0 || z == MAP_BLOCKSIZE-1)
745 v3s16 p_map = p + posnodes;
746 unlight_from[p_map] = oldlight;
752 if(bank == LIGHTBANK_DAY)
754 bool bottom_valid = block->propagateSunlight(light_sources);
757 num_bottom_invalid++;
759 // If bottom is valid, we're done.
763 else if(bank == LIGHTBANK_NIGHT)
765 // For night lighting, sunlight is not propagated
770 assert("Invalid lighting bank" == NULL);
773 /*infostream<<"Bottom for sunlight-propagated block ("
774 <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
777 // Bottom sunlight is not valid; get the block and loop to it
781 block = getBlockNoCreate(pos);
783 catch(InvalidPositionException &e)
785 FATAL_ERROR("Invalid position");
794 Enable this to disable proper lighting for speeding up map
795 generation for testing or whatever
798 //if(g_settings->get(""))
800 core::map<v3s16, MapBlock*>::Iterator i;
801 i = blocks_to_update.getIterator();
802 for(; i.atEnd() == false; i++)
804 MapBlock *block = i.getNode()->getValue();
805 v3s16 p = block->getPos();
806 block->setLightingExpired(false);
814 //TimeTaker timer("unspreadLight");
815 unspreadLight(bank, unlight_from, light_sources, modified_blocks);
820 u32 diff = modified_blocks.size() - count_was;
821 count_was = modified_blocks.size();
822 infostream<<"unspreadLight modified "<<diff<<std::endl;
826 //TimeTaker timer("spreadLight");
827 spreadLight(bank, light_sources, modified_blocks);
832 u32 diff = modified_blocks.size() - count_was;
833 count_was = modified_blocks.size();
834 infostream<<"spreadLight modified "<<diff<<std::endl;
840 //MapVoxelManipulator vmanip(this);
842 // Make a manual voxel manipulator and load all the blocks
843 // that touch the requested blocks
844 ManualMapVoxelManipulator vmanip(this);
847 //TimeTaker timer("initialEmerge");
849 core::map<v3s16, MapBlock*>::Iterator i;
850 i = blocks_to_update.getIterator();
851 for(; i.atEnd() == false; i++)
853 MapBlock *block = i.getNode()->getValue();
854 v3s16 p = block->getPos();
856 // Add all surrounding blocks
857 vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1));
860 Add all surrounding blocks that have up-to-date lighting
861 NOTE: This doesn't quite do the job (not everything
862 appropriate is lighted)
864 /*for(s16 z=-1; z<=1; z++)
865 for(s16 y=-1; y<=1; y++)
866 for(s16 x=-1; x<=1; x++)
868 v3s16 p2 = p + v3s16(x,y,z);
869 MapBlock *block = getBlockNoCreateNoEx(p2);
874 if(block->getLightingExpired())
876 vmanip.initialEmerge(p2, p2);
879 // Lighting of block will be updated completely
880 block->setLightingExpired(false);
885 //TimeTaker timer("unSpreadLight");
886 vmanip.unspreadLight(bank, unlight_from, light_sources, nodemgr);
889 //TimeTaker timer("spreadLight");
890 vmanip.spreadLight(bank, light_sources, nodemgr);
893 //TimeTaker timer("blitBack");
894 vmanip.blitBack(modified_blocks);
896 /*infostream<<"emerge_time="<<emerge_time<<std::endl;
901 //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
904 void Map::updateLighting(std::map<v3s16, MapBlock*> & a_blocks,
905 std::map<v3s16, MapBlock*> & modified_blocks)
907 updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
908 updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
911 Update information about whether day and night light differ
913 for(std::map<v3s16, MapBlock*>::iterator
914 i = modified_blocks.begin();
915 i != modified_blocks.end(); ++i)
917 MapBlock *block = i->second;
918 block->expireDayNightDiff();
924 void Map::addNodeAndUpdate(v3s16 p, MapNode n,
925 std::map<v3s16, MapBlock*> &modified_blocks,
926 bool remove_metadata)
928 INodeDefManager *ndef = m_gamedef->ndef();
931 m_dout<<DTIME<<"Map::addNodeAndUpdate(): p=("
932 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
935 From this node to nodes underneath:
936 If lighting is sunlight (1.0), unlight neighbours and
941 v3s16 toppos = p + v3s16(0,1,0);
942 //v3s16 bottompos = p + v3s16(0,-1,0);
944 bool node_under_sunlight = true;
945 std::set<v3s16> light_sources;
948 Collect old node for rollback
950 RollbackNode rollback_oldnode(this, p, m_gamedef);
953 If there is a node at top and it doesn't have sunlight,
954 there has not been any sunlight going down.
956 Otherwise there probably is.
959 bool is_valid_position;
960 MapNode topnode = getNodeNoEx(toppos, &is_valid_position);
962 if(is_valid_position && topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
963 node_under_sunlight = false;
966 Remove all light that has come out of this node
969 enum LightBank banks[] =
974 for(s32 i=0; i<2; i++)
976 enum LightBank bank = banks[i];
978 u8 lightwas = getNodeNoEx(p).getLight(bank, ndef);
980 // Add the block of the added node to modified_blocks
981 v3s16 blockpos = getNodeBlockPos(p);
982 MapBlock * block = getBlockNoCreate(blockpos);
983 assert(block != NULL);
984 modified_blocks[blockpos] = block;
986 assert(isValidPosition(p));
988 // Unlight neighbours of node.
989 // This means setting light of all consequent dimmer nodes
991 // This also collects the nodes at the border which will spread
992 // light again into this.
993 unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
995 n.setLight(bank, 0, ndef);
999 If node lets sunlight through and is under sunlight, it has
1002 if(node_under_sunlight && ndef->get(n).sunlight_propagates)
1004 n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef);
1008 Remove node metadata
1010 if (remove_metadata) {
1011 removeNodeMetadata(p);
1015 Set the node on the map
1021 If node is under sunlight and doesn't let sunlight through,
1022 take all sunlighted nodes under it and clear light from them
1023 and from where the light has been spread.
1024 TODO: This could be optimized by mass-unlighting instead
1027 if(node_under_sunlight && !ndef->get(n).sunlight_propagates)
1031 //m_dout<<DTIME<<"y="<<y<<std::endl;
1032 v3s16 n2pos(p.X, y, p.Z);
1036 n2 = getNodeNoEx(n2pos, &is_valid_position);
1037 if (!is_valid_position)
1040 if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
1042 unLightNeighbors(LIGHTBANK_DAY,
1043 n2pos, n2.getLight(LIGHTBANK_DAY, ndef),
1044 light_sources, modified_blocks);
1045 n2.setLight(LIGHTBANK_DAY, 0, ndef);
1053 for(s32 i=0; i<2; i++)
1055 enum LightBank bank = banks[i];
1058 Spread light from all nodes that might be capable of doing so
1060 spreadLight(bank, light_sources, modified_blocks);
1064 Update information about whether day and night light differ
1066 for(std::map<v3s16, MapBlock*>::iterator
1067 i = modified_blocks.begin();
1068 i != modified_blocks.end(); ++i)
1070 i->second->expireDayNightDiff();
1076 if(m_gamedef->rollback())
1078 RollbackNode rollback_newnode(this, p, m_gamedef);
1079 RollbackAction action;
1080 action.setSetNode(p, rollback_oldnode, rollback_newnode);
1081 m_gamedef->rollback()->reportAction(action);
1085 Add neighboring liquid nodes and the node itself if it is
1086 liquid (=water node was added) to transform queue.
1089 v3s16(0,0,0), // self
1090 v3s16(0,0,1), // back
1091 v3s16(0,1,0), // top
1092 v3s16(1,0,0), // right
1093 v3s16(0,0,-1), // front
1094 v3s16(0,-1,0), // bottom
1095 v3s16(-1,0,0), // left
1097 for(u16 i=0; i<7; i++)
1099 v3s16 p2 = p + dirs[i];
1101 MapNode n2 = getNodeNoEx(p2, &is_valid_position);
1102 if(is_valid_position
1103 && (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR))
1105 m_transforming_liquid.push_back(p2);
1112 void Map::removeNodeAndUpdate(v3s16 p,
1113 std::map<v3s16, MapBlock*> &modified_blocks)
1115 INodeDefManager *ndef = m_gamedef->ndef();
1117 /*PrintInfo(m_dout);
1118 m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
1119 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1121 bool node_under_sunlight = true;
1123 v3s16 toppos = p + v3s16(0,1,0);
1125 // Node will be replaced with this
1126 content_t replace_material = CONTENT_AIR;
1129 Collect old node for rollback
1131 RollbackNode rollback_oldnode(this, p, m_gamedef);
1134 If there is a node at top and it doesn't have sunlight,
1135 there will be no sunlight going down.
1137 bool is_valid_position;
1138 MapNode topnode = getNodeNoEx(toppos, &is_valid_position);
1140 if(is_valid_position && topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
1141 node_under_sunlight = false;
1143 std::set<v3s16> light_sources;
1145 enum LightBank banks[] =
1150 for(s32 i=0; i<2; i++)
1152 enum LightBank bank = banks[i];
1155 Unlight neighbors (in case the node is a light source)
1157 unLightNeighbors(bank, p,
1158 getNodeNoEx(p).getLight(bank, ndef),
1159 light_sources, modified_blocks);
1163 Remove node metadata
1166 removeNodeMetadata(p);
1170 This also clears the lighting.
1173 MapNode n(replace_material);
1176 for(s32 i=0; i<2; i++)
1178 enum LightBank bank = banks[i];
1181 Recalculate lighting
1183 spreadLight(bank, light_sources, modified_blocks);
1186 // Add the block of the removed node to modified_blocks
1187 v3s16 blockpos = getNodeBlockPos(p);
1188 MapBlock * block = getBlockNoCreate(blockpos);
1189 assert(block != NULL);
1190 modified_blocks[blockpos] = block;
1193 If the removed node was under sunlight, propagate the
1194 sunlight down from it and then light all neighbors
1195 of the propagated blocks.
1197 if(node_under_sunlight)
1199 s16 ybottom = propagateSunlight(p, modified_blocks);
1200 /*m_dout<<DTIME<<"Node was under sunlight. "
1201 "Propagating sunlight";
1202 m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
1204 for(; y >= ybottom; y--)
1206 v3s16 p2(p.X, y, p.Z);
1207 /*m_dout<<DTIME<<"lighting neighbors of node ("
1208 <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
1210 lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
1215 // Set the lighting of this node to 0
1216 // TODO: Is this needed? Lighting is cleared up there already.
1217 MapNode n = getNodeNoEx(p, &is_valid_position);
1218 if (is_valid_position) {
1219 n.setLight(LIGHTBANK_DAY, 0, ndef);
1222 FATAL_ERROR("Invalid position");
1226 for(s32 i=0; i<2; i++)
1228 enum LightBank bank = banks[i];
1230 // Get the brightest neighbour node and propagate light from it
1231 v3s16 n2p = getBrightestNeighbour(bank, p);
1233 //MapNode n2 = getNode(n2p);
1234 lightNeighbors(bank, n2p, modified_blocks);
1236 catch(InvalidPositionException &e)
1242 Update information about whether day and night light differ
1244 for(std::map<v3s16, MapBlock*>::iterator
1245 i = modified_blocks.begin();
1246 i != modified_blocks.end(); ++i)
1248 i->second->expireDayNightDiff();
1254 if(m_gamedef->rollback())
1256 RollbackNode rollback_newnode(this, p, m_gamedef);
1257 RollbackAction action;
1258 action.setSetNode(p, rollback_oldnode, rollback_newnode);
1259 m_gamedef->rollback()->reportAction(action);
1263 Add neighboring liquid nodes and this node to transform queue.
1264 (it's vital for the node itself to get updated last.)
1267 v3s16(0,0,1), // back
1268 v3s16(0,1,0), // top
1269 v3s16(1,0,0), // right
1270 v3s16(0,0,-1), // front
1271 v3s16(0,-1,0), // bottom
1272 v3s16(-1,0,0), // left
1273 v3s16(0,0,0), // self
1275 for(u16 i=0; i<7; i++)
1277 v3s16 p2 = p + dirs[i];
1279 bool is_position_valid;
1280 MapNode n2 = getNodeNoEx(p2, &is_position_valid);
1281 if (is_position_valid
1282 && (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR))
1284 m_transforming_liquid.push_back(p2);
1289 bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata)
1292 event.type = remove_metadata ? MEET_ADDNODE : MEET_SWAPNODE;
1296 bool succeeded = true;
1298 std::map<v3s16, MapBlock*> modified_blocks;
1299 addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
1301 // Copy modified_blocks to event
1302 for(std::map<v3s16, MapBlock*>::iterator
1303 i = modified_blocks.begin();
1304 i != modified_blocks.end(); ++i)
1306 event.modified_blocks.insert(i->first);
1309 catch(InvalidPositionException &e){
1313 dispatchEvent(&event);
1318 bool Map::removeNodeWithEvent(v3s16 p)
1321 event.type = MEET_REMOVENODE;
1324 bool succeeded = true;
1326 std::map<v3s16, MapBlock*> modified_blocks;
1327 removeNodeAndUpdate(p, modified_blocks);
1329 // Copy modified_blocks to event
1330 for(std::map<v3s16, MapBlock*>::iterator
1331 i = modified_blocks.begin();
1332 i != modified_blocks.end(); ++i)
1334 event.modified_blocks.insert(i->first);
1337 catch(InvalidPositionException &e){
1341 dispatchEvent(&event);
1346 bool Map::getDayNightDiff(v3s16 blockpos)
1349 v3s16 p = blockpos + v3s16(0,0,0);
1350 MapBlock *b = getBlockNoCreate(p);
1351 if(b->getDayNightDiff())
1354 catch(InvalidPositionException &e){}
1357 v3s16 p = blockpos + v3s16(-1,0,0);
1358 MapBlock *b = getBlockNoCreate(p);
1359 if(b->getDayNightDiff())
1362 catch(InvalidPositionException &e){}
1364 v3s16 p = blockpos + v3s16(0,-1,0);
1365 MapBlock *b = getBlockNoCreate(p);
1366 if(b->getDayNightDiff())
1369 catch(InvalidPositionException &e){}
1371 v3s16 p = blockpos + v3s16(0,0,-1);
1372 MapBlock *b = getBlockNoCreate(p);
1373 if(b->getDayNightDiff())
1376 catch(InvalidPositionException &e){}
1379 v3s16 p = blockpos + v3s16(1,0,0);
1380 MapBlock *b = getBlockNoCreate(p);
1381 if(b->getDayNightDiff())
1384 catch(InvalidPositionException &e){}
1386 v3s16 p = blockpos + v3s16(0,1,0);
1387 MapBlock *b = getBlockNoCreate(p);
1388 if(b->getDayNightDiff())
1391 catch(InvalidPositionException &e){}
1393 v3s16 p = blockpos + v3s16(0,0,1);
1394 MapBlock *b = getBlockNoCreate(p);
1395 if(b->getDayNightDiff())
1398 catch(InvalidPositionException &e){}
1404 Updates usage timers
1406 void Map::timerUpdate(float dtime, float unload_timeout,
1407 std::vector<v3s16> *unloaded_blocks)
1409 bool save_before_unloading = (mapType() == MAPTYPE_SERVER);
1411 // Profile modified reasons
1412 Profiler modprofiler;
1414 std::vector<v2s16> sector_deletion_queue;
1415 u32 deleted_blocks_count = 0;
1416 u32 saved_blocks_count = 0;
1417 u32 block_count_all = 0;
1420 for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin();
1421 si != m_sectors.end(); ++si) {
1422 MapSector *sector = si->second;
1424 bool all_blocks_deleted = true;
1426 MapBlockVect blocks;
1427 sector->getBlocks(blocks);
1429 for(MapBlockVect::iterator i = blocks.begin();
1430 i != blocks.end(); ++i) {
1431 MapBlock *block = (*i);
1433 block->incrementUsageTimer(dtime);
1435 if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout) {
1436 v3s16 p = block->getPos();
1439 if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) {
1440 modprofiler.add(block->getModifiedReason(), 1);
1441 if (!saveBlock(block))
1443 saved_blocks_count++;
1446 // Delete from memory
1447 sector->deleteBlock(block);
1450 unloaded_blocks->push_back(p);
1452 deleted_blocks_count++;
1455 all_blocks_deleted = false;
1460 if(all_blocks_deleted) {
1461 sector_deletion_queue.push_back(si->first);
1466 // Finally delete the empty sectors
1467 deleteSectors(sector_deletion_queue);
1469 if(deleted_blocks_count != 0)
1471 PrintInfo(infostream); // ServerMap/ClientMap:
1472 infostream<<"Unloaded "<<deleted_blocks_count
1473 <<" blocks from memory";
1474 if(save_before_unloading)
1475 infostream<<", of which "<<saved_blocks_count<<" were written";
1476 infostream<<", "<<block_count_all<<" blocks in memory";
1477 infostream<<"."<<std::endl;
1478 if(saved_blocks_count != 0){
1479 PrintInfo(infostream); // ServerMap/ClientMap:
1480 infostream<<"Blocks modified by: "<<std::endl;
1481 modprofiler.print(infostream);
1486 void Map::unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks)
1488 timerUpdate(0.0, -1.0, unloaded_blocks);
1491 void Map::deleteSectors(std::vector<v2s16> §orList)
1493 for(std::vector<v2s16>::iterator j = sectorList.begin();
1494 j != sectorList.end(); ++j) {
1495 MapSector *sector = m_sectors[*j];
1496 // If sector is in sector cache, remove it from there
1497 if(m_sector_cache == sector)
1498 m_sector_cache = NULL;
1499 // Remove from map and delete
1500 m_sectors.erase(*j);
1505 void Map::PrintInfo(std::ostream &out)
1510 #define WATER_DROP_BOOST 4
1514 NEIGHBOR_SAME_LEVEL,
1517 struct NodeNeighbor {
1521 bool l; //can liquid
1527 NodeNeighbor(const MapNode &node, NeighborType n_type, v3s16 pos)
1534 void Map::transforming_liquid_add(v3s16 p) {
1535 m_transforming_liquid.push_back(p);
1538 s32 Map::transforming_liquid_size() {
1539 return m_transforming_liquid.size();
1542 void Map::transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks)
1545 INodeDefManager *nodemgr = m_gamedef->ndef();
1547 DSTACK(__FUNCTION_NAME);
1548 //TimeTaker timer("transformLiquids()");
1551 u32 initial_size = m_transforming_liquid.size();
1553 /*if(initial_size != 0)
1554 infostream<<"transformLiquids(): initial_size="<<initial_size<<std::endl;*/
1556 // list of nodes that due to viscosity have not reached their max level height
1557 std::deque<v3s16> must_reflow;
1559 // List of MapBlocks that will require a lighting update (due to lava)
1560 std::map<v3s16, MapBlock*> lighting_modified_blocks;
1562 u32 liquid_loop_max = g_settings->getS32("liquid_loop_max");
1563 u32 loop_max = liquid_loop_max;
1567 /* If liquid_loop_max is not keeping up with the queue size increase
1568 * loop_max up to a maximum of liquid_loop_max * dedicated_server_step.
1570 if (m_transforming_liquid.size() > loop_max * 2) {
1572 float server_step = g_settings->getFloat("dedicated_server_step");
1573 if (m_transforming_liquid_loop_count_multiplier - 1.0 < server_step)
1574 m_transforming_liquid_loop_count_multiplier *= 1.0 + server_step / 10;
1576 m_transforming_liquid_loop_count_multiplier = 1.0;
1579 loop_max *= m_transforming_liquid_loop_count_multiplier;
1582 while(m_transforming_liquid.size() != 0)
1584 // This should be done here so that it is done when continue is used
1585 if(loopcount >= initial_size || loopcount >= loop_max)
1590 Get a queued transforming liquid node
1592 v3s16 p0 = m_transforming_liquid.front();
1593 m_transforming_liquid.pop_front();
1595 MapNode n0 = getNodeNoEx(p0);
1598 Collect information about current node
1600 s8 liquid_level = -1;
1601 content_t liquid_kind = CONTENT_IGNORE;
1602 LiquidType liquid_type = nodemgr->get(n0).liquid_type;
1603 switch (liquid_type) {
1605 liquid_level = LIQUID_LEVEL_SOURCE;
1606 liquid_kind = nodemgr->getId(nodemgr->get(n0).liquid_alternative_flowing);
1608 case LIQUID_FLOWING:
1609 liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
1610 liquid_kind = n0.getContent();
1613 // if this is an air node, it *could* be transformed into a liquid. otherwise,
1614 // continue with the next node.
1615 if (n0.getContent() != CONTENT_AIR)
1617 liquid_kind = CONTENT_AIR;
1622 Collect information about the environment
1624 const v3s16 *dirs = g_6dirs;
1625 NodeNeighbor sources[6]; // surrounding sources
1626 int num_sources = 0;
1627 NodeNeighbor flows[6]; // surrounding flowing liquid nodes
1629 NodeNeighbor airs[6]; // surrounding air
1631 NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid
1632 int num_neutrals = 0;
1633 bool flowing_down = false;
1634 for (u16 i = 0; i < 6; i++) {
1635 NeighborType nt = NEIGHBOR_SAME_LEVEL;
1638 nt = NEIGHBOR_UPPER;
1641 nt = NEIGHBOR_LOWER;
1644 v3s16 npos = p0 + dirs[i];
1645 NodeNeighbor nb(getNodeNoEx(npos), nt, npos);
1646 switch (nodemgr->get(nb.n.getContent()).liquid_type) {
1648 if (nb.n.getContent() == CONTENT_AIR) {
1649 airs[num_airs++] = nb;
1650 // if the current node is a water source the neighbor
1651 // should be enqueded for transformation regardless of whether the
1652 // current node changes or not.
1653 if (nb.t != NEIGHBOR_UPPER && liquid_type != LIQUID_NONE)
1654 m_transforming_liquid.push_back(npos);
1655 // if the current node happens to be a flowing node, it will start to flow down here.
1656 if (nb.t == NEIGHBOR_LOWER) {
1657 flowing_down = true;
1660 neutrals[num_neutrals++] = nb;
1664 // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
1665 if (liquid_kind == CONTENT_AIR)
1666 liquid_kind = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing);
1667 if (nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing) != liquid_kind) {
1668 neutrals[num_neutrals++] = nb;
1670 // Do not count bottom source, it will screw things up
1672 sources[num_sources++] = nb;
1675 case LIQUID_FLOWING:
1676 // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
1677 if (liquid_kind == CONTENT_AIR)
1678 liquid_kind = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing);
1679 if (nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing) != liquid_kind) {
1680 neutrals[num_neutrals++] = nb;
1682 flows[num_flows++] = nb;
1683 if (nb.t == NEIGHBOR_LOWER)
1684 flowing_down = true;
1691 decide on the type (and possibly level) of the current node
1693 content_t new_node_content;
1694 s8 new_node_level = -1;
1695 s8 max_node_level = -1;
1697 u8 range = nodemgr->get(liquid_kind).liquid_range;
1698 if (range > LIQUID_LEVEL_MAX+1)
1699 range = LIQUID_LEVEL_MAX+1;
1701 if ((num_sources >= 2 && nodemgr->get(liquid_kind).liquid_renewable) || liquid_type == LIQUID_SOURCE) {
1702 // liquid_kind will be set to either the flowing alternative of the node (if it's a liquid)
1703 // or the flowing alternative of the first of the surrounding sources (if it's air), so
1704 // it's perfectly safe to use liquid_kind here to determine the new node content.
1705 new_node_content = nodemgr->getId(nodemgr->get(liquid_kind).liquid_alternative_source);
1706 } else if (num_sources >= 1 && sources[0].t != NEIGHBOR_LOWER) {
1707 // liquid_kind is set properly, see above
1708 new_node_content = liquid_kind;
1709 max_node_level = new_node_level = LIQUID_LEVEL_MAX;
1710 if (new_node_level < (LIQUID_LEVEL_MAX+1-range))
1711 new_node_content = CONTENT_AIR;
1713 // no surrounding sources, so get the maximum level that can flow into this node
1714 for (u16 i = 0; i < num_flows; i++) {
1715 u8 nb_liquid_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
1716 switch (flows[i].t) {
1717 case NEIGHBOR_UPPER:
1718 if (nb_liquid_level + WATER_DROP_BOOST > max_node_level) {
1719 max_node_level = LIQUID_LEVEL_MAX;
1720 if (nb_liquid_level + WATER_DROP_BOOST < LIQUID_LEVEL_MAX)
1721 max_node_level = nb_liquid_level + WATER_DROP_BOOST;
1722 } else if (nb_liquid_level > max_node_level)
1723 max_node_level = nb_liquid_level;
1725 case NEIGHBOR_LOWER:
1727 case NEIGHBOR_SAME_LEVEL:
1728 if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK &&
1729 nb_liquid_level > 0 && nb_liquid_level - 1 > max_node_level) {
1730 max_node_level = nb_liquid_level - 1;
1736 u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity;
1737 if (viscosity > 1 && max_node_level != liquid_level) {
1738 // amount to gain, limited by viscosity
1739 // must be at least 1 in absolute value
1740 s8 level_inc = max_node_level - liquid_level;
1741 if (level_inc < -viscosity || level_inc > viscosity)
1742 new_node_level = liquid_level + level_inc/viscosity;
1743 else if (level_inc < 0)
1744 new_node_level = liquid_level - 1;
1745 else if (level_inc > 0)
1746 new_node_level = liquid_level + 1;
1747 if (new_node_level != max_node_level)
1748 must_reflow.push_back(p0);
1750 new_node_level = max_node_level;
1752 if (max_node_level >= (LIQUID_LEVEL_MAX+1-range))
1753 new_node_content = liquid_kind;
1755 new_node_content = CONTENT_AIR;
1760 check if anything has changed. if not, just continue with the next node.
1762 if (new_node_content == n0.getContent() && (nodemgr->get(n0.getContent()).liquid_type != LIQUID_FLOWING ||
1763 ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level &&
1764 ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)
1770 update the current node
1773 //bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
1774 if (nodemgr->get(new_node_content).liquid_type == LIQUID_FLOWING) {
1775 // set level to last 3 bits, flowing down bit to 4th bit
1776 n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK);
1778 // set the liquid level and flow bit to 0
1779 n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK);
1781 n0.setContent(new_node_content);
1783 // Find out whether there is a suspect for this action
1784 std::string suspect;
1785 if(m_gamedef->rollback()) {
1786 suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1);
1789 if(m_gamedef->rollback() && !suspect.empty()){
1791 RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true);
1792 // Get old node for rollback
1793 RollbackNode rollback_oldnode(this, p0, m_gamedef);
1797 RollbackNode rollback_newnode(this, p0, m_gamedef);
1798 RollbackAction action;
1799 action.setSetNode(p0, rollback_oldnode, rollback_newnode);
1800 m_gamedef->rollback()->reportAction(action);
1806 v3s16 blockpos = getNodeBlockPos(p0);
1807 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1809 modified_blocks[blockpos] = block;
1810 // If new or old node emits light, MapBlock requires lighting update
1811 if(nodemgr->get(n0).light_source != 0 ||
1812 nodemgr->get(n00).light_source != 0)
1813 lighting_modified_blocks[block->getPos()] = block;
1817 enqueue neighbors for update if neccessary
1819 switch (nodemgr->get(n0.getContent()).liquid_type) {
1821 case LIQUID_FLOWING:
1822 // make sure source flows into all neighboring nodes
1823 for (u16 i = 0; i < num_flows; i++)
1824 if (flows[i].t != NEIGHBOR_UPPER)
1825 m_transforming_liquid.push_back(flows[i].p);
1826 for (u16 i = 0; i < num_airs; i++)
1827 if (airs[i].t != NEIGHBOR_UPPER)
1828 m_transforming_liquid.push_back(airs[i].p);
1831 // this flow has turned to air; neighboring flows might need to do the same
1832 for (u16 i = 0; i < num_flows; i++)
1833 m_transforming_liquid.push_back(flows[i].p);
1837 //infostream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
1839 for (std::deque<v3s16>::iterator iter = must_reflow.begin(); iter != must_reflow.end(); ++iter)
1840 m_transforming_liquid.push_back(*iter);
1842 updateLighting(lighting_modified_blocks, modified_blocks);
1845 /* ----------------------------------------------------------------------
1846 * Manage the queue so that it does not grow indefinately
1848 u16 time_until_purge = g_settings->getU16("liquid_queue_purge_time");
1850 if (time_until_purge == 0)
1851 return; // Feature disabled
1853 time_until_purge *= 1000; // seconds -> milliseconds
1855 u32 curr_time = getTime(PRECISION_MILLI);
1856 u32 prev_unprocessed = m_unprocessed_count;
1857 m_unprocessed_count = m_transforming_liquid.size();
1859 // if unprocessed block count is decreasing or stable
1860 if (m_unprocessed_count <= prev_unprocessed) {
1861 m_queue_size_timer_started = false;
1863 if (!m_queue_size_timer_started)
1864 m_inc_trending_up_start_time = curr_time;
1865 m_queue_size_timer_started = true;
1868 // Account for curr_time overflowing
1869 if (m_queue_size_timer_started && m_inc_trending_up_start_time > curr_time)
1870 m_queue_size_timer_started = false;
1872 /* If the queue has been growing for more than liquid_queue_purge_time seconds
1873 * and the number of unprocessed blocks is still > liquid_loop_max then we
1874 * cannot keep up; dump the oldest blocks from the queue so that the queue
1875 * has liquid_loop_max items in it
1877 if (m_queue_size_timer_started
1878 && curr_time - m_inc_trending_up_start_time > time_until_purge
1879 && m_unprocessed_count > liquid_loop_max) {
1881 size_t dump_qty = m_unprocessed_count - liquid_loop_max;
1883 infostream << "transformLiquids(): DUMPING " << dump_qty
1884 << " blocks from the queue" << std::endl;
1887 m_transforming_liquid.pop_front();
1889 m_queue_size_timer_started = false; // optimistically assume we can keep up now
1890 m_unprocessed_count = m_transforming_liquid.size();
1894 NodeMetadata *Map::getNodeMetadata(v3s16 p)
1896 v3s16 blockpos = getNodeBlockPos(p);
1897 v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
1898 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1900 infostream<<"Map::getNodeMetadata(): Need to emerge "
1901 <<PP(blockpos)<<std::endl;
1902 block = emergeBlock(blockpos, false);
1905 infostream<<"WARNING: Map::getNodeMetadata(): Block not found"
1909 NodeMetadata *meta = block->m_node_metadata.get(p_rel);
1913 bool Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
1915 v3s16 blockpos = getNodeBlockPos(p);
1916 v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
1917 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1919 infostream<<"Map::setNodeMetadata(): Need to emerge "
1920 <<PP(blockpos)<<std::endl;
1921 block = emergeBlock(blockpos, false);
1924 infostream<<"WARNING: Map::setNodeMetadata(): Block not found"
1928 block->m_node_metadata.set(p_rel, meta);
1932 void Map::removeNodeMetadata(v3s16 p)
1934 v3s16 blockpos = getNodeBlockPos(p);
1935 v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
1936 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1939 infostream<<"WARNING: Map::removeNodeMetadata(): Block not found"
1943 block->m_node_metadata.remove(p_rel);
1946 NodeTimer Map::getNodeTimer(v3s16 p)
1948 v3s16 blockpos = getNodeBlockPos(p);
1949 v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
1950 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1952 infostream<<"Map::getNodeTimer(): Need to emerge "
1953 <<PP(blockpos)<<std::endl;
1954 block = emergeBlock(blockpos, false);
1957 infostream<<"WARNING: Map::getNodeTimer(): Block not found"
1961 NodeTimer t = block->m_node_timers.get(p_rel);
1965 void Map::setNodeTimer(v3s16 p, NodeTimer t)
1967 v3s16 blockpos = getNodeBlockPos(p);
1968 v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
1969 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1971 infostream<<"Map::setNodeTimer(): Need to emerge "
1972 <<PP(blockpos)<<std::endl;
1973 block = emergeBlock(blockpos, false);
1976 infostream<<"WARNING: Map::setNodeTimer(): Block not found"
1980 block->m_node_timers.set(p_rel, t);
1983 void Map::removeNodeTimer(v3s16 p)
1985 v3s16 blockpos = getNodeBlockPos(p);
1986 v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
1987 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1990 infostream<<"WARNING: Map::removeNodeTimer(): Block not found"
1994 block->m_node_timers.remove(p_rel);
2000 ServerMap::ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emerge):
2001 Map(dout_server, gamedef),
2003 m_map_metadata_changed(true)
2005 verbosestream<<__FUNCTION_NAME<<std::endl;
2008 Try to load map; if not found, create a new one.
2011 // Determine which database backend to use
2012 std::string conf_path = savedir + DIR_DELIM + "world.mt";
2014 bool succeeded = conf.readConfigFile(conf_path.c_str());
2015 if (!succeeded || !conf.exists("backend")) {
2016 // fall back to sqlite3
2017 conf.set("backend", "sqlite3");
2019 std::string backend = conf.get("backend");
2020 dbase = createDatabase(backend, savedir, conf);
2022 if (!conf.updateConfigFile(conf_path.c_str()))
2023 errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl;
2025 m_savedir = savedir;
2026 m_map_saving_enabled = false;
2030 // If directory exists, check contents and load if possible
2031 if(fs::PathExists(m_savedir))
2033 // If directory is empty, it is safe to save into it.
2034 if(fs::GetDirListing(m_savedir).size() == 0)
2036 infostream<<"ServerMap: Empty save directory is valid."
2038 m_map_saving_enabled = true;
2043 // Load map metadata (seed, chunksize)
2046 catch(SettingNotFoundException &e){
2047 infostream<<"ServerMap: Some metadata not found."
2048 <<" Using default settings."<<std::endl;
2050 catch(FileNotGoodException &e){
2051 infostream<<"WARNING: Could not load map metadata"
2052 //<<" Disabling chunk-based generator."
2057 infostream<<"ServerMap: Successfully loaded map "
2058 <<"metadata from "<<savedir
2059 <<", assuming valid save directory."
2060 <<" seed="<< m_emerge->params.seed <<"."
2063 m_map_saving_enabled = true;
2064 // Map loaded, not creating new one
2068 // If directory doesn't exist, it is safe to save to it
2070 m_map_saving_enabled = true;
2073 catch(std::exception &e)
2075 infostream<<"WARNING: ServerMap: Failed to load map from "<<savedir
2076 <<", exception: "<<e.what()<<std::endl;
2077 infostream<<"Please remove the map or fix it."<<std::endl;
2078 infostream<<"WARNING: Map saving will be disabled."<<std::endl;
2081 infostream<<"Initializing new map."<<std::endl;
2083 // Create zero sector
2084 emergeSector(v2s16(0,0));
2086 // Initially write whole map
2087 save(MOD_STATE_CLEAN);
2090 ServerMap::~ServerMap()
2092 verbosestream<<__FUNCTION_NAME<<std::endl;
2096 if(m_map_saving_enabled)
2098 // Save only changed parts
2099 save(MOD_STATE_WRITE_AT_UNLOAD);
2100 infostream<<"ServerMap: Saved map to "<<m_savedir<<std::endl;
2104 infostream<<"ServerMap: Map not saved"<<std::endl;
2107 catch(std::exception &e)
2109 infostream<<"ServerMap: Failed to save map to "<<m_savedir
2110 <<", exception: "<<e.what()<<std::endl;
2114 Close database if it was opened
2122 core::map<v2s16, MapChunk*>::Iterator i = m_chunks.getIterator();
2123 for(; i.atEnd() == false; i++)
2125 MapChunk *chunk = i.getNode()->getValue();
2131 u64 ServerMap::getSeed()
2133 return m_emerge->params.seed;
2136 s16 ServerMap::getWaterLevel()
2138 return m_emerge->params.water_level;
2141 bool ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos)
2143 bool enable_mapgen_debug_info = m_emerge->mapgen_debug_info;
2144 EMERGE_DBG_OUT("initBlockMake(): " PP(blockpos) " - " PP(blockpos));
2146 s16 chunksize = m_emerge->params.chunksize;
2147 s16 coffset = -chunksize / 2;
2148 v3s16 chunk_offset(coffset, coffset, coffset);
2149 v3s16 blockpos_div = getContainerPos(blockpos - chunk_offset, chunksize);
2150 v3s16 blockpos_min = blockpos_div * chunksize;
2151 v3s16 blockpos_max = blockpos_div * chunksize + v3s16(1,1,1)*(chunksize-1);
2152 blockpos_min += chunk_offset;
2153 blockpos_max += chunk_offset;
2155 v3s16 extra_borders(1,1,1);
2157 // Do nothing if not inside limits (+-1 because of neighbors)
2158 if(blockpos_over_limit(blockpos_min - extra_borders) ||
2159 blockpos_over_limit(blockpos_max + extra_borders))
2162 data->seed = m_emerge->params.seed;
2163 data->blockpos_min = blockpos_min;
2164 data->blockpos_max = blockpos_max;
2165 data->blockpos_requested = blockpos;
2166 data->nodedef = m_gamedef->ndef();
2169 Create the whole area of this and the neighboring blocks
2172 //TimeTaker timer("initBlockMake() create area");
2174 for(s16 x=blockpos_min.X-extra_borders.X;
2175 x<=blockpos_max.X+extra_borders.X; x++)
2176 for(s16 z=blockpos_min.Z-extra_borders.Z;
2177 z<=blockpos_max.Z+extra_borders.Z; z++)
2179 v2s16 sectorpos(x, z);
2180 // Sector metadata is loaded from disk if not already loaded.
2181 ServerMapSector *sector = createSector(sectorpos);
2182 FATAL_ERROR_IF(sector == NULL, "createSector() failed");
2185 for(s16 y=blockpos_min.Y-extra_borders.Y;
2186 y<=blockpos_max.Y+extra_borders.Y; y++)
2189 //MapBlock *block = createBlock(p);
2190 // 1) get from memory, 2) load from disk
2191 MapBlock *block = emergeBlock(p, false);
2192 // 3) create a blank one
2195 block = createBlock(p);
2198 Block gets sunlight if this is true.
2200 Refer to the map generator heuristics.
2202 bool ug = m_emerge->isBlockUnderground(p);
2203 block->setIsUnderground(ug);
2206 // Lighting will not be valid after make_chunk is called
2207 block->setLightingExpired(true);
2208 // Lighting will be calculated
2209 //block->setLightingExpired(false);
2215 Now we have a big empty area.
2217 Make a ManualMapVoxelManipulator that contains this and the
2221 // The area that contains this block and it's neighbors
2222 v3s16 bigarea_blocks_min = blockpos_min - extra_borders;
2223 v3s16 bigarea_blocks_max = blockpos_max + extra_borders;
2225 data->vmanip = new MMVManip(this);
2226 //data->vmanip->setMap(this);
2230 //TimeTaker timer("initBlockMake() initialEmerge");
2231 data->vmanip->initialEmerge(bigarea_blocks_min, bigarea_blocks_max);
2234 // Ensure none of the blocks to be generated were marked as containing CONTENT_IGNORE
2235 /* for (s16 z = blockpos_min.Z; z <= blockpos_max.Z; z++) {
2236 for (s16 y = blockpos_min.Y; y <= blockpos_max.Y; y++) {
2237 for (s16 x = blockpos_min.X; x <= blockpos_max.X; x++) {
2238 core::map<v3s16, u8>::Node *n;
2239 n = data->vmanip->m_loaded_blocks.find(v3s16(x, y, z));
2242 u8 flags = n->getValue();
2243 flags &= ~VMANIP_BLOCK_CONTAINS_CIGNORE;
2249 // Data is ready now.
2253 void ServerMap::finishBlockMake(BlockMakeData *data,
2254 std::map<v3s16, MapBlock*> &changed_blocks)
2256 v3s16 blockpos_min = data->blockpos_min;
2257 v3s16 blockpos_max = data->blockpos_max;
2258 v3s16 blockpos_requested = data->blockpos_requested;
2259 /*infostream<<"finishBlockMake(): ("<<blockpos_requested.X<<","
2260 <<blockpos_requested.Y<<","
2261 <<blockpos_requested.Z<<")"<<std::endl;*/
2263 v3s16 extra_borders(1,1,1);
2265 bool enable_mapgen_debug_info = m_emerge->mapgen_debug_info;
2267 /*infostream<<"Resulting vmanip:"<<std::endl;
2268 data->vmanip.print(infostream);*/
2270 // Make sure affected blocks are loaded
2271 for(s16 x=blockpos_min.X-extra_borders.X;
2272 x<=blockpos_max.X+extra_borders.X; x++)
2273 for(s16 z=blockpos_min.Z-extra_borders.Z;
2274 z<=blockpos_max.Z+extra_borders.Z; z++)
2275 for(s16 y=blockpos_min.Y-extra_borders.Y;
2276 y<=blockpos_max.Y+extra_borders.Y; y++)
2279 // Load from disk if not already in memory
2280 emergeBlock(p, false);
2284 Blit generated stuff to map
2285 NOTE: blitBackAll adds nearly everything to changed_blocks
2289 //TimeTaker timer("finishBlockMake() blitBackAll");
2290 data->vmanip->blitBackAll(&changed_blocks);
2293 EMERGE_DBG_OUT("finishBlockMake: changed_blocks.size()=" << changed_blocks.size());
2296 Copy transforming liquid information
2298 while(data->transforming_liquid.size() > 0)
2300 m_transforming_liquid.push_back(data->transforming_liquid.front());
2301 data->transforming_liquid.pop_front();
2305 Do stuff in central blocks
2313 TimeTaker t("finishBlockMake lighting update");
2315 core::map<v3s16, MapBlock*> lighting_update_blocks;
2318 for(s16 x=blockpos_min.X-extra_borders.X;
2319 x<=blockpos_max.X+extra_borders.X; x++)
2320 for(s16 z=blockpos_min.Z-extra_borders.Z;
2321 z<=blockpos_max.Z+extra_borders.Z; z++)
2322 for(s16 y=blockpos_min.Y-extra_borders.Y;
2323 y<=blockpos_max.Y+extra_borders.Y; y++)
2326 MapBlock *block = getBlockNoCreateNoEx(p);
2328 lighting_update_blocks.insert(block->getPos(), block);
2331 updateLighting(lighting_update_blocks, changed_blocks);
2335 Set lighting to non-expired state in all of them.
2336 This is cheating, but it is not fast enough if all of them
2337 would actually be updated.
2339 for(s16 x=blockpos_min.X-extra_borders.X;
2340 x<=blockpos_max.X+extra_borders.X; x++)
2341 for(s16 z=blockpos_min.Z-extra_borders.Z;
2342 z<=blockpos_max.Z+extra_borders.Z; z++)
2343 for(s16 y=blockpos_min.Y-extra_borders.Y;
2344 y<=blockpos_max.Y+extra_borders.Y; y++)
2347 MapBlock * block = getBlockNoCreateNoEx(p);
2349 block->setLightingExpired(false);
2353 if(enable_mapgen_debug_info == false)
2354 t.stop(true); // Hide output
2359 Go through changed blocks
2361 for(std::map<v3s16, MapBlock*>::iterator i = changed_blocks.begin();
2362 i != changed_blocks.end(); ++i)
2364 MapBlock *block = i->second;
2368 Update day/night difference cache of the MapBlocks
2370 block->expireDayNightDiff();
2372 Set block as modified
2374 block->raiseModified(MOD_STATE_WRITE_NEEDED,
2375 "finishBlockMake expireDayNightDiff");
2379 Set central blocks as generated
2381 for(s16 x=blockpos_min.X; x<=blockpos_max.X; x++)
2382 for(s16 z=blockpos_min.Z; z<=blockpos_max.Z; z++)
2383 for(s16 y=blockpos_min.Y; y<=blockpos_max.Y; y++)
2386 MapBlock *block = getBlockNoCreateNoEx(p);
2389 block->setGenerated(true);
2393 Save changed parts of map
2394 NOTE: Will be saved later.
2396 //save(MOD_STATE_WRITE_AT_UNLOAD);
2398 /*infostream<<"finishBlockMake() done for ("<<blockpos_requested.X
2399 <<","<<blockpos_requested.Y<<","
2400 <<blockpos_requested.Z<<")"<<std::endl;*/
2404 if(enable_mapgen_debug_info)
2407 Analyze resulting blocks
2409 /*for(s16 x=blockpos_min.X-1; x<=blockpos_max.X+1; x++)
2410 for(s16 z=blockpos_min.Z-1; z<=blockpos_max.Z+1; z++)
2411 for(s16 y=blockpos_min.Y-1; y<=blockpos_max.Y+1; y++)*/
2412 for(s16 x=blockpos_min.X-0; x<=blockpos_max.X+0; x++)
2413 for(s16 z=blockpos_min.Z-0; z<=blockpos_max.Z+0; z++)
2414 for(s16 y=blockpos_min.Y-0; y<=blockpos_max.Y+0; y++)
2416 v3s16 p = v3s16(x,y,z);
2417 MapBlock *block = getBlockNoCreateNoEx(p);
2419 snprintf(spos, 20, "(%2d,%2d,%2d)", x, y, z);
2420 infostream<<"Generated "<<spos<<": "
2421 <<analyze_block(block)<<std::endl;
2426 getBlockNoCreateNoEx(blockpos_requested);
2429 ServerMapSector * ServerMap::createSector(v2s16 p2d)
2431 DSTACKF("%s: p2d=(%d,%d)",
2436 Check if it exists already in memory
2438 ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d);
2443 Try to load it from disk (with blocks)
2445 //if(loadSectorFull(p2d) == true)
2448 Try to load metadata from disk
2451 if(loadSectorMeta(p2d) == true)
2453 ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d);
2456 infostream<<"ServerMap::createSector(): loadSectorFull didn't make a sector"<<std::endl;
2457 throw InvalidPositionException("");
2463 Do not create over-limit
2465 if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2466 || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2467 || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2468 || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
2469 throw InvalidPositionException("createSector(): pos. over limit");
2472 Generate blank sector
2475 sector = new ServerMapSector(this, p2d, m_gamedef);
2477 // Sector position on map in nodes
2478 //v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
2483 m_sectors[p2d] = sector;
2490 This is a quick-hand function for calling makeBlock().
2492 MapBlock * ServerMap::generateBlock(
2494 std::map<v3s16, MapBlock*> &modified_blocks
2497 DSTACKF("%s: p=(%d,%d,%d)", __FUNCTION_NAME, p.X, p.Y, p.Z);
2499 /*infostream<<"generateBlock(): "
2500 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2503 bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
2505 TimeTaker timer("generateBlock");
2507 //MapBlock *block = original_dummy;
2509 v2s16 p2d(p.X, p.Z);
2510 v2s16 p2d_nodes = p2d * MAP_BLOCKSIZE;
2513 Do not generate over-limit
2515 if(blockpos_over_limit(p))
2517 infostream<<__FUNCTION_NAME<<": Block position over limit"<<std::endl;
2518 throw InvalidPositionException("generateBlock(): pos. over limit");
2522 Create block make data
2525 initBlockMake(&data, p);
2531 TimeTaker t("mapgen::make_block()");
2532 mapgen->makeChunk(&data);
2533 //mapgen::make_block(&data);
2535 if(enable_mapgen_debug_info == false)
2536 t.stop(true); // Hide output
2540 Blit data back on map, update lighting, add mobs and whatever this does
2542 finishBlockMake(&data, modified_blocks);
2547 MapBlock *block = getBlockNoCreateNoEx(p);
2555 bool erroneus_content = false;
2556 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
2557 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
2558 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
2561 MapNode n = block->getNode(p);
2562 if(n.getContent() == CONTENT_IGNORE)
2564 infostream<<"CONTENT_IGNORE at "
2565 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2567 erroneus_content = true;
2571 if(erroneus_content)
2580 Generate a completely empty block
2584 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
2585 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
2587 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
2590 n.setContent(CONTENT_AIR);
2591 block->setNode(v3s16(x0,y0,z0), n);
2597 if(enable_mapgen_debug_info == false)
2598 timer.stop(true); // Hide output
2604 MapBlock * ServerMap::createBlock(v3s16 p)
2606 DSTACKF("%s: p=(%d,%d,%d)",
2607 __FUNCTION_NAME, p.X, p.Y, p.Z);
2610 Do not create over-limit
2612 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2613 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2614 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2615 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2616 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
2617 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
2618 throw InvalidPositionException("createBlock(): pos. over limit");
2620 v2s16 p2d(p.X, p.Z);
2623 This will create or load a sector if not found in memory.
2624 If block exists on disk, it will be loaded.
2626 NOTE: On old save formats, this will be slow, as it generates
2627 lighting on blocks for them.
2629 ServerMapSector *sector;
2631 sector = (ServerMapSector*)createSector(p2d);
2632 assert(sector->getId() == MAPSECTOR_SERVER);
2634 catch(InvalidPositionException &e)
2636 infostream<<"createBlock: createSector() failed"<<std::endl;
2640 NOTE: This should not be done, or at least the exception
2641 should not be passed on as std::exception, because it
2642 won't be catched at all.
2644 /*catch(std::exception &e)
2646 infostream<<"createBlock: createSector() failed: "
2647 <<e.what()<<std::endl;
2652 Try to get a block from the sector
2655 MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
2658 if(block->isDummy())
2663 block = sector->createBlankBlock(block_y);
2668 MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank)
2670 DSTACKF("%s: p=(%d,%d,%d), create_blank=%d",
2672 p.X, p.Y, p.Z, create_blank);
2675 MapBlock *block = getBlockNoCreateNoEx(p);
2676 if(block && block->isDummy() == false)
2681 MapBlock *block = loadBlock(p);
2687 ServerMapSector *sector = createSector(v2s16(p.X, p.Z));
2688 MapBlock *block = sector->createBlankBlock(p.Y);
2696 std::map<v3s16, MapBlock*> modified_blocks;
2697 MapBlock *block = generateBlock(p, modified_blocks);
2701 event.type = MEET_OTHER;
2704 // Copy modified_blocks to event
2705 for(std::map<v3s16, MapBlock*>::iterator
2706 i = modified_blocks.begin();
2707 i != modified_blocks.end(); ++i)
2709 event.modified_blocks.insert(i->first);
2713 dispatchEvent(&event);
2723 MapBlock *ServerMap::getBlockOrEmerge(v3s16 p3d)
2725 MapBlock *block = getBlockNoCreateNoEx(p3d);
2727 m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, p3d, false);
2732 void ServerMap::prepareBlock(MapBlock *block) {
2735 // N.B. This requires no synchronization, since data will not be modified unless
2736 // the VoxelManipulator being updated belongs to the same thread.
2737 void ServerMap::updateVManip(v3s16 pos)
2739 Mapgen *mg = m_emerge->getCurrentMapgen();
2743 MMVManip *vm = mg->vm;
2747 if (!vm->m_area.contains(pos))
2750 s32 idx = vm->m_area.index(pos);
2751 vm->m_data[idx] = getNodeNoEx(pos);
2752 vm->m_flags[idx] &= ~VOXELFLAG_NO_DATA;
2754 vm->m_is_dirty = true;
2757 s16 ServerMap::findGroundLevel(v2s16 p2d)
2761 Uh, just do something random...
2763 // Find existing map from top to down
2766 v3s16 p(p2d.X, max, p2d.Y);
2767 for(; p.Y>min; p.Y--)
2769 MapNode n = getNodeNoEx(p);
2770 if(n.getContent() != CONTENT_IGNORE)
2775 // If this node is not air, go to plan b
2776 if(getNodeNoEx(p).getContent() != CONTENT_AIR)
2778 // Search existing walkable and return it
2779 for(; p.Y>min; p.Y--)
2781 MapNode n = getNodeNoEx(p);
2782 if(content_walkable(n.d) && n.getContent() != CONTENT_IGNORE)
2791 Determine from map generator noise functions
2794 s16 level = m_emerge->getGroundLevelAtPoint(p2d);
2797 //double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT;
2798 //return (s16)level;
2801 bool ServerMap::loadFromFolders() {
2802 if (!dbase->initialized() &&
2803 !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite"))
2808 void ServerMap::createDirs(std::string path)
2810 if(fs::CreateAllDirs(path) == false)
2812 m_dout<<DTIME<<"ServerMap: Failed to create directory "
2813 <<"\""<<path<<"\""<<std::endl;
2814 throw BaseException("ServerMap failed to create directory");
2818 std::string ServerMap::getSectorDir(v2s16 pos, int layout)
2824 snprintf(cc, 9, "%.4x%.4x",
2825 (unsigned int) pos.X & 0xffff,
2826 (unsigned int) pos.Y & 0xffff);
2828 return m_savedir + DIR_DELIM + "sectors" + DIR_DELIM + cc;
2830 snprintf(cc, 9, (std::string("%.3x") + DIR_DELIM + "%.3x").c_str(),
2831 (unsigned int) pos.X & 0xfff,
2832 (unsigned int) pos.Y & 0xfff);
2834 return m_savedir + DIR_DELIM + "sectors2" + DIR_DELIM + cc;
2841 v2s16 ServerMap::getSectorPos(std::string dirname)
2843 unsigned int x = 0, y = 0;
2845 std::string component;
2846 fs::RemoveLastPathComponent(dirname, &component, 1);
2847 if(component.size() == 8)
2850 r = sscanf(component.c_str(), "%4x%4x", &x, &y);
2852 else if(component.size() == 3)
2855 fs::RemoveLastPathComponent(dirname, &component, 2);
2856 r = sscanf(component.c_str(), (std::string("%3x") + DIR_DELIM + "%3x").c_str(), &x, &y);
2857 // Sign-extend the 12 bit values up to 16 bits...
2858 if(x & 0x800) x |= 0xF000;
2859 if(y & 0x800) y |= 0xF000;
2866 FATAL_ERROR_IF(r != 2, "getSectorPos()");
2867 v2s16 pos((s16)x, (s16)y);
2871 v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
2873 v2s16 p2d = getSectorPos(sectordir);
2875 if(blockfile.size() != 4){
2876 throw InvalidFilenameException("Invalid block filename");
2879 int r = sscanf(blockfile.c_str(), "%4x", &y);
2881 throw InvalidFilenameException("Invalid block filename");
2882 return v3s16(p2d.X, y, p2d.Y);
2885 std::string ServerMap::getBlockFilename(v3s16 p)
2888 snprintf(cc, 5, "%.4x", (unsigned int)p.Y&0xffff);
2892 void ServerMap::save(ModifiedState save_level)
2894 DSTACK(__FUNCTION_NAME);
2895 if(m_map_saving_enabled == false) {
2896 infostream<<"WARNING: Not saving map, saving disabled."<<std::endl;
2900 if(save_level == MOD_STATE_CLEAN)
2901 infostream<<"ServerMap: Saving whole map, this can take time."
2904 if(m_map_metadata_changed || save_level == MOD_STATE_CLEAN) {
2908 // Profile modified reasons
2909 Profiler modprofiler;
2911 u32 sector_meta_count = 0;
2912 u32 block_count = 0;
2913 u32 block_count_all = 0; // Number of blocks in memory
2915 // Don't do anything with sqlite unless something is really saved
2916 bool save_started = false;
2918 for(std::map<v2s16, MapSector*>::iterator i = m_sectors.begin();
2919 i != m_sectors.end(); ++i) {
2920 ServerMapSector *sector = (ServerMapSector*)i->second;
2921 assert(sector->getId() == MAPSECTOR_SERVER);
2923 if(sector->differs_from_disk || save_level == MOD_STATE_CLEAN) {
2924 saveSectorMeta(sector);
2925 sector_meta_count++;
2928 MapBlockVect blocks;
2929 sector->getBlocks(blocks);
2931 for(MapBlockVect::iterator j = blocks.begin();
2932 j != blocks.end(); ++j) {
2933 MapBlock *block = *j;
2937 if(block->getModified() >= (u32)save_level) {
2941 save_started = true;
2944 modprofiler.add(block->getModifiedReason(), 1);
2949 /*infostream<<"ServerMap: Written block ("
2950 <<block->getPos().X<<","
2951 <<block->getPos().Y<<","
2952 <<block->getPos().Z<<")"
2962 Only print if something happened or saved whole map
2964 if(save_level == MOD_STATE_CLEAN || sector_meta_count != 0
2965 || block_count != 0) {
2966 infostream<<"ServerMap: Written: "
2967 <<sector_meta_count<<" sector metadata files, "
2968 <<block_count<<" block files"
2969 <<", "<<block_count_all<<" blocks in memory."
2971 PrintInfo(infostream); // ServerMap/ClientMap:
2972 infostream<<"Blocks modified by: "<<std::endl;
2973 modprofiler.print(infostream);
2977 void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst)
2979 if (loadFromFolders()) {
2980 errorstream << "Map::listAllLoadableBlocks(): Result will be missing "
2981 << "all blocks that are stored in flat files." << std::endl;
2983 dbase->listAllLoadableBlocks(dst);
2986 void ServerMap::listAllLoadedBlocks(std::vector<v3s16> &dst)
2988 for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin();
2989 si != m_sectors.end(); ++si)
2991 MapSector *sector = si->second;
2993 MapBlockVect blocks;
2994 sector->getBlocks(blocks);
2996 for(MapBlockVect::iterator i = blocks.begin();
2997 i != blocks.end(); ++i) {
2998 v3s16 p = (*i)->getPos();
3004 void ServerMap::saveMapMeta()
3006 DSTACK(__FUNCTION_NAME);
3008 /*infostream<<"ServerMap::saveMapMeta(): "
3012 createDirs(m_savedir);
3014 std::string fullpath = m_savedir + DIR_DELIM "map_meta.txt";
3015 std::ostringstream ss(std::ios_base::binary);
3019 m_emerge->saveParamsToSettings(¶ms);
3020 params.writeLines(ss);
3022 ss<<"[end_of_params]\n";
3024 if(!fs::safeWriteToFile(fullpath, ss.str()))
3026 infostream<<"ERROR: ServerMap::saveMapMeta(): "
3027 <<"could not write "<<fullpath<<std::endl;
3028 throw FileNotGoodException("Cannot save chunk metadata");
3031 m_map_metadata_changed = false;
3034 void ServerMap::loadMapMeta()
3036 DSTACK(__FUNCTION_NAME);
3039 std::string fullpath = m_savedir + DIR_DELIM "map_meta.txt";
3041 if (fs::PathExists(fullpath)) {
3042 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3044 errorstream << "ServerMap::loadMapMeta(): "
3045 "could not open " << fullpath << std::endl;
3046 throw FileNotGoodException("Cannot open map metadata");
3049 if (!params.parseConfigLines(is, "[end_of_params]")) {
3050 throw SerializationError("ServerMap::loadMapMeta(): "
3051 "[end_of_params] not found!");
3055 m_emerge->loadParamsFromSettings(¶ms);
3057 verbosestream << "ServerMap::loadMapMeta(): seed="
3058 << m_emerge->params.seed << std::endl;
3061 void ServerMap::saveSectorMeta(ServerMapSector *sector)
3063 DSTACK(__FUNCTION_NAME);
3064 // Format used for writing
3065 u8 version = SER_FMT_VER_HIGHEST_WRITE;
3067 v2s16 pos = sector->getPos();
3068 std::string dir = getSectorDir(pos);
3071 std::string fullpath = dir + DIR_DELIM + "meta";
3072 std::ostringstream ss(std::ios_base::binary);
3074 sector->serialize(ss, version);
3076 if(!fs::safeWriteToFile(fullpath, ss.str()))
3077 throw FileNotGoodException("Cannot write sector metafile");
3079 sector->differs_from_disk = false;
3082 MapSector* ServerMap::loadSectorMeta(std::string sectordir, bool save_after_load)
3084 DSTACK(__FUNCTION_NAME);
3086 v2s16 p2d = getSectorPos(sectordir);
3088 ServerMapSector *sector = NULL;
3090 std::string fullpath = sectordir + DIR_DELIM + "meta";
3091 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3092 if(is.good() == false)
3094 // If the directory exists anyway, it probably is in some old
3095 // format. Just go ahead and create the sector.
3096 if(fs::PathExists(sectordir))
3098 /*infostream<<"ServerMap::loadSectorMeta(): Sector metafile "
3099 <<fullpath<<" doesn't exist but directory does."
3100 <<" Continuing with a sector with no metadata."
3102 sector = new ServerMapSector(this, p2d, m_gamedef);
3103 m_sectors[p2d] = sector;
3107 throw FileNotGoodException("Cannot open sector metafile");
3112 sector = ServerMapSector::deSerialize
3113 (is, this, p2d, m_sectors, m_gamedef);
3115 saveSectorMeta(sector);
3118 sector->differs_from_disk = false;
3123 bool ServerMap::loadSectorMeta(v2s16 p2d)
3125 DSTACK(__FUNCTION_NAME);
3127 // The directory layout we're going to load from.
3128 // 1 - original sectors/xxxxzzzz/
3129 // 2 - new sectors2/xxx/zzz/
3130 // If we load from anything but the latest structure, we will
3131 // immediately save to the new one, and remove the old.
3133 std::string sectordir1 = getSectorDir(p2d, 1);
3134 std::string sectordir;
3135 if(fs::PathExists(sectordir1))
3137 sectordir = sectordir1;
3142 sectordir = getSectorDir(p2d, 2);
3146 loadSectorMeta(sectordir, loadlayout != 2);
3148 catch(InvalidFilenameException &e)
3152 catch(FileNotGoodException &e)
3156 catch(std::exception &e)
3165 bool ServerMap::loadSectorFull(v2s16 p2d)
3167 DSTACK(__FUNCTION_NAME);
3169 MapSector *sector = NULL;
3171 // The directory layout we're going to load from.
3172 // 1 - original sectors/xxxxzzzz/
3173 // 2 - new sectors2/xxx/zzz/
3174 // If we load from anything but the latest structure, we will
3175 // immediately save to the new one, and remove the old.
3177 std::string sectordir1 = getSectorDir(p2d, 1);
3178 std::string sectordir;
3179 if(fs::PathExists(sectordir1))
3181 sectordir = sectordir1;
3186 sectordir = getSectorDir(p2d, 2);
3190 sector = loadSectorMeta(sectordir, loadlayout != 2);
3192 catch(InvalidFilenameException &e)
3196 catch(FileNotGoodException &e)
3200 catch(std::exception &e)
3208 std::vector<fs::DirListNode> list2 = fs::GetDirListing
3210 std::vector<fs::DirListNode>::iterator i2;
3211 for(i2=list2.begin(); i2!=list2.end(); i2++)
3217 loadBlock(sectordir, i2->name, sector, loadlayout != 2);
3219 catch(InvalidFilenameException &e)
3221 // This catches unknown crap in directory
3227 infostream<<"Sector converted to new layout - deleting "<<
3228 sectordir1<<std::endl;
3229 fs::RecursiveDelete(sectordir1);
3236 Database *ServerMap::createDatabase(const std::string &name, const std::string &savedir, Settings &conf)
3238 if (name == "sqlite3")
3239 return new Database_SQLite3(savedir);
3240 if (name == "dummy")
3241 return new Database_Dummy();
3243 else if (name == "leveldb")
3244 return new Database_LevelDB(savedir);
3247 else if (name == "redis")
3248 return new Database_Redis(conf);
3251 throw BaseException(std::string("Database backend ") + name + " not supported.");
3254 void ServerMap::beginSave()
3259 void ServerMap::endSave()
3264 bool ServerMap::saveBlock(MapBlock *block)
3266 return saveBlock(block, dbase);
3269 bool ServerMap::saveBlock(MapBlock *block, Database *db)
3271 v3s16 p3d = block->getPos();
3273 // Dummy blocks are not written
3274 if (block->isDummy()) {
3275 errorstream << "WARNING: saveBlock: Not writing dummy block "
3276 << PP(p3d) << std::endl;
3280 // Format used for writing
3281 u8 version = SER_FMT_VER_HIGHEST_WRITE;
3284 [0] u8 serialization version
3287 std::ostringstream o(std::ios_base::binary);
3288 o.write((char*) &version, 1);
3289 block->serialize(o, version, true);
3291 std::string data = o.str();
3292 bool ret = db->saveBlock(p3d, data);
3294 // We just wrote it to the disk so clear modified flag
3295 block->resetModified();
3300 void ServerMap::loadBlock(std::string sectordir, std::string blockfile,
3301 MapSector *sector, bool save_after_load)
3303 DSTACK(__FUNCTION_NAME);
3305 std::string fullpath = sectordir + DIR_DELIM + blockfile;
3308 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3309 if(is.good() == false)
3310 throw FileNotGoodException("Cannot open block file");
3312 v3s16 p3d = getBlockPos(sectordir, blockfile);
3313 v2s16 p2d(p3d.X, p3d.Z);
3315 assert(sector->getPos() == p2d);
3317 u8 version = SER_FMT_VER_INVALID;
3318 is.read((char*)&version, 1);
3321 throw SerializationError("ServerMap::loadBlock(): Failed"
3322 " to read MapBlock version");
3324 /*u32 block_size = MapBlock::serializedLength(version);
3325 SharedBuffer<u8> data(block_size);
3326 is.read((char*)*data, block_size);*/
3328 // This will always return a sector because we're the server
3329 //MapSector *sector = emergeSector(p2d);
3331 MapBlock *block = NULL;
3332 bool created_new = false;
3333 block = sector->getBlockNoCreateNoEx(p3d.Y);
3336 block = sector->createBlankBlockNoInsert(p3d.Y);
3341 block->deSerialize(is, version, true);
3343 // If it's a new block, insert it to the map
3345 sector->insertBlock(block);
3348 Save blocks loaded in old format in new format
3351 if(version < SER_FMT_VER_HIGHEST_WRITE || save_after_load)
3355 // Should be in database now, so delete the old file
3356 fs::RecursiveDelete(fullpath);
3359 // We just loaded it from the disk, so it's up-to-date.
3360 block->resetModified();
3363 catch(SerializationError &e)
3365 infostream<<"WARNING: Invalid block data on disk "
3366 <<"fullpath="<<fullpath
3367 <<" (SerializationError). "
3368 <<"what()="<<e.what()
3370 // Ignoring. A new one will be generated.
3373 // TODO: Backup file; name is in fullpath.
3377 void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load)
3379 DSTACK(__FUNCTION_NAME);
3382 std::istringstream is(*blob, std::ios_base::binary);
3384 u8 version = SER_FMT_VER_INVALID;
3385 is.read((char*)&version, 1);
3388 throw SerializationError("ServerMap::loadBlock(): Failed"
3389 " to read MapBlock version");
3391 /*u32 block_size = MapBlock::serializedLength(version);
3392 SharedBuffer<u8> data(block_size);
3393 is.read((char*)*data, block_size);*/
3395 // This will always return a sector because we're the server
3396 //MapSector *sector = emergeSector(p2d);
3398 MapBlock *block = NULL;
3399 bool created_new = false;
3400 block = sector->getBlockNoCreateNoEx(p3d.Y);
3403 block = sector->createBlankBlockNoInsert(p3d.Y);
3408 block->deSerialize(is, version, true);
3410 // If it's a new block, insert it to the map
3412 sector->insertBlock(block);
3415 Save blocks loaded in old format in new format
3418 //if(version < SER_FMT_VER_HIGHEST_READ || save_after_load)
3419 // Only save if asked to; no need to update version
3423 // We just loaded it from, so it's up-to-date.
3424 block->resetModified();
3427 catch(SerializationError &e)
3429 errorstream<<"Invalid block data in database"
3430 <<" ("<<p3d.X<<","<<p3d.Y<<","<<p3d.Z<<")"
3431 <<" (SerializationError): "<<e.what()<<std::endl;
3433 // TODO: Block should be marked as invalid in memory so that it is
3434 // not touched but the game can run
3436 if(g_settings->getBool("ignore_world_load_errors")){
3437 errorstream<<"Ignoring block load error. Duck and cover! "
3438 <<"(ignore_world_load_errors)"<<std::endl;
3440 throw SerializationError("Invalid block data in database");
3445 MapBlock* ServerMap::loadBlock(v3s16 blockpos)
3447 DSTACK(__FUNCTION_NAME);
3449 v2s16 p2d(blockpos.X, blockpos.Z);
3453 ret = dbase->loadBlock(blockpos);
3455 loadBlock(&ret, blockpos, createSector(p2d), false);
3456 return getBlockNoCreateNoEx(blockpos);
3458 // Not found in database, try the files
3460 // The directory layout we're going to load from.
3461 // 1 - original sectors/xxxxzzzz/
3462 // 2 - new sectors2/xxx/zzz/
3463 // If we load from anything but the latest structure, we will
3464 // immediately save to the new one, and remove the old.
3466 std::string sectordir1 = getSectorDir(p2d, 1);
3467 std::string sectordir;
3468 if(fs::PathExists(sectordir1))
3470 sectordir = sectordir1;
3475 sectordir = getSectorDir(p2d, 2);
3479 Make sure sector is loaded
3481 MapSector *sector = getSectorNoGenerateNoEx(p2d);
3485 sector = loadSectorMeta(sectordir, loadlayout != 2);
3487 catch(InvalidFilenameException &e)
3491 catch(FileNotGoodException &e)
3495 catch(std::exception &e)
3502 Make sure file exists
3505 std::string blockfilename = getBlockFilename(blockpos);
3506 if(fs::PathExists(sectordir + DIR_DELIM + blockfilename) == false)
3510 Load block and save it to the database
3512 loadBlock(sectordir, blockfilename, sector, true);
3513 return getBlockNoCreateNoEx(blockpos);
3516 bool ServerMap::deleteBlock(v3s16 blockpos)
3518 if (!dbase->deleteBlock(blockpos))
3521 MapBlock *block = getBlockNoCreateNoEx(blockpos);
3523 v2s16 p2d(blockpos.X, blockpos.Z);
3524 MapSector *sector = getSectorNoGenerateNoEx(p2d);
3527 sector->deleteBlock(block);
3533 void ServerMap::PrintInfo(std::ostream &out)
3538 MMVManip::MMVManip(Map *map):
3541 m_create_area(false),
3546 MMVManip::~MMVManip()
3550 void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
3551 bool load_if_inexistent)
3553 TimeTaker timer1("initialEmerge", &emerge_time);
3555 // Units of these are MapBlocks
3556 v3s16 p_min = blockpos_min;
3557 v3s16 p_max = blockpos_max;
3559 VoxelArea block_area_nodes
3560 (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
3562 u32 size_MB = block_area_nodes.getVolume()*4/1000000;
3565 infostream<<"initialEmerge: area: ";
3566 block_area_nodes.print(infostream);
3567 infostream<<" ("<<size_MB<<"MB)";
3568 infostream<<std::endl;
3571 addArea(block_area_nodes);
3573 for(s32 z=p_min.Z; z<=p_max.Z; z++)
3574 for(s32 y=p_min.Y; y<=p_max.Y; y++)
3575 for(s32 x=p_min.X; x<=p_max.X; x++)
3580 std::map<v3s16, u8>::iterator n;
3581 n = m_loaded_blocks.find(p);
3582 if(n != m_loaded_blocks.end())
3585 bool block_data_inexistent = false;
3588 TimeTaker timer1("emerge load", &emerge_load_time);
3590 block = m_map->getBlockNoCreate(p);
3591 if(block->isDummy())
3592 block_data_inexistent = true;
3594 block->copyTo(*this);
3596 catch(InvalidPositionException &e)
3598 block_data_inexistent = true;
3601 if(block_data_inexistent)
3604 if (load_if_inexistent) {
3605 ServerMap *svrmap = (ServerMap *)m_map;
3606 block = svrmap->emergeBlock(p, false);
3608 block = svrmap->createBlock(p);
3609 block->copyTo(*this);
3611 flags |= VMANIP_BLOCK_DATA_INEXIST;
3614 Mark area inexistent
3616 VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
3617 // Fill with VOXELFLAG_NO_DATA
3618 for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
3619 for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
3621 s32 i = m_area.index(a.MinEdge.X,y,z);
3622 memset(&m_flags[i], VOXELFLAG_NO_DATA, MAP_BLOCKSIZE);
3626 /*else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE)
3628 // Mark that block was loaded as blank
3629 flags |= VMANIP_BLOCK_CONTAINS_CIGNORE;
3632 m_loaded_blocks[p] = flags;
3638 void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
3639 bool overwrite_generated)
3641 if(m_area.getExtent() == v3s16(0,0,0))
3645 Copy data of all blocks
3647 for(std::map<v3s16, u8>::iterator
3648 i = m_loaded_blocks.begin();
3649 i != m_loaded_blocks.end(); ++i)
3652 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
3653 bool existed = !(i->second & VMANIP_BLOCK_DATA_INEXIST);
3654 if ((existed == false) || (block == NULL) ||
3655 (overwrite_generated == false && block->isGenerated() == true))
3658 block->copyFrom(*this);
3661 (*modified_blocks)[p] = block;