3 Copyright (C) 2010 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 General Public License as published by
7 the Free Software Foundation; either version 2 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 General Public License for more details.
15 You should have received a copy of the GNU 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.
22 #include "jmutexautolock.h"
33 Map::Map(std::ostream &dout):
35 m_camera_position(0,0,0),
36 m_camera_direction(0,0,1),
40 m_sector_mutex.Init();
41 m_camera_mutex.Init();
42 assert(m_sector_mutex.IsInitialized());
43 assert(m_camera_mutex.IsInitialized());
45 // Get this so that the player can stay on it at first
46 //getSector(v2s16(0,0));
54 /*updater.setRun(false);
55 while(updater.IsRunning())
61 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
62 for(; i.atEnd() == false; i++)
64 MapSector *sector = i.getNode()->getValue();
69 MapSector * Map::getSectorNoGenerate(v2s16 p)
71 JMutexAutoLock lock(m_sector_mutex);
73 if(m_sector_cache != NULL && p == m_sector_cache_p){
74 MapSector * sector = m_sector_cache;
75 // Reset inactivity timer
76 sector->usage_timer = 0.0;
80 core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
81 // If sector doesn't exist, throw an exception
84 throw InvalidPositionException();
87 MapSector *sector = n->getValue();
89 // Cache the last result
91 m_sector_cache = sector;
93 //MapSector * ref(sector);
95 // Reset inactivity timer
96 sector->usage_timer = 0.0;
100 MapBlock * Map::getBlockNoCreate(v3s16 p3d)
102 v2s16 p2d(p3d.X, p3d.Z);
103 MapSector * sector = getSectorNoGenerate(p2d);
105 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
110 MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
114 v2s16 p2d(p3d.X, p3d.Z);
115 MapSector * sector = getSectorNoGenerate(p2d);
116 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
119 catch(InvalidPositionException &e)
125 f32 Map::getGroundHeight(v2s16 p, bool generate)
128 v2s16 sectorpos = getNodeSectorPos(p);
129 MapSector * sref = getSectorNoGenerate(sectorpos);
130 v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
131 f32 y = sref->getGroundHeight(relpos);
134 catch(InvalidPositionException &e)
136 return GROUNDHEIGHT_NOTFOUND_SETVALUE;
140 void Map::setGroundHeight(v2s16 p, f32 y, bool generate)
142 /*m_dout<<DTIME<<"Map::setGroundHeight(("
144 <<"), "<<y<<")"<<std::endl;*/
145 v2s16 sectorpos = getNodeSectorPos(p);
146 MapSector * sref = getSectorNoGenerate(sectorpos);
147 v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
148 //sref->mutex.Lock();
149 sref->setGroundHeight(relpos, y);
150 //sref->mutex.Unlock();
153 bool Map::isNodeUnderground(v3s16 p)
155 v3s16 blockpos = getNodeBlockPos(p);
157 MapBlock * block = getBlockNoCreate(blockpos);
158 return block->getIsUnderground();
160 catch(InvalidPositionException &e)
167 Goes recursively through the neighbours of the node.
169 Alters only transparent nodes.
171 If the lighting of the neighbour is lower than the lighting of
172 the node was (before changing it to 0 at the step before), the
173 lighting of the neighbour is set to 0 and then the same stuff
174 repeats for the neighbour.
176 The ending nodes of the routine are stored in light_sources.
177 This is useful when a light is removed. In such case, this
178 routine can be called for the light node and then again for
179 light_sources to re-light the area without the removed light.
181 values of from_nodes are lighting values.
183 void Map::unspreadLight(enum LightBank bank,
184 core::map<v3s16, u8> & from_nodes,
185 core::map<v3s16, bool> & light_sources,
186 core::map<v3s16, MapBlock*> & modified_blocks)
189 v3s16(0,0,1), // back
191 v3s16(1,0,0), // right
192 v3s16(0,0,-1), // front
193 v3s16(0,-1,0), // bottom
194 v3s16(-1,0,0), // left
197 if(from_nodes.size() == 0)
200 u32 blockchangecount = 0;
202 core::map<v3s16, u8> unlighted_nodes;
203 core::map<v3s16, u8>::Iterator j;
204 j = from_nodes.getIterator();
207 Initialize block cache
210 MapBlock *block = NULL;
211 // Cache this a bit, too
212 bool block_checked_in_modified = false;
214 for(; j.atEnd() == false; j++)
216 v3s16 pos = j.getNode()->getKey();
217 v3s16 blockpos = getNodeBlockPos(pos);
219 // Only fetch a new block if the block position has changed
221 if(block == NULL || blockpos != blockpos_last){
222 block = getBlockNoCreate(blockpos);
223 blockpos_last = blockpos;
225 block_checked_in_modified = false;
229 catch(InvalidPositionException &e)
237 // Calculate relative position in block
238 v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
240 // Get node straight from the block
241 MapNode n = block->getNode(relpos);
243 u8 oldlight = j.getNode()->getValue();
245 // Loop through 6 neighbors
246 for(u16 i=0; i<6; i++)
248 // Get the position of the neighbor node
249 v3s16 n2pos = pos + dirs[i];
251 // Get the block where the node is located
252 v3s16 blockpos = getNodeBlockPos(n2pos);
256 // Only fetch a new block if the block position has changed
258 if(block == NULL || blockpos != blockpos_last){
259 block = getBlockNoCreate(blockpos);
260 blockpos_last = blockpos;
262 block_checked_in_modified = false;
266 catch(InvalidPositionException &e)
271 // Calculate relative position in block
272 v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
273 // Get node straight from the block
274 MapNode n2 = block->getNode(relpos);
276 bool changed = false;
278 //TODO: Optimize output by optimizing light_sources?
281 If the neighbor is dimmer than what was specified
282 as oldlight (the light of the previous node)
284 if(n2.getLight(bank) < oldlight)
287 And the neighbor is transparent and it has some light
289 if(n2.light_propagates() && n2.getLight(bank) != 0)
292 Set light to 0 and add to queue
295 u8 current_light = n2.getLight(bank);
296 n2.setLight(bank, 0);
297 block->setNode(relpos, n2);
299 unlighted_nodes.insert(n2pos, current_light);
303 Remove from light_sources if it is there
304 NOTE: This doesn't happen nearly at all
306 /*if(light_sources.find(n2pos))
308 std::cout<<"Removed from light_sources"<<std::endl;
309 light_sources.remove(n2pos);
314 if(light_sources.find(n2pos) != NULL)
315 light_sources.remove(n2pos);*/
318 light_sources.insert(n2pos, true);
321 // Add to modified_blocks
322 if(changed == true && block_checked_in_modified == false)
324 // If the block is not found in modified_blocks, add.
325 if(modified_blocks.find(blockpos) == NULL)
327 modified_blocks.insert(blockpos, block);
329 block_checked_in_modified = true;
332 catch(InvalidPositionException &e)
339 /*dstream<<"unspreadLight(): Changed block "
340 <<blockchangecount<<" times"
341 <<" for "<<from_nodes.size()<<" nodes"
344 if(unlighted_nodes.size() > 0)
345 unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
349 A single-node wrapper of the above
351 void Map::unLightNeighbors(enum LightBank bank,
352 v3s16 pos, u8 lightwas,
353 core::map<v3s16, bool> & light_sources,
354 core::map<v3s16, MapBlock*> & modified_blocks)
356 core::map<v3s16, u8> from_nodes;
357 from_nodes.insert(pos, lightwas);
359 unspreadLight(bank, from_nodes, light_sources, modified_blocks);
363 Lights neighbors of from_nodes, collects all them and then
366 void Map::spreadLight(enum LightBank bank,
367 core::map<v3s16, bool> & from_nodes,
368 core::map<v3s16, MapBlock*> & modified_blocks)
370 const v3s16 dirs[6] = {
371 v3s16(0,0,1), // back
373 v3s16(1,0,0), // right
374 v3s16(0,0,-1), // front
375 v3s16(0,-1,0), // bottom
376 v3s16(-1,0,0), // left
379 if(from_nodes.size() == 0)
382 u32 blockchangecount = 0;
384 core::map<v3s16, bool> lighted_nodes;
385 core::map<v3s16, bool>::Iterator j;
386 j = from_nodes.getIterator();
389 Initialize block cache
392 MapBlock *block = NULL;
393 // Cache this a bit, too
394 bool block_checked_in_modified = false;
396 for(; j.atEnd() == false; j++)
397 //for(; j != from_nodes.end(); j++)
399 v3s16 pos = j.getNode()->getKey();
401 //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
402 v3s16 blockpos = getNodeBlockPos(pos);
404 // Only fetch a new block if the block position has changed
406 if(block == NULL || blockpos != blockpos_last){
407 block = getBlockNoCreate(blockpos);
408 blockpos_last = blockpos;
410 block_checked_in_modified = false;
414 catch(InvalidPositionException &e)
422 // Calculate relative position in block
423 v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
425 // Get node straight from the block
426 MapNode n = block->getNode(relpos);
428 u8 oldlight = n.getLight(bank);
429 u8 newlight = diminish_light(oldlight);
431 // Loop through 6 neighbors
432 for(u16 i=0; i<6; i++){
433 // Get the position of the neighbor node
434 v3s16 n2pos = pos + dirs[i];
436 // Get the block where the node is located
437 v3s16 blockpos = getNodeBlockPos(n2pos);
441 // Only fetch a new block if the block position has changed
443 if(block == NULL || blockpos != blockpos_last){
444 block = getBlockNoCreate(blockpos);
445 blockpos_last = blockpos;
447 block_checked_in_modified = false;
451 catch(InvalidPositionException &e)
456 // Calculate relative position in block
457 v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
458 // Get node straight from the block
459 MapNode n2 = block->getNode(relpos);
461 bool changed = false;
463 If the neighbor is brighter than the current node,
464 add to list (it will light up this node on its turn)
466 if(n2.getLight(bank) > undiminish_light(oldlight))
468 lighted_nodes.insert(n2pos, true);
469 //lighted_nodes.push_back(n2pos);
473 If the neighbor is dimmer than how much light this node
474 would spread on it, add to list
476 if(n2.getLight(bank) < newlight)
478 if(n2.light_propagates())
480 n2.setLight(bank, newlight);
481 block->setNode(relpos, n2);
482 lighted_nodes.insert(n2pos, true);
483 //lighted_nodes.push_back(n2pos);
488 // Add to modified_blocks
489 if(changed == true && block_checked_in_modified == false)
491 // If the block is not found in modified_blocks, add.
492 if(modified_blocks.find(blockpos) == NULL)
494 modified_blocks.insert(blockpos, block);
496 block_checked_in_modified = true;
499 catch(InvalidPositionException &e)
506 /*dstream<<"spreadLight(): Changed block "
507 <<blockchangecount<<" times"
508 <<" for "<<from_nodes.size()<<" nodes"
511 if(lighted_nodes.size() > 0)
512 spreadLight(bank, lighted_nodes, modified_blocks);
516 A single-node source variation of the above.
518 void Map::lightNeighbors(enum LightBank bank,
520 core::map<v3s16, MapBlock*> & modified_blocks)
522 core::map<v3s16, bool> from_nodes;
523 from_nodes.insert(pos, true);
524 spreadLight(bank, from_nodes, modified_blocks);
527 v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
530 v3s16(0,0,1), // back
532 v3s16(1,0,0), // right
533 v3s16(0,0,-1), // front
534 v3s16(0,-1,0), // bottom
535 v3s16(-1,0,0), // left
538 u8 brightest_light = 0;
539 v3s16 brightest_pos(0,0,0);
540 bool found_something = false;
542 // Loop through 6 neighbors
543 for(u16 i=0; i<6; i++){
544 // Get the position of the neighbor node
545 v3s16 n2pos = p + dirs[i];
550 catch(InvalidPositionException &e)
554 if(n2.getLight(bank) > brightest_light || found_something == false){
555 brightest_light = n2.getLight(bank);
556 brightest_pos = n2pos;
557 found_something = true;
561 if(found_something == false)
562 throw InvalidPositionException();
564 return brightest_pos;
568 Propagates sunlight down from a node.
569 Starting point gets sunlight.
571 Returns the lowest y value of where the sunlight went.
573 Mud is turned into grass in where the sunlight stops.
575 s16 Map::propagateSunlight(v3s16 start,
576 core::map<v3s16, MapBlock*> & modified_blocks)
581 v3s16 pos(start.X, y, start.Z);
583 v3s16 blockpos = getNodeBlockPos(pos);
586 block = getBlockNoCreate(blockpos);
588 catch(InvalidPositionException &e)
593 v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
594 MapNode n = block->getNode(relpos);
596 if(n.sunlight_propagates())
598 n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
599 block->setNode(relpos, n);
601 modified_blocks.insert(blockpos, block);
605 // Turn mud into grass
606 if(n.d == CONTENT_MUD)
609 block->setNode(relpos, n);
610 modified_blocks.insert(blockpos, block);
613 // Sunlight goes no further
620 void Map::updateLighting(enum LightBank bank,
621 core::map<v3s16, MapBlock*> & a_blocks,
622 core::map<v3s16, MapBlock*> & modified_blocks)
624 /*m_dout<<DTIME<<"Map::updateLighting(): "
625 <<a_blocks.getSize()<<" blocks... ";*/
629 u32 count_was = modified_blocks.size();
631 core::map<v3s16, bool> light_sources;
633 core::map<v3s16, u8> unlight_from;
635 core::map<v3s16, MapBlock*>::Iterator i;
636 i = a_blocks.getIterator();
637 for(; i.atEnd() == false; i++)
639 MapBlock *block = i.getNode()->getValue();
643 // Don't bother with dummy blocks.
647 v3s16 pos = block->getPos();
648 modified_blocks.insert(pos, block);
651 Clear all light from block
653 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
654 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
655 for(s16 y=0; y<MAP_BLOCKSIZE; y++)
660 MapNode n = block->getNode(v3s16(x,y,z));
661 u8 oldlight = n.getLight(bank);
663 block->setNode(v3s16(x,y,z), n);
665 // Collect borders for unlighting
666 if(x==0 || x == MAP_BLOCKSIZE-1
667 || y==0 || y == MAP_BLOCKSIZE-1
668 || z==0 || z == MAP_BLOCKSIZE-1)
670 v3s16 p_map = p + v3s16(
673 MAP_BLOCKSIZE*pos.Z);
674 unlight_from.insert(p_map, oldlight);
677 catch(InvalidPositionException &e)
680 This would happen when dealing with a
684 dstream<<"updateLighting(): InvalidPositionException"
689 if(bank == LIGHTBANK_DAY)
691 bool bottom_valid = block->propagateSunlight(light_sources);
693 // If bottom is valid, we're done.
697 else if(bank == LIGHTBANK_NIGHT)
706 /*dstream<<"Bottom for sunlight-propagated block ("
707 <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
710 // Else get the block below and loop to it
714 block = getBlockNoCreate(pos);
716 catch(InvalidPositionException &e)
725 //TimeTaker timer("unspreadLight");
726 unspreadLight(bank, unlight_from, light_sources, modified_blocks);
731 u32 diff = modified_blocks.size() - count_was;
732 count_was = modified_blocks.size();
733 dstream<<"unspreadLight modified "<<diff<<std::endl;
736 // TODO: Spread light from propagated sunlight?
737 // Yes, add it to light_sources... somehow.
738 // It has to be added at somewhere above, in the loop.
740 // NOTE: This actually works fine without doing so
741 // - Find out why it works
744 //TimeTaker timer("spreadLight");
745 spreadLight(bank, light_sources, modified_blocks);
750 u32 diff = modified_blocks.size() - count_was;
751 count_was = modified_blocks.size();
752 dstream<<"spreadLight modified "<<diff<<std::endl;
755 //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
758 void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
759 core::map<v3s16, MapBlock*> & modified_blocks)
761 updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
762 updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
765 Update information about whether day and night light differ
767 for(core::map<v3s16, MapBlock*>::Iterator
768 i = modified_blocks.getIterator();
769 i.atEnd() == false; i++)
771 MapBlock *block = i.getNode()->getValue();
772 block->updateDayNightDiff();
777 This is called after changing a node from transparent to opaque.
778 The lighting value of the node should be left as-is after changing
779 other values. This sets the lighting value to 0.
781 /*void Map::nodeAddedUpdate(v3s16 p, u8 lightwas,
782 core::map<v3s16, MapBlock*> &modified_blocks)*/
783 void Map::addNodeAndUpdate(v3s16 p, MapNode n,
784 core::map<v3s16, MapBlock*> &modified_blocks)
787 m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
788 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
791 From this node to nodes underneath:
792 If lighting is sunlight (1.0), unlight neighbours and
797 v3s16 toppos = p + v3s16(0,1,0);
798 v3s16 bottompos = p + v3s16(0,-1,0);
800 bool node_under_sunlight = true;
801 core::map<v3s16, bool> light_sources;
804 If there is a node at top and it doesn't have sunlight,
805 there has not been any sunlight going down.
807 Otherwise there probably is.
810 MapNode topnode = getNode(toppos);
812 if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
813 node_under_sunlight = false;
815 catch(InvalidPositionException &e)
819 if(n.d != CONTENT_TORCH)
822 If there is grass below, change it to mud
825 MapNode bottomnode = getNode(bottompos);
827 if(bottomnode.d == CONTENT_GRASS
828 || bottomnode.d == CONTENT_GRASS_FOOTSTEPS)
830 bottomnode.d = CONTENT_MUD;
831 setNode(bottompos, bottomnode);
834 catch(InvalidPositionException &e)
839 enum LightBank banks[] =
844 for(s32 i=0; i<2; i++)
846 enum LightBank bank = banks[i];
848 u8 lightwas = getNode(p).getLight(bank);
850 // Add the block of the added node to modified_blocks
851 v3s16 blockpos = getNodeBlockPos(p);
852 MapBlock * block = getBlockNoCreate(blockpos);
853 assert(block != NULL);
854 modified_blocks.insert(blockpos, block);
856 if(isValidPosition(p) == false)
859 // Unlight neighbours of node.
860 // This means setting light of all consequent dimmer nodes
862 // This also collects the nodes at the border which will spread
863 // light again into this.
864 unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
872 If node is under sunlight, take all sunlighted nodes under
873 it and clear light from them and from where the light has
875 TODO: This could be optimized by mass-unlighting instead
878 if(node_under_sunlight)
882 //m_dout<<DTIME<<"y="<<y<<std::endl;
883 v3s16 n2pos(p.X, y, p.Z);
889 catch(InvalidPositionException &e)
894 if(n2.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
896 //m_dout<<DTIME<<"doing"<<std::endl;
897 unLightNeighbors(LIGHTBANK_DAY,
898 n2pos, n2.getLight(LIGHTBANK_DAY),
899 light_sources, modified_blocks);
900 n2.setLight(LIGHTBANK_DAY, 0);
908 for(s32 i=0; i<2; i++)
910 enum LightBank bank = banks[i];
913 Spread light from all nodes that might be capable of doing so
914 TODO: Convert to spreadLight
916 spreadLight(bank, light_sources, modified_blocks);
920 Update information about whether day and night light differ
922 for(core::map<v3s16, MapBlock*>::Iterator
923 i = modified_blocks.getIterator();
924 i.atEnd() == false; i++)
926 MapBlock *block = i.getNode()->getValue();
927 block->updateDayNightDiff();
933 void Map::removeNodeAndUpdate(v3s16 p,
934 core::map<v3s16, MapBlock*> &modified_blocks)
937 m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
938 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
940 bool node_under_sunlight = true;
942 v3s16 toppos = p + v3s16(0,1,0);
944 // Node will be replaced with this
945 u8 replace_material = CONTENT_AIR;
948 If there is a node at top and it doesn't have sunlight,
949 there will be no sunlight going down.
952 MapNode topnode = getNode(toppos);
954 if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
955 node_under_sunlight = false;
957 catch(InvalidPositionException &e)
961 core::map<v3s16, bool> light_sources;
963 enum LightBank banks[] =
968 for(s32 i=0; i<2; i++)
970 enum LightBank bank = banks[i];
973 Unlight neighbors (in case the node is a light source)
975 unLightNeighbors(bank, p,
976 getNode(p).getLight(bank),
977 light_sources, modified_blocks);
982 This also clears the lighting.
986 n.d = replace_material;
989 for(s32 i=0; i<2; i++)
991 enum LightBank bank = banks[i];
996 spreadLight(bank, light_sources, modified_blocks);
999 // Add the block of the removed node to modified_blocks
1000 v3s16 blockpos = getNodeBlockPos(p);
1001 MapBlock * block = getBlockNoCreate(blockpos);
1002 assert(block != NULL);
1003 modified_blocks.insert(blockpos, block);
1006 If the removed node was under sunlight, propagate the
1007 sunlight down from it and then light all neighbors
1008 of the propagated blocks.
1010 if(node_under_sunlight)
1012 s16 ybottom = propagateSunlight(p, modified_blocks);
1013 /*m_dout<<DTIME<<"Node was under sunlight. "
1014 "Propagating sunlight";
1015 m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
1017 for(; y >= ybottom; y--)
1019 v3s16 p2(p.X, y, p.Z);
1020 /*m_dout<<DTIME<<"lighting neighbors of node ("
1021 <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
1023 lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
1028 // Set the lighting of this node to 0
1029 // TODO: Is this needed? Lighting is cleared up there already.
1031 MapNode n = getNode(p);
1032 n.setLight(LIGHTBANK_DAY, 0);
1035 catch(InvalidPositionException &e)
1041 for(s32 i=0; i<2; i++)
1043 enum LightBank bank = banks[i];
1045 // Get the brightest neighbour node and propagate light from it
1046 v3s16 n2p = getBrightestNeighbour(bank, p);
1048 MapNode n2 = getNode(n2p);
1049 lightNeighbors(bank, n2p, modified_blocks);
1051 catch(InvalidPositionException &e)
1057 Update information about whether day and night light differ
1059 for(core::map<v3s16, MapBlock*>::Iterator
1060 i = modified_blocks.getIterator();
1061 i.atEnd() == false; i++)
1063 MapBlock *block = i.getNode()->getValue();
1064 block->updateDayNightDiff();
1069 void Map::expireMeshes(bool only_daynight_diffed)
1071 TimeTaker timer("expireMeshes()");
1073 core::map<v2s16, MapSector*>::Iterator si;
1074 si = m_sectors.getIterator();
1075 for(; si.atEnd() == false; si++)
1077 MapSector *sector = si.getNode()->getValue();
1079 core::list< MapBlock * > sectorblocks;
1080 sector->getBlocks(sectorblocks);
1082 core::list< MapBlock * >::Iterator i;
1083 for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
1085 MapBlock *block = *i;
1087 if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false)
1093 JMutexAutoLock lock(block->mesh_mutex);
1094 if(block->mesh != NULL)
1096 /*block->mesh->drop();
1097 block->mesh = NULL;*/
1098 block->setMeshExpired(true);
1105 void Map::updateMeshes(v3s16 blockpos, u32 daynight_ratio)
1107 assert(mapType() == MAPTYPE_CLIENT);
1110 v3s16 p = blockpos + v3s16(0,0,0);
1111 MapBlock *b = getBlockNoCreate(p);
1112 b->updateMesh(daynight_ratio);
1114 catch(InvalidPositionException &e){}
1117 v3s16 p = blockpos + v3s16(-1,0,0);
1118 MapBlock *b = getBlockNoCreate(p);
1119 b->updateMesh(daynight_ratio);
1121 catch(InvalidPositionException &e){}
1123 v3s16 p = blockpos + v3s16(0,-1,0);
1124 MapBlock *b = getBlockNoCreate(p);
1125 b->updateMesh(daynight_ratio);
1127 catch(InvalidPositionException &e){}
1129 v3s16 p = blockpos + v3s16(0,0,-1);
1130 MapBlock *b = getBlockNoCreate(p);
1131 b->updateMesh(daynight_ratio);
1133 catch(InvalidPositionException &e){}
1136 v3s16 p = blockpos + v3s16(1,0,0);
1137 MapBlock *b = getBlockNoCreate(p);
1138 b->updateMesh(daynight_ratio);
1140 catch(InvalidPositionException &e){}
1142 v3s16 p = blockpos + v3s16(0,1,0);
1143 MapBlock *b = getBlockNoCreate(p);
1144 b->updateMesh(daynight_ratio);
1146 catch(InvalidPositionException &e){}
1148 v3s16 p = blockpos + v3s16(0,0,1);
1149 MapBlock *b = getBlockNoCreate(p);
1150 b->updateMesh(daynight_ratio);
1152 catch(InvalidPositionException &e){}*/
1157 bool Map::dayNightDiffed(v3s16 blockpos)
1160 v3s16 p = blockpos + v3s16(0,0,0);
1161 MapBlock *b = getBlockNoCreate(p);
1162 if(b->dayNightDiffed())
1165 catch(InvalidPositionException &e){}
1168 v3s16 p = blockpos + v3s16(-1,0,0);
1169 MapBlock *b = getBlockNoCreate(p);
1170 if(b->dayNightDiffed())
1173 catch(InvalidPositionException &e){}
1175 v3s16 p = blockpos + v3s16(0,-1,0);
1176 MapBlock *b = getBlockNoCreate(p);
1177 if(b->dayNightDiffed())
1180 catch(InvalidPositionException &e){}
1182 v3s16 p = blockpos + v3s16(0,0,-1);
1183 MapBlock *b = getBlockNoCreate(p);
1184 if(b->dayNightDiffed())
1187 catch(InvalidPositionException &e){}
1190 v3s16 p = blockpos + v3s16(1,0,0);
1191 MapBlock *b = getBlockNoCreate(p);
1192 if(b->dayNightDiffed())
1195 catch(InvalidPositionException &e){}
1197 v3s16 p = blockpos + v3s16(0,1,0);
1198 MapBlock *b = getBlockNoCreate(p);
1199 if(b->dayNightDiffed())
1202 catch(InvalidPositionException &e){}
1204 v3s16 p = blockpos + v3s16(0,0,1);
1205 MapBlock *b = getBlockNoCreate(p);
1206 if(b->dayNightDiffed())
1209 catch(InvalidPositionException &e){}
1215 Updates usage timers
1217 void Map::timerUpdate(float dtime)
1219 JMutexAutoLock lock(m_sector_mutex);
1221 core::map<v2s16, MapSector*>::Iterator si;
1223 si = m_sectors.getIterator();
1224 for(; si.atEnd() == false; si++)
1226 MapSector *sector = si.getNode()->getValue();
1227 sector->usage_timer += dtime;
1231 void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
1234 Wait for caches to be removed before continuing.
1236 This disables the existence of caches while locked
1238 //SharedPtr<JMutexAutoLock> cachelock(m_blockcachelock.waitCaches());
1240 core::list<v2s16>::Iterator j;
1241 for(j=list.begin(); j!=list.end(); j++)
1243 MapSector *sector = m_sectors[*j];
1246 sector->deleteBlocks();
1251 If sector is in sector cache, remove it from there
1253 if(m_sector_cache == sector)
1255 m_sector_cache = NULL;
1258 Remove from map and delete
1260 m_sectors.remove(*j);
1266 u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
1267 core::list<v3s16> *deleted_blocks)
1269 JMutexAutoLock lock(m_sector_mutex);
1271 core::list<v2s16> sector_deletion_queue;
1272 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
1273 for(; i.atEnd() == false; i++)
1275 MapSector *sector = i.getNode()->getValue();
1277 Delete sector from memory if it hasn't been used in a long time
1279 if(sector->usage_timer > timeout)
1281 sector_deletion_queue.push_back(i.getNode()->getKey());
1283 if(deleted_blocks != NULL)
1285 // Collect positions of blocks of sector
1286 MapSector *sector = i.getNode()->getValue();
1287 core::list<MapBlock*> blocks;
1288 sector->getBlocks(blocks);
1289 for(core::list<MapBlock*>::Iterator i = blocks.begin();
1290 i != blocks.end(); i++)
1292 deleted_blocks->push_back((*i)->getPos());
1297 deleteSectors(sector_deletion_queue, only_blocks);
1298 return sector_deletion_queue.getSize();
1301 void Map::PrintInfo(std::ostream &out)
1310 ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
1315 Experimental and debug stuff
1319 dstream<<"Generating map point attribute lists"<<std::endl;
1321 PointAttributeList *list_baseheight = m_padb.getList("hm_baseheight");
1322 PointAttributeList *list_randmax = m_padb.getList("hm_randmax");
1323 PointAttributeList *list_randfactor = m_padb.getList("hm_randfactor");
1324 PointAttributeList *list_plants_amount = m_padb.getList("plants_amount");
1325 PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
1328 NOTE: BEWARE: Too big amount of these will make map generation
1329 slow. Especially those that are read by every block emerge.
1332 for(u32 i=0; i<15000; i++)
1334 /*u32 lim = MAP_GENERATION_LIMIT;
1338 u32 lim = 1000 + MAP_GENERATION_LIMIT * i / 15000;
1341 -lim + myrand()%(lim*2),
1343 -lim + myrand()%(lim*2)
1345 /*float plants_amount = (float)(myrand()%1050) / 1000.0;
1346 plants_amount = pow(plants_amount, 5);
1347 list_plants_amount->addPoint(p, Attribute(plants_amount));*/
1349 float plants_amount = 0;
1352 plants_amount = 1.5;
1354 else if(myrand()%4 == 0)
1356 plants_amount = 0.5;
1358 else if(myrand()%2 == 0)
1360 plants_amount = 0.03;
1364 plants_amount = 0.0;
1368 list_plants_amount->addPoint(p, Attribute(plants_amount));
1371 for(u32 i=0; i<1000; i++)
1373 /*u32 lim = MAP_GENERATION_LIMIT;
1377 u32 lim = 500 + MAP_GENERATION_LIMIT * i / 1000;
1380 -lim + myrand()%(lim*2),
1382 -lim + myrand()%(lim*2)
1385 float caves_amount = 0;
1390 else if(myrand()%3 == 0)
1396 caves_amount = 0.05;
1399 list_caves_amount->addPoint(p, Attribute(caves_amount));
1402 for(u32 i=0; i<5000; i++)
1404 /*u32 lim = MAP_GENERATION_LIMIT;
1408 u32 lim = 1000 + MAP_GENERATION_LIMIT * i / 5000;
1411 -lim + (myrand()%(lim*2)),
1413 -lim + (myrand()%(lim*2))
1416 /*s32 bh_i = (myrand()%200) - 50;
1417 float baseheight = (float)bh_i;
1421 float randmax = (float)(myrand()%(int)(10.*pow(m, 1./e)))/10.;
1422 randmax = pow(randmax, e);
1424 //float randmax = (float)(myrand()%60);
1425 float randfactor = (float)(myrand()%450) / 1000.0 + 0.4;*/
1427 float baseheight = 0;
1429 float randfactor = 0;
1437 else if(myrand()%6 == 0)
1443 else if(myrand()%4 == 0)
1449 else if(myrand()%3 == 0)
1462 list_baseheight->addPoint(p, Attribute(baseheight));
1463 list_randmax->addPoint(p, Attribute(randmax));
1464 list_randfactor->addPoint(p, Attribute(randfactor));
1467 /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(5));
1468 list_randmax->addPoint(v3s16(0,0,0), Attribute(20));
1469 list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.6));*/
1472 /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(0));
1473 list_randmax->addPoint(v3s16(0,0,0), Attribute(10));
1474 list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.65));*/
1478 Try to load map; if not found, create a new one.
1481 m_savedir = savedir;
1482 m_map_saving_enabled = false;
1486 // If directory exists, check contents and load if possible
1487 if(fs::PathExists(m_savedir))
1489 // If directory is empty, it is safe to save into it.
1490 if(fs::GetDirListing(m_savedir).size() == 0)
1492 dstream<<DTIME<<"Server: Empty save directory is valid."
1494 m_map_saving_enabled = true;
1498 // Load master heightmap
1499 loadMasterHeightmap();
1501 // Load sector (0,0) and throw and exception on fail
1502 if(loadSectorFull(v2s16(0,0)) == false)
1503 throw LoadError("Failed to load sector (0,0)");
1505 dstream<<DTIME<<"Server: Successfully loaded master "
1506 "heightmap and sector (0,0) from "<<savedir<<
1507 ", assuming valid save directory."
1510 m_map_saving_enabled = true;
1511 // Map loaded, not creating new one
1515 // If directory doesn't exist, it is safe to save to it
1517 m_map_saving_enabled = true;
1520 catch(std::exception &e)
1522 dstream<<DTIME<<"Server: Failed to load map from "<<savedir
1523 <<", exception: "<<e.what()<<std::endl;
1524 dstream<<DTIME<<"Please remove the map or fix it."<<std::endl;
1525 dstream<<DTIME<<"WARNING: Map saving will be disabled."<<std::endl;
1528 dstream<<DTIME<<"Initializing new map."<<std::endl;
1530 // Create master heightmap
1531 /*ValueGenerator *maxgen =
1532 ValueGenerator::deSerialize(hmp.randmax);
1533 ValueGenerator *factorgen =
1534 ValueGenerator::deSerialize(hmp.randfactor);
1535 ValueGenerator *basegen =
1536 ValueGenerator::deSerialize(hmp.base);
1537 m_heightmap = new UnlimitedHeightmap
1538 (hmp.blocksize, maxgen, factorgen, basegen, &m_padb);*/
1540 /*m_heightmap = new UnlimitedHeightmap
1541 (hmp.blocksize, &m_padb);*/
1543 m_heightmap = new UnlimitedHeightmap
1546 // Set map parameters
1549 // Create zero sector
1550 emergeSector(v2s16(0,0));
1552 // Initially write whole map
1556 ServerMap::~ServerMap()
1560 if(m_map_saving_enabled)
1563 // Save only changed parts
1565 dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
1569 dstream<<DTIME<<"Server: map not saved"<<std::endl;
1572 catch(std::exception &e)
1574 dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
1575 <<", exception: "<<e.what()<<std::endl;
1578 if(m_heightmap != NULL)
1582 MapSector * ServerMap::emergeSector(v2s16 p2d)
1584 DSTACK("%s: p2d=(%d,%d)",
1587 // Check that it doesn't exist already
1589 return getSectorNoGenerate(p2d);
1591 catch(InvalidPositionException &e)
1596 Try to load the sector from disk.
1598 if(loadSectorFull(p2d) == true)
1600 return getSectorNoGenerate(p2d);
1604 If there is no master heightmap, throw.
1606 if(m_heightmap == NULL)
1608 throw InvalidPositionException("emergeSector(): no heightmap");
1612 Do not generate over-limit
1614 if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
1615 || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
1616 || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
1617 || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
1618 throw InvalidPositionException("emergeSector(): pos. over limit");
1621 Generate sector and heightmaps
1624 // Number of heightmaps in sector in each direction
1625 u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
1627 // Heightmap side width
1628 s16 hm_d = MAP_BLOCKSIZE / hm_split;
1630 ServerMapSector *sector = new ServerMapSector(this, p2d, hm_split);
1632 // Sector position on map in nodes
1633 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
1635 /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
1636 " heightmaps and objects"<<std::endl;*/
1639 Calculate some information about local properties
1642 v2s16 mhm_p = p2d * hm_split;
1644 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
1645 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
1646 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
1647 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
1650 float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0;
1651 float avgslope = 0.0;
1652 avgslope += fabs(avgheight - corners[0]);
1653 avgslope += fabs(avgheight - corners[1]);
1654 avgslope += fabs(avgheight - corners[2]);
1655 avgslope += fabs(avgheight - corners[3]);
1657 avgslope /= MAP_BLOCKSIZE;
1658 //dstream<<"avgslope="<<avgslope<<std::endl;
1660 float pitness = 0.0;
1662 a = m_heightmap->getSlope(p2d+v2s16(0,0));
1665 a = m_heightmap->getSlope(p2d+v2s16(0,1));
1668 a = m_heightmap->getSlope(p2d+v2s16(1,1));
1671 a = m_heightmap->getSlope(p2d+v2s16(1,0));
1675 pitness /= MAP_BLOCKSIZE;
1676 //dstream<<"pitness="<<pitness<<std::endl;
1679 Get local attributes
1682 //dstream<<"emergeSector(): Reading point attribute lists"<<std::endl;
1684 // Get plant amount from attributes
1685 PointAttributeList *palist = m_padb.getList("plants_amount");
1687 /*float local_plants_amount =
1688 palist->getNearAttr(nodepos2d).getFloat();*/
1689 float local_plants_amount =
1690 palist->getInterpolatedFloat(nodepos2d);
1692 //dstream<<"emergeSector(): done."<<std::endl;
1695 Generate sector heightmap
1698 // Loop through sub-heightmaps
1699 for(s16 y=0; y<hm_split; y++)
1700 for(s16 x=0; x<hm_split; x++)
1702 v2s16 p_in_sector = v2s16(x,y);
1703 v2s16 mhm_p = p2d * hm_split + p_in_sector;
1705 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
1706 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
1707 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
1708 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
1711 /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
1712 <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
1715 FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
1717 sector->setHeightmap(p_in_sector, hm);
1719 //TODO: Make these values configurable
1720 //hm->generateContinued(0.0, 0.0, corners);
1721 //hm->generateContinued(0.25, 0.2, corners);
1722 //hm->generateContinued(0.5, 0.2, corners);
1723 //hm->generateContinued(1.0, 0.2, corners);
1724 //hm->generateContinued(2.0, 0.2, corners);
1725 //hm->generateContinued(2.0 * avgslope, 0.5, corners);
1726 hm->generateContinued(avgslope * MAP_BLOCKSIZE/8, 0.5, corners);
1735 core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
1736 sector->setObjects(objects);
1738 float area = MAP_BLOCKSIZE * MAP_BLOCKSIZE;
1741 Plant some trees if there is not much slope
1744 // Avgslope is the derivative of a hill
1745 //float t = avgslope * avgslope;
1747 float a = area/16 * m_params.plants_amount * local_plants_amount;
1749 //float something = 0.17*0.17;
1750 float something = 0.3;
1752 tree_max = a / (t/something);
1756 u32 count = (myrand()%(tree_max+1));
1757 //u32 count = tree_max;
1758 for(u32 i=0; i<count; i++)
1760 s16 x = (myrand()%(MAP_BLOCKSIZE-2))+1;
1761 s16 z = (myrand()%(MAP_BLOCKSIZE-2))+1;
1762 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
1765 objects->insert(v3s16(x, y, z),
1766 SECTOR_OBJECT_TREE_1);
1770 Plant some bushes if sector is pit-like
1773 // Pitness usually goes at around -0.5...0.5
1775 u32 a = area/16 * 3.0 * m_params.plants_amount * local_plants_amount;
1777 bush_max = (pitness*a*4);
1780 u32 count = (myrand()%(bush_max+1));
1781 for(u32 i=0; i<count; i++)
1783 s16 x = myrand()%(MAP_BLOCKSIZE-0)+0;
1784 s16 z = myrand()%(MAP_BLOCKSIZE-0)+0;
1785 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
1788 objects->insert(v3s16(x, y, z),
1789 SECTOR_OBJECT_BUSH_1);
1793 Add ravine (randomly)
1795 if(m_params.ravines_amount > 0.001)
1797 if(myrand()%(s32)(200.0 / m_params.ravines_amount) == 0)
1800 s16 x = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
1801 s16 z = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
1804 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
1805 objects->insert(v3s16(x, y, z),
1806 SECTOR_OBJECT_RAVINE);
1813 JMutexAutoLock lock(m_sector_mutex);
1814 m_sectors.insert(p2d, sector);
1819 MapBlock * ServerMap::emergeBlock(
1821 bool only_from_disk,
1822 core::map<v3s16, MapBlock*> &changed_blocks,
1823 core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
1826 DSTACK("%s: p=(%d,%d,%d), only_from_disk=%d",
1828 p.X, p.Y, p.Z, only_from_disk);
1830 /*dstream<<"ServerMap::emergeBlock(): "
1831 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1832 <<", only_from_disk="<<only_from_disk<<std::endl;*/
1833 v2s16 p2d(p.X, p.Z);
1836 This will create or load a sector if not found in memory.
1837 If block exists on disk, it will be loaded.
1839 NOTE: On old save formats, this will be slow, as it generates
1840 lighting on blocks for them.
1842 ServerMapSector *sector = (ServerMapSector*)emergeSector(p2d);
1843 assert(sector->getId() == MAPSECTOR_SERVER);
1845 // Try to get a block from the sector
1846 MapBlock *block = NULL;
1847 bool not_on_disk = false;
1849 block = sector->getBlockNoCreate(block_y);
1850 if(block->isDummy() == true)
1855 catch(InvalidPositionException &e)
1861 If block was not found on disk and not going to generate a
1862 new one, make sure there is a dummy block in place.
1864 if(not_on_disk && only_from_disk)
1868 // Create dummy block
1869 block = new MapBlock(this, p, true);
1871 // Add block to sector
1872 sector->insertBlock(block);
1878 //dstream<<"Not found on disk, generating."<<std::endl;
1879 //TimeTaker("emergeBlock()", g_irrlicht);
1882 Do not generate over-limit
1884 if(blockpos_over_limit(p))
1885 throw InvalidPositionException("emergeBlock(): pos. over limit");
1890 Go on generating the block.
1892 TODO: If a dungeon gets generated so that it's side gets
1893 revealed to the outside air, the lighting should be
1898 If block doesn't exist, create one.
1899 If it exists, it is a dummy. In that case unDummify() it.
1901 NOTE: This already sets the map as the parent of the block
1905 block = sector->createBlankBlockNoInsert(block_y);
1909 // Remove the block so that nobody can get a half-generated one.
1910 sector->removeBlock(block);
1911 // Allocate the block to contain the generated data
1915 /*u8 water_material = CONTENT_WATER;
1916 if(g_settings.getBool("endless_water"))
1917 water_material = CONTENT_WATERSOURCE;*/
1918 u8 water_material = CONTENT_WATERSOURCE;
1920 s32 lowest_ground_y = 32767;
1921 s32 highest_ground_y = -32768;
1924 //sector->printHeightmaps();
1926 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1927 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1929 //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
1931 float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
1932 //assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
1933 if(surface_y_f < GROUNDHEIGHT_VALID_MINVALUE)
1935 dstream<<"WARNING: Surface height not found in sector "
1936 "for block that is being emerged"<<std::endl;
1940 s16 surface_y = surface_y_f;
1941 //avg_ground_y += surface_y;
1942 if(surface_y < lowest_ground_y)
1943 lowest_ground_y = surface_y;
1944 if(surface_y > highest_ground_y)
1945 highest_ground_y = surface_y;
1947 s32 surface_depth = 0;
1949 float slope = sector->getSlope(v2s16(x0,z0)).getLength();
1951 //float min_slope = 0.45;
1952 //float max_slope = 0.85;
1953 float min_slope = 0.60;
1954 float max_slope = 1.20;
1955 float min_slope_depth = 5.0;
1956 float max_slope_depth = 0;
1958 if(slope < min_slope)
1959 surface_depth = min_slope_depth;
1960 else if(slope > max_slope)
1961 surface_depth = max_slope_depth;
1963 surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
1965 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1967 s16 real_y = block_y * MAP_BLOCKSIZE + y0;
1972 NOTE: If there are some man-made structures above the
1973 newly created block, they won't be taken into account.
1975 if(real_y > surface_y)
1976 n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
1982 // If node is over heightmap y, it's air or water
1983 if(real_y > surface_y)
1985 // If under water level, it's water
1986 if(real_y < WATER_LEVEL)
1988 n.d = water_material;
1989 n.setLight(LIGHTBANK_DAY,
1990 diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
1996 // Else it's ground or dungeons (air)
1999 // If it's surface_depth under ground, it's stone
2000 if(real_y <= surface_y - surface_depth)
2002 n.d = CONTENT_STONE;
2006 // It is mud if it is under the first ground
2007 // level or under water
2008 if(real_y < WATER_LEVEL || real_y <= surface_y - 1)
2014 n.d = CONTENT_GRASS;
2017 //n.d = CONTENT_MUD;
2019 /*// If under water level, it's mud
2020 if(real_y < WATER_LEVEL)
2022 // Only the topmost node is grass
2023 else if(real_y <= surface_y - 1)
2026 n.d = CONTENT_GRASS;*/
2030 block->setNode(v3s16(x0,y0,z0), n);
2035 Calculate some helper variables
2038 // Completely underground if the highest part of block is under lowest
2040 // This has to be very sure; it's probably one too strict now but
2041 // that's just better.
2042 bool completely_underground =
2043 block_y * MAP_BLOCKSIZE + MAP_BLOCKSIZE < lowest_ground_y;
2045 bool some_part_underground = block_y * MAP_BLOCKSIZE <= highest_ground_y;
2047 bool mostly_underwater_surface = false;
2048 if(highest_ground_y < WATER_LEVEL
2049 && some_part_underground && !completely_underground)
2050 mostly_underwater_surface = true;
2053 Get local attributes
2056 //dstream<<"emergeBlock(): Getting local attributes"<<std::endl;
2058 float caves_amount = 0;
2062 NOTE: BEWARE: Too big amount of attribute points slows verything
2064 1 interpolation from 5000 points takes 2-3ms.
2066 //TimeTaker timer("emergeBlock() local attribute retrieval");
2067 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
2068 PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
2069 caves_amount = list_caves_amount->getInterpolatedFloat(nodepos2d);
2072 //dstream<<"emergeBlock(): Done"<<std::endl;
2078 // Initialize temporary table
2079 const s32 ued = MAP_BLOCKSIZE;
2080 bool underground_emptiness[ued*ued*ued];
2081 for(s32 i=0; i<ued*ued*ued; i++)
2083 underground_emptiness[i] = 0;
2090 Initialize orp and ors. Try to find if some neighboring
2091 MapBlock has a tunnel ended in its side
2095 (float)(myrand()%ued)+0.5,
2096 (float)(myrand()%ued)+0.5,
2097 (float)(myrand()%ued)+0.5
2100 bool found_existing = false;
2106 for(s16 y=0; y<ued; y++)
2107 for(s16 x=0; x<ued; x++)
2109 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2110 if(getNode(ap).d == CONTENT_AIR)
2112 orp = v3f(x+1,y+1,0);
2113 found_existing = true;
2114 goto continue_generating;
2118 catch(InvalidPositionException &e){}
2124 for(s16 y=0; y<ued; y++)
2125 for(s16 x=0; x<ued; x++)
2127 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2128 if(getNode(ap).d == CONTENT_AIR)
2130 orp = v3f(x+1,y+1,ued-1);
2131 found_existing = true;
2132 goto continue_generating;
2136 catch(InvalidPositionException &e){}
2142 for(s16 y=0; y<ued; y++)
2143 for(s16 z=0; z<ued; z++)
2145 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2146 if(getNode(ap).d == CONTENT_AIR)
2148 orp = v3f(0,y+1,z+1);
2149 found_existing = true;
2150 goto continue_generating;
2154 catch(InvalidPositionException &e){}
2160 for(s16 y=0; y<ued; y++)
2161 for(s16 z=0; z<ued; z++)
2163 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2164 if(getNode(ap).d == CONTENT_AIR)
2166 orp = v3f(ued-1,y+1,z+1);
2167 found_existing = true;
2168 goto continue_generating;
2172 catch(InvalidPositionException &e){}
2178 for(s16 x=0; x<ued; x++)
2179 for(s16 z=0; z<ued; z++)
2181 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2182 if(getNode(ap).d == CONTENT_AIR)
2184 orp = v3f(x+1,0,z+1);
2185 found_existing = true;
2186 goto continue_generating;
2190 catch(InvalidPositionException &e){}
2196 for(s16 x=0; x<ued; x++)
2197 for(s16 z=0; z<ued; z++)
2199 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2200 if(getNode(ap).d == CONTENT_AIR)
2202 orp = v3f(x+1,ued-1,z+1);
2203 found_existing = true;
2204 goto continue_generating;
2208 catch(InvalidPositionException &e){}
2210 continue_generating:
2213 Choose whether to actually generate dungeon
2215 bool do_generate_dungeons = true;
2216 // Don't generate if no part is underground
2217 if(!some_part_underground)
2219 do_generate_dungeons = false;
2221 // Don't generate if mostly underwater surface
2222 else if(mostly_underwater_surface)
2224 do_generate_dungeons = false;
2226 // Partly underground = cave
2227 else if(!completely_underground)
2229 do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
2231 // Found existing dungeon underground
2232 else if(found_existing && completely_underground)
2234 do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
2236 // Underground and no dungeons found
2239 do_generate_dungeons = (rand() % 300 <= (s32)(caves_amount*100));
2242 if(do_generate_dungeons)
2245 Generate some tunnel starting from orp and ors
2247 for(u16 i=0; i<3; i++)
2250 (float)(myrand()%ued)+0.5,
2251 (float)(myrand()%ued)+0.5,
2252 (float)(myrand()%ued)+0.5
2256 s16 rs = (myrand()%(max_d-min_d+1))+min_d;
2260 for(float f=0; f<1.0; f+=0.04)
2262 v3f fp = orp + vec * f;
2263 v3s16 cp(fp.X, fp.Y, fp.Z);
2265 s16 d1 = d0 + rs - 1;
2266 for(s16 z0=d0; z0<=d1; z0++)
2268 s16 si = rs - abs(z0);
2269 for(s16 x0=-si; x0<=si-1; x0++)
2271 s16 si2 = rs - abs(x0);
2272 for(s16 y0=-si2+1; y0<=si2-1; y0++)
2278 if(isInArea(p, ued) == false)
2280 underground_emptiness[ued*ued*z + ued*y + x] = 1;
2292 // Set to true if has caves.
2293 // Set when some non-air is changed to air when making caves.
2294 bool has_dungeons = false;
2297 Apply temporary cave data to block
2300 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
2301 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
2303 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
2305 MapNode n = block->getNode(v3s16(x0,y0,z0));
2308 if(underground_emptiness[
2309 ued*ued*(z0*ued/MAP_BLOCKSIZE)
2310 +ued*(y0*ued/MAP_BLOCKSIZE)
2311 +(x0*ued/MAP_BLOCKSIZE)])
2313 if(is_ground_content(n.d))
2316 has_dungeons = true;
2322 block->setNode(v3s16(x0,y0,z0), n);
2327 This is used for guessing whether or not the block should
2328 receive sunlight from the top if the top block doesn't exist
2330 block->setIsUnderground(completely_underground);
2333 Force lighting update if some part of block is partly
2334 underground and has caves.
2336 /*if(some_part_underground && !completely_underground && has_dungeons)
2338 //dstream<<"Half-ground caves"<<std::endl;
2339 lighting_invalidated_blocks[block->getPos()] = block;
2342 // DEBUG: Always update lighting
2343 //lighting_invalidated_blocks[block->getPos()] = block;
2349 if(some_part_underground)
2351 s16 underground_level = (lowest_ground_y/MAP_BLOCKSIZE - block_y)+1;
2356 for(s16 i=0; i<underground_level/4 + 1; i++)
2358 if(myrand()%50 == 0)
2361 (myrand()%(MAP_BLOCKSIZE-2))+1,
2362 (myrand()%(MAP_BLOCKSIZE-2))+1,
2363 (myrand()%(MAP_BLOCKSIZE-2))+1
2369 //if(is_ground_content(block->getNode(cp).d))
2370 if(block->getNode(cp).d == CONTENT_STONE)
2372 block->setNode(cp, n);
2374 for(u16 i=0; i<26; i++)
2376 //if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
2377 if(block->getNode(cp+g_26dirs[i]).d == CONTENT_STONE)
2379 block->setNode(cp+g_26dirs[i], n);
2387 u16 coal_amount = 30.0 * g_settings.getFloat("coal_amount");
2388 u16 coal_rareness = 60 / coal_amount;
2389 if(coal_rareness == 0)
2391 if(myrand()%coal_rareness == 0)
2393 u16 a = myrand() % 16;
2394 u16 amount = coal_amount * a*a*a / 1000;
2395 for(s16 i=0; i<amount; i++)
2398 (myrand()%(MAP_BLOCKSIZE-2))+1,
2399 (myrand()%(MAP_BLOCKSIZE-2))+1,
2400 (myrand()%(MAP_BLOCKSIZE-2))+1
2404 n.d = CONTENT_COALSTONE;
2406 //dstream<<"Adding coalstone"<<std::endl;
2408 //if(is_ground_content(block->getNode(cp).d))
2409 if(block->getNode(cp).d == CONTENT_STONE)
2411 block->setNode(cp, n);
2413 for(u16 i=0; i<26; i++)
2415 //if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
2416 if(block->getNode(cp+g_26dirs[i]).d == CONTENT_STONE)
2418 block->setNode(cp+g_26dirs[i], n);
2425 Create a few rats in empty blocks underground
2427 if(completely_underground)
2429 //for(u16 i=0; i<2; i++)
2432 (myrand()%(MAP_BLOCKSIZE-2))+1,
2433 (myrand()%(MAP_BLOCKSIZE-2))+1,
2434 (myrand()%(MAP_BLOCKSIZE-2))+1
2437 // Check that the place is empty
2438 //if(!is_ground_content(block->getNode(cp).d))
2441 RatObject *obj = new RatObject(NULL, -1, intToFloat(cp));
2442 block->addObject(obj);
2448 Add block to sector.
2450 sector->insertBlock(block);
2456 // An y-wise container of changed blocks
2457 core::map<s16, MapBlock*> changed_blocks_sector;
2460 Check if any sector's objects can be placed now.
2463 core::map<v3s16, u8> *objects = sector->getObjects();
2464 core::list<v3s16> objects_to_remove;
2465 for(core::map<v3s16, u8>::Iterator i = objects->getIterator();
2466 i.atEnd() == false; i++)
2468 v3s16 p = i.getNode()->getKey();
2470 u8 d = i.getNode()->getValue();
2472 // Ground level point (user for stuff that is on ground)
2474 bool ground_found = true;
2476 // Search real ground level
2480 MapNode n = sector->getNode(gp);
2482 // If not air, go one up and continue to placing the tree
2483 if(n.d != CONTENT_AIR)
2489 // If air, go one down
2490 gp += v3s16(0,-1,0);
2492 }catch(InvalidPositionException &e)
2494 // Ground not found.
2495 ground_found = false;
2496 // This is most close to ground
2503 if(d == SECTOR_OBJECT_TEST)
2505 if(sector->isValidArea(p + v3s16(0,0,0),
2506 p + v3s16(0,0,0), &changed_blocks_sector))
2509 n.d = CONTENT_TORCH;
2510 sector->setNode(p, n);
2511 objects_to_remove.push_back(p);
2514 else if(d == SECTOR_OBJECT_TREE_1)
2516 if(ground_found == false)
2519 v3s16 p_min = gp + v3s16(-1,0,-1);
2520 v3s16 p_max = gp + v3s16(1,5,1);
2521 if(sector->isValidArea(p_min, p_max,
2522 &changed_blocks_sector))
2526 sector->setNode(gp+v3s16(0,0,0), n);
2527 sector->setNode(gp+v3s16(0,1,0), n);
2528 sector->setNode(gp+v3s16(0,2,0), n);
2529 sector->setNode(gp+v3s16(0,3,0), n);
2531 n.d = CONTENT_LEAVES;
2533 if(rand()%4!=0) sector->setNode(gp+v3s16(0,5,0), n);
2535 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,0), n);
2536 if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,0), n);
2537 if(rand()%3!=0) sector->setNode(gp+v3s16(0,5,-1), n);
2538 if(rand()%3!=0) sector->setNode(gp+v3s16(0,5,1), n);
2539 /*if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,1), n);
2540 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,1), n);
2541 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,-1), n);
2542 if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,-1), n);*/
2544 sector->setNode(gp+v3s16(0,4,0), n);
2546 sector->setNode(gp+v3s16(-1,4,0), n);
2547 sector->setNode(gp+v3s16(1,4,0), n);
2548 sector->setNode(gp+v3s16(0,4,-1), n);
2549 sector->setNode(gp+v3s16(0,4,1), n);
2550 sector->setNode(gp+v3s16(1,4,1), n);
2551 sector->setNode(gp+v3s16(-1,4,1), n);
2552 sector->setNode(gp+v3s16(-1,4,-1), n);
2553 sector->setNode(gp+v3s16(1,4,-1), n);
2555 sector->setNode(gp+v3s16(-1,3,0), n);
2556 sector->setNode(gp+v3s16(1,3,0), n);
2557 sector->setNode(gp+v3s16(0,3,-1), n);
2558 sector->setNode(gp+v3s16(0,3,1), n);
2559 sector->setNode(gp+v3s16(1,3,1), n);
2560 sector->setNode(gp+v3s16(-1,3,1), n);
2561 sector->setNode(gp+v3s16(-1,3,-1), n);
2562 sector->setNode(gp+v3s16(1,3,-1), n);
2564 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,0), n);
2565 if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,0), n);
2566 if(rand()%3!=0) sector->setNode(gp+v3s16(0,2,-1), n);
2567 if(rand()%3!=0) sector->setNode(gp+v3s16(0,2,1), n);
2568 /*if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,1), n);
2569 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,1), n);
2570 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,-1), n);
2571 if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,-1), n);*/
2573 // Objects are identified by wanted position
2574 objects_to_remove.push_back(p);
2576 // Lighting has to be recalculated for this one.
2577 sector->getBlocksInArea(p_min, p_max,
2578 lighting_invalidated_blocks);
2581 else if(d == SECTOR_OBJECT_BUSH_1)
2583 if(ground_found == false)
2586 if(sector->isValidArea(gp + v3s16(0,0,0),
2587 gp + v3s16(0,0,0), &changed_blocks_sector))
2590 n.d = CONTENT_LEAVES;
2591 sector->setNode(gp+v3s16(0,0,0), n);
2593 // Objects are identified by wanted position
2594 objects_to_remove.push_back(p);
2597 else if(d == SECTOR_OBJECT_RAVINE)
2600 v3s16 p_min = p + v3s16(-6,maxdepth,-6);
2601 v3s16 p_max = p + v3s16(6,6,6);
2602 if(sector->isValidArea(p_min, p_max,
2603 &changed_blocks_sector))
2606 n.d = CONTENT_STONE;
2609 s16 depth = maxdepth + (myrand()%10);
2611 s16 minz = -6 - (-2);
2613 for(s16 x=-6; x<=6; x++)
2615 z += -1 + (myrand()%3);
2620 for(s16 y=depth+(myrand()%2); y<=6; y++)
2622 /*std::cout<<"("<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
2625 v3s16 p2 = p + v3s16(x,y,z-2);
2626 if(is_ground_content(sector->getNode(p2).d)
2627 && !is_mineral(sector->getNode(p2).d))
2628 sector->setNode(p2, n);
2631 v3s16 p2 = p + v3s16(x,y,z-1);
2632 if(is_ground_content(sector->getNode(p2).d)
2633 && !is_mineral(sector->getNode(p2).d))
2634 sector->setNode(p2, n2);
2637 v3s16 p2 = p + v3s16(x,y,z+0);
2638 if(is_ground_content(sector->getNode(p2).d)
2639 && !is_mineral(sector->getNode(p2).d))
2640 sector->setNode(p2, n2);
2643 v3s16 p2 = p + v3s16(x,y,z+1);
2644 if(is_ground_content(sector->getNode(p2).d)
2645 && !is_mineral(sector->getNode(p2).d))
2646 sector->setNode(p2, n);
2649 //if(sector->getNode(p+v3s16(x,y,z+1)).solidness()==2)
2650 //if(p.Y+y <= sector->getGroundHeight(p2d+v2s16(x,z-2))+0.5)
2654 objects_to_remove.push_back(p);
2656 // Lighting has to be recalculated for this one.
2657 sector->getBlocksInArea(p_min, p_max,
2658 lighting_invalidated_blocks);
2663 dstream<<"ServerMap::emergeBlock(): "
2664 "Invalid heightmap object"
2669 catch(InvalidPositionException &e)
2671 dstream<<"WARNING: "<<__FUNCTION_NAME
2672 <<": while inserting object "<<(int)d
2673 <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<"):"
2674 <<" InvalidPositionException.what()="
2675 <<e.what()<<std::endl;
2676 // This is not too fatal and seems to happen sometimes.
2681 for(core::list<v3s16>::Iterator i = objects_to_remove.begin();
2682 i != objects_to_remove.end(); i++)
2684 objects->remove(*i);
2688 Initially update sunlight
2692 core::map<v3s16, bool> light_sources;
2693 bool black_air_left = false;
2694 bool bottom_invalid =
2695 block->propagateSunlight(light_sources, true, &black_air_left);
2697 // If sunlight didn't reach everywhere and part of block is
2698 // above ground, lighting has to be properly updated
2699 if(black_air_left && some_part_underground)
2701 lighting_invalidated_blocks[block->getPos()] = block;
2706 Translate sector's changed blocks to global changed blocks
2709 for(core::map<s16, MapBlock*>::Iterator
2710 i = changed_blocks_sector.getIterator();
2711 i.atEnd() == false; i++)
2713 MapBlock *block = i.getNode()->getValue();
2715 changed_blocks.insert(block->getPos(), block);
2724 <<"lighting_invalidated_blocks.size()"
2728 <<" "<<lighting_invalidated_blocks.size()
2729 <<", "<<has_dungeons
2730 <<", "<<completely_underground
2731 <<", "<<some_part_underground
2736 Debug mode operation
2738 bool haxmode = g_settings.getBool("haxmode");
2741 // Don't calculate lighting at all
2742 lighting_invalidated_blocks.clear();
2748 void ServerMap::createDir(std::string path)
2750 if(fs::CreateDir(path) == false)
2752 m_dout<<DTIME<<"ServerMap: Failed to create directory "
2753 <<"\""<<path<<"\""<<std::endl;
2754 throw BaseException("ServerMap failed to create directory");
2758 std::string ServerMap::getSectorSubDir(v2s16 pos)
2761 snprintf(cc, 9, "%.4x%.4x",
2762 (unsigned int)pos.X&0xffff,
2763 (unsigned int)pos.Y&0xffff);
2765 return std::string(cc);
2768 std::string ServerMap::getSectorDir(v2s16 pos)
2770 return m_savedir + "/sectors/" + getSectorSubDir(pos);
2773 v2s16 ServerMap::getSectorPos(std::string dirname)
2775 if(dirname.size() != 8)
2776 throw InvalidFilenameException("Invalid sector directory name");
2778 int r = sscanf(dirname.c_str(), "%4x%4x", &x, &y);
2780 throw InvalidFilenameException("Invalid sector directory name");
2781 v2s16 pos((s16)x, (s16)y);
2785 v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
2787 v2s16 p2d = getSectorPos(sectordir);
2789 if(blockfile.size() != 4){
2790 throw InvalidFilenameException("Invalid block filename");
2793 int r = sscanf(blockfile.c_str(), "%4x", &y);
2795 throw InvalidFilenameException("Invalid block filename");
2796 return v3s16(p2d.X, y, p2d.Y);
2800 #define ENABLE_SECTOR_SAVING 1
2801 #define ENABLE_SECTOR_LOADING 1
2802 #define ENABLE_BLOCK_SAVING 1
2803 #define ENABLE_BLOCK_LOADING 1
2805 void ServerMap::save(bool only_changed)
2807 DSTACK(__FUNCTION_NAME);
2808 if(m_map_saving_enabled == false)
2810 dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
2814 if(only_changed == false)
2815 dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
2818 saveMasterHeightmap();
2820 u32 sector_meta_count = 0;
2821 u32 block_count = 0;
2824 JMutexAutoLock lock(m_sector_mutex);
2826 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
2827 for(; i.atEnd() == false; i++)
2829 ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue();
2830 assert(sector->getId() == MAPSECTOR_SERVER);
2832 if(ENABLE_SECTOR_SAVING)
2834 if(sector->differs_from_disk || only_changed == false)
2836 saveSectorMeta(sector);
2837 sector_meta_count++;
2840 if(ENABLE_BLOCK_SAVING)
2842 core::list<MapBlock*> blocks;
2843 sector->getBlocks(blocks);
2844 core::list<MapBlock*>::Iterator j;
2845 for(j=blocks.begin(); j!=blocks.end(); j++)
2847 MapBlock *block = *j;
2848 if(block->getChangedFlag() || only_changed == false)
2860 Only print if something happened or saved whole map
2862 if(only_changed == false || sector_meta_count != 0
2863 || block_count != 0)
2865 dstream<<DTIME<<"ServerMap: Written: "
2866 <<sector_meta_count<<" sector metadata files, "
2867 <<block_count<<" block files"
2872 void ServerMap::loadAll()
2874 DSTACK(__FUNCTION_NAME);
2875 dstream<<DTIME<<"ServerMap: Loading map..."<<std::endl;
2877 loadMasterHeightmap();
2879 std::vector<fs::DirListNode> list = fs::GetDirListing(m_savedir+"/sectors/");
2881 dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
2883 JMutexAutoLock lock(m_sector_mutex);
2886 s32 printed_counter = -100000;
2887 s32 count = list.size();
2889 std::vector<fs::DirListNode>::iterator i;
2890 for(i=list.begin(); i!=list.end(); i++)
2892 if(counter > printed_counter + 10)
2894 dstream<<DTIME<<counter<<"/"<<count<<std::endl;
2895 printed_counter = counter;
2899 MapSector *sector = NULL;
2901 // We want directories
2905 sector = loadSectorMeta(i->name);
2907 catch(InvalidFilenameException &e)
2909 // This catches unknown crap in directory
2912 if(ENABLE_BLOCK_LOADING)
2914 std::vector<fs::DirListNode> list2 = fs::GetDirListing
2915 (m_savedir+"/sectors/"+i->name);
2916 std::vector<fs::DirListNode>::iterator i2;
2917 for(i2=list2.begin(); i2!=list2.end(); i2++)
2923 loadBlock(i->name, i2->name, sector);
2925 catch(InvalidFilenameException &e)
2927 // This catches unknown crap in directory
2932 dstream<<DTIME<<"ServerMap: Map loaded."<<std::endl;
2935 void ServerMap::saveMasterHeightmap()
2937 DSTACK(__FUNCTION_NAME);
2938 createDir(m_savedir);
2940 std::string fullpath = m_savedir + "/master_heightmap";
2941 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
2942 if(o.good() == false)
2943 throw FileNotGoodException("Cannot open master heightmap");
2945 // Format used for writing
2946 u8 version = SER_FMT_VER_HIGHEST;
2949 SharedBuffer<u8> hmdata = m_heightmap->serialize(version);
2951 [0] u8 serialization version
2952 [1] X master heightmap
2954 u32 fullsize = 1 + hmdata.getSize();
2955 SharedBuffer<u8> data(fullsize);
2958 memcpy(&data[1], *hmdata, hmdata.getSize());
2960 o.write((const char*)*data, fullsize);
2963 m_heightmap->serialize(o, version);
2966 void ServerMap::loadMasterHeightmap()
2968 DSTACK(__FUNCTION_NAME);
2969 std::string fullpath = m_savedir + "/master_heightmap";
2970 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
2971 if(is.good() == false)
2972 throw FileNotGoodException("Cannot open master heightmap");
2974 if(m_heightmap != NULL)
2977 m_heightmap = UnlimitedHeightmap::deSerialize(is, &m_padb);
2980 void ServerMap::saveSectorMeta(ServerMapSector *sector)
2982 DSTACK(__FUNCTION_NAME);
2983 // Format used for writing
2984 u8 version = SER_FMT_VER_HIGHEST;
2986 v2s16 pos = sector->getPos();
2987 createDir(m_savedir);
2988 createDir(m_savedir+"/sectors");
2989 std::string dir = getSectorDir(pos);
2992 std::string fullpath = dir + "/heightmap";
2993 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
2994 if(o.good() == false)
2995 throw FileNotGoodException("Cannot open master heightmap");
2997 sector->serialize(o, version);
2999 sector->differs_from_disk = false;
3002 MapSector* ServerMap::loadSectorMeta(std::string dirname)
3004 DSTACK(__FUNCTION_NAME);
3006 v2s16 p2d = getSectorPos(dirname);
3007 std::string dir = m_savedir + "/sectors/" + dirname;
3009 std::string fullpath = dir + "/heightmap";
3010 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3011 if(is.good() == false)
3012 throw FileNotGoodException("Cannot open sector heightmap");
3014 ServerMapSector *sector = ServerMapSector::deSerialize
3015 (is, this, p2d, &m_hwrapper, m_sectors);
3017 sector->differs_from_disk = false;
3022 bool ServerMap::loadSectorFull(v2s16 p2d)
3024 DSTACK(__FUNCTION_NAME);
3025 std::string sectorsubdir = getSectorSubDir(p2d);
3027 MapSector *sector = NULL;
3029 JMutexAutoLock lock(m_sector_mutex);
3032 sector = loadSectorMeta(sectorsubdir);
3034 catch(InvalidFilenameException &e)
3038 catch(FileNotGoodException &e)
3042 catch(std::exception &e)
3047 if(ENABLE_BLOCK_LOADING)
3049 std::vector<fs::DirListNode> list2 = fs::GetDirListing
3050 (m_savedir+"/sectors/"+sectorsubdir);
3051 std::vector<fs::DirListNode>::iterator i2;
3052 for(i2=list2.begin(); i2!=list2.end(); i2++)
3058 loadBlock(sectorsubdir, i2->name, sector);
3060 catch(InvalidFilenameException &e)
3062 // This catches unknown crap in directory
3070 bool ServerMap::deFlushSector(v2s16 p2d)
3072 DSTACK(__FUNCTION_NAME);
3073 // See if it already exists in memory
3075 MapSector *sector = getSectorNoGenerate(p2d);
3078 catch(InvalidPositionException &e)
3081 Try to load the sector from disk.
3083 if(loadSectorFull(p2d) == true)
3092 void ServerMap::saveBlock(MapBlock *block)
3094 DSTACK(__FUNCTION_NAME);
3096 Dummy blocks are not written
3098 if(block->isDummy())
3100 /*v3s16 p = block->getPos();
3101 dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
3102 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
3106 // Format used for writing
3107 u8 version = SER_FMT_VER_HIGHEST;
3109 v3s16 p3d = block->getPos();
3110 v2s16 p2d(p3d.X, p3d.Z);
3111 createDir(m_savedir);
3112 createDir(m_savedir+"/sectors");
3113 std::string dir = getSectorDir(p2d);
3116 // Block file is map/sectors/xxxxxxxx/xxxx
3118 snprintf(cc, 5, "%.4x", (unsigned int)p3d.Y&0xffff);
3119 std::string fullpath = dir + "/" + cc;
3120 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
3121 if(o.good() == false)
3122 throw FileNotGoodException("Cannot open block data");
3125 [0] u8 serialization version
3128 o.write((char*)&version, 1);
3130 block->serialize(o, version);
3133 Versions up from 9 have block objects.
3137 block->serializeObjects(o, version);
3140 // We just wrote it to the disk
3141 block->resetChangedFlag();
3144 void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
3146 DSTACK(__FUNCTION_NAME);
3150 // Block file is map/sectors/xxxxxxxx/xxxx
3151 std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
3152 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3153 if(is.good() == false)
3154 throw FileNotGoodException("Cannot open block file");
3156 v3s16 p3d = getBlockPos(sectordir, blockfile);
3157 v2s16 p2d(p3d.X, p3d.Z);
3159 assert(sector->getPos() == p2d);
3161 u8 version = SER_FMT_VER_INVALID;
3162 is.read((char*)&version, 1);
3164 /*u32 block_size = MapBlock::serializedLength(version);
3165 SharedBuffer<u8> data(block_size);
3166 is.read((char*)*data, block_size);*/
3168 // This will always return a sector because we're the server
3169 //MapSector *sector = emergeSector(p2d);
3171 MapBlock *block = NULL;
3172 bool created_new = false;
3174 block = sector->getBlockNoCreate(p3d.Y);
3176 catch(InvalidPositionException &e)
3178 block = sector->createBlankBlockNoInsert(p3d.Y);
3182 // deserialize block data
3183 block->deSerialize(is, version);
3186 Versions up from 9 have block objects.
3190 block->updateObjects(is, version, NULL, 0);
3194 sector->insertBlock(block);
3197 Convert old formats to new and save
3200 // Save old format blocks in new format
3201 if(version < SER_FMT_VER_HIGHEST)
3206 // We just loaded it from the disk, so it's up-to-date.
3207 block->resetChangedFlag();
3210 catch(SerializationError &e)
3212 dstream<<"WARNING: Invalid block data on disk "
3213 "(SerializationError). Ignoring."
3218 // Gets from master heightmap
3219 void ServerMap::getSectorCorners(v2s16 p2d, s16 *corners)
3221 assert(m_heightmap != NULL);
3229 corners[0] = m_heightmap->getGroundHeight
3230 ((p2d+v2s16(0,0))*SECTOR_HEIGHTMAP_SPLIT);
3231 corners[1] = m_heightmap->getGroundHeight
3232 ((p2d+v2s16(1,0))*SECTOR_HEIGHTMAP_SPLIT);
3233 corners[2] = m_heightmap->getGroundHeight
3234 ((p2d+v2s16(1,1))*SECTOR_HEIGHTMAP_SPLIT);
3235 corners[3] = m_heightmap->getGroundHeight
3236 ((p2d+v2s16(0,1))*SECTOR_HEIGHTMAP_SPLIT);
3239 void ServerMap::PrintInfo(std::ostream &out)
3250 ClientMap::ClientMap(
3252 MapDrawControl &control,
3253 scene::ISceneNode* parent,
3254 scene::ISceneManager* mgr,
3258 scene::ISceneNode(parent, mgr, id),
3265 /*m_box = core::aabbox3d<f32>(0,0,0,
3266 map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
3267 /*m_box = core::aabbox3d<f32>(0,0,0,
3268 map->getSizeNodes().X * BS,
3269 map->getSizeNodes().Y * BS,
3270 map->getSizeNodes().Z * BS);*/
3271 m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
3272 BS*1000000,BS*1000000,BS*1000000);
3274 //setPosition(v3f(BS,BS,BS));
3277 ClientMap::~ClientMap()
3279 JMutexAutoLock lock(mesh_mutex);
3288 MapSector * ClientMap::emergeSector(v2s16 p2d)
3290 DSTACK(__FUNCTION_NAME);
3291 // Check that it doesn't exist already
3293 return getSectorNoGenerate(p2d);
3295 catch(InvalidPositionException &e)
3299 // Create a sector with no heightmaps
3300 ClientMapSector *sector = new ClientMapSector(this, p2d);
3303 JMutexAutoLock lock(m_sector_mutex);
3304 m_sectors.insert(p2d, sector);
3310 void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
3312 DSTACK(__FUNCTION_NAME);
3313 ClientMapSector *sector = NULL;
3315 JMutexAutoLock lock(m_sector_mutex);
3317 core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
3321 sector = (ClientMapSector*)n->getValue();
3322 assert(sector->getId() == MAPSECTOR_CLIENT);
3326 sector = new ClientMapSector(this, p2d);
3328 JMutexAutoLock lock(m_sector_mutex);
3329 m_sectors.insert(p2d, sector);
3333 sector->deSerialize(is);
3336 void ClientMap::OnRegisterSceneNode()
3340 SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
3341 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
3344 ISceneNode::OnRegisterSceneNode();
3347 void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
3349 //m_dout<<DTIME<<"Rendering map..."<<std::endl;
3350 DSTACK(__FUNCTION_NAME);
3352 bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
3355 Get time for measuring timeout.
3357 Measuring time is very useful for long delays when the
3358 machine is swapping a lot.
3360 int time1 = time(0);
3362 u32 daynight_ratio = m_client->getDayNightRatio();
3364 m_camera_mutex.Lock();
3365 v3f camera_position = m_camera_position;
3366 v3f camera_direction = m_camera_direction;
3367 m_camera_mutex.Unlock();
3370 Get all blocks and draw all visible ones
3373 v3s16 cam_pos_nodes(
3374 camera_position.X / BS,
3375 camera_position.Y / BS,
3376 camera_position.Z / BS);
3378 v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
3380 v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
3381 v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
3383 // Take a fair amount as we will be dropping more out later
3385 p_nodes_min.X / MAP_BLOCKSIZE - 1,
3386 p_nodes_min.Y / MAP_BLOCKSIZE - 1,
3387 p_nodes_min.Z / MAP_BLOCKSIZE - 1);
3389 p_nodes_max.X / MAP_BLOCKSIZE + 1,
3390 p_nodes_max.Y / MAP_BLOCKSIZE + 1,
3391 p_nodes_max.Z / MAP_BLOCKSIZE + 1);
3393 u32 vertex_count = 0;
3395 // For limiting number of mesh updates per frame
3396 u32 mesh_update_count = 0;
3398 u32 blocks_would_have_drawn = 0;
3399 u32 blocks_drawn = 0;
3401 //NOTE: The sectors map should be locked but we're not doing it
3402 // because it'd cause too much delays
3404 int timecheck_counter = 0;
3405 core::map<v2s16, MapSector*>::Iterator si;
3406 si = m_sectors.getIterator();
3407 for(; si.atEnd() == false; si++)
3410 timecheck_counter++;
3411 if(timecheck_counter > 50)
3413 int time2 = time(0);
3414 if(time2 > time1 + 4)
3416 dstream<<"ClientMap::renderMap(): "
3417 "Rendering takes ages, returning."
3424 MapSector *sector = si.getNode()->getValue();
3425 v2s16 sp = sector->getPos();
3427 if(m_control.range_all == false)
3429 if(sp.X < p_blocks_min.X
3430 || sp.X > p_blocks_max.X
3431 || sp.Y < p_blocks_min.Z
3432 || sp.Y > p_blocks_max.Z)
3436 core::list< MapBlock * > sectorblocks;
3437 sector->getBlocks(sectorblocks);
3443 core::list< MapBlock * >::Iterator i;
3444 for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
3446 MapBlock *block = *i;
3449 Compare block position to camera position, skip
3450 if not seen on display
3453 float range = 100000 * BS;
3454 if(m_control.range_all == false)
3455 range = m_control.wanted_range * BS;
3457 if(isBlockInSight(block->getPos(), camera_position,
3458 camera_direction, range) == false)
3464 v3s16 blockpos_nodes = block->getPosRelative();
3466 // Block center position
3468 ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
3469 ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
3470 ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
3473 // Block position relative to camera
3474 v3f blockpos_relative = blockpos - camera_position;
3476 // Distance in camera direction (+=front, -=back)
3477 f32 dforward = blockpos_relative.dotProduct(camera_direction);
3480 f32 d = blockpos_relative.getLength();
3482 if(m_control.range_all == false)
3484 // If block is far away, don't draw it
3485 if(d > m_control.wanted_range * BS)
3489 // Maximum radius of a block
3490 f32 block_max_radius = 0.5*1.44*1.44*MAP_BLOCKSIZE*BS;
3492 // If block is (nearly) touching the camera, don't
3493 // bother validating further (that is, render it anyway)
3494 if(d > block_max_radius * 1.5)
3496 // Cosine of the angle between the camera direction
3497 // and the block direction (camera_direction is an unit vector)
3498 f32 cosangle = dforward / d;
3500 // Compensate for the size of the block
3501 // (as the block has to be shown even if it's a bit off FOV)
3502 // This is an estimate.
3503 cosangle += block_max_radius / dforward;
3505 // If block is not in the field of view, skip it
3506 //if(cosangle < cos(FOV_ANGLE/2))
3507 if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
3512 v3s16 blockpos_nodes = block->getPosRelative();
3514 // Block center position
3516 ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
3517 ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
3518 ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
3521 // Block position relative to camera
3522 v3f blockpos_relative = blockpos - camera_position;
3525 f32 d = blockpos_relative.getLength();
3528 Draw the faces of the block
3531 bool mesh_expired = false;
3534 JMutexAutoLock lock(block->mesh_mutex);
3536 mesh_expired = block->getMeshExpired();
3538 // Mesh has not been expired and there is no mesh:
3539 // block has no content
3540 if(block->mesh == NULL && mesh_expired == false)
3544 f32 faraway = BS*50;
3545 //f32 faraway = m_control.wanted_range * BS;
3548 This has to be done with the mesh_mutex unlocked
3550 // Pretty random but this should work somewhat nicely
3551 if(mesh_expired && (
3552 (mesh_update_count < 3
3553 && (d < faraway || mesh_update_count < 2)
3556 (m_control.range_all && mesh_update_count < 20)
3559 /*if(mesh_expired && mesh_update_count < 6
3560 && (d < faraway || mesh_update_count < 3))*/
3562 mesh_update_count++;
3564 // Mesh has been expired: generate new mesh
3565 //block->updateMeshes(daynight_i);
3566 block->updateMesh(daynight_ratio);
3568 mesh_expired = false;
3572 Don't draw an expired mesh that is far away
3574 /*if(mesh_expired && d >= faraway)
3577 // Instead, delete it
3578 JMutexAutoLock lock(block->mesh_mutex);
3581 block->mesh->drop();
3584 // And continue to next block
3589 JMutexAutoLock lock(block->mesh_mutex);
3591 scene::SMesh *mesh = block->mesh;
3596 blocks_would_have_drawn++;
3597 if(blocks_drawn >= m_control.wanted_max_blocks
3598 && m_control.range_all == false
3599 && d > m_control.wanted_min_range * BS)
3603 u32 c = mesh->getMeshBufferCount();
3605 for(u32 i=0; i<c; i++)
3607 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
3608 const video::SMaterial& material = buf->getMaterial();
3609 video::IMaterialRenderer* rnd =
3610 driver->getMaterialRenderer(material.MaterialType);
3611 bool transparent = (rnd && rnd->isTransparent());
3612 // Render transparent on transparent pass and likewise.
3613 if(transparent == is_transparent_pass)
3615 driver->setMaterial(buf->getMaterial());
3616 driver->drawMeshBuffer(buf);
3617 vertex_count += buf->getVertexCount();
3621 } // foreach sectorblocks
3624 m_control.blocks_drawn = blocks_drawn;
3625 m_control.blocks_would_have_drawn = blocks_would_have_drawn;
3627 /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
3628 <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
3631 v3s16 ClientMap::setTempMod(v3s16 p, NodeMod mod, bool *changed)
3634 Add it to all blocks touching it
3637 v3s16(0,0,0), // this
3638 v3s16(0,0,1), // back
3639 v3s16(0,1,0), // top
3640 v3s16(1,0,0), // right
3641 v3s16(0,0,-1), // front
3642 v3s16(0,-1,0), // bottom
3643 v3s16(-1,0,0), // left
3645 for(u16 i=0; i<7; i++)
3647 v3s16 p2 = p + dirs[i];
3648 // Block position of neighbor (or requested) node
3649 v3s16 blockpos = getNodeBlockPos(p2);
3650 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
3651 if(blockref == NULL)
3653 // Relative position of requested node
3654 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
3655 if(blockref->setTempMod(relpos, mod))
3661 return getNodeBlockPos(p);
3663 v3s16 ClientMap::clearTempMod(v3s16 p, bool *changed)
3666 v3s16(0,0,0), // this
3667 v3s16(0,0,1), // back
3668 v3s16(0,1,0), // top
3669 v3s16(1,0,0), // right
3670 v3s16(0,0,-1), // front
3671 v3s16(0,-1,0), // bottom
3672 v3s16(-1,0,0), // left
3674 for(u16 i=0; i<7; i++)
3676 v3s16 p2 = p + dirs[i];
3677 // Block position of neighbor (or requested) node
3678 v3s16 blockpos = getNodeBlockPos(p2);
3679 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
3680 if(blockref == NULL)
3682 // Relative position of requested node
3683 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
3684 if(blockref->clearTempMod(relpos))
3690 return getNodeBlockPos(p);
3693 void ClientMap::PrintInfo(std::ostream &out)
3704 MapVoxelManipulator::MapVoxelManipulator(Map *map)
3709 MapVoxelManipulator::~MapVoxelManipulator()
3711 /*dstream<<"MapVoxelManipulator: blocks: "<<m_loaded_blocks.size()
3716 void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id)
3718 TimeTaker timer1("emerge", &emerge_time);
3720 // Units of these are MapBlocks
3721 v3s16 p_min = getNodeBlockPos(a.MinEdge);
3722 v3s16 p_max = getNodeBlockPos(a.MaxEdge);
3724 VoxelArea block_area_nodes
3725 (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
3727 addArea(block_area_nodes);
3729 for(s32 z=p_min.Z; z<=p_max.Z; z++)
3730 for(s32 y=p_min.Y; y<=p_max.Y; y++)
3731 for(s32 x=p_min.X; x<=p_max.X; x++)
3734 core::map<v3s16, bool>::Node *n;
3735 n = m_loaded_blocks.find(p);
3739 bool block_data_inexistent = false;
3742 TimeTaker timer1("emerge load", &emerge_load_time);
3744 /*dstream<<"Loading block (caller_id="<<caller_id<<")"
3745 <<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
3748 dstream<<std::endl;*/
3750 MapBlock *block = m_map->getBlockNoCreate(p);
3751 if(block->isDummy())
3752 block_data_inexistent = true;
3754 block->copyTo(*this);
3756 catch(InvalidPositionException &e)
3758 block_data_inexistent = true;
3761 if(block_data_inexistent)
3763 VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
3764 // Fill with VOXELFLAG_INEXISTENT
3765 for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
3766 for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
3768 s32 i = m_area.index(a.MinEdge.X,y,z);
3769 memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE);
3773 m_loaded_blocks.insert(p, true);
3776 //dstream<<"emerge done"<<std::endl;
3781 void MapVoxelManipulator::emerge(VoxelArea a)
3783 TimeTaker timer1("emerge", &emerge_time);
3785 v3s16 size = a.getExtent();
3787 VoxelArea padded = a;
3788 padded.pad(m_area.getExtent() / 4);
3791 for(s16 z=0; z<size.Z; z++)
3792 for(s16 y=0; y<size.Y; y++)
3793 for(s16 x=0; x<size.X; x++)
3796 s32 i = m_area.index(a.MinEdge + p);
3797 // Don't touch nodes that have already been loaded
3798 if(!(m_flags[i] & VOXELFLAG_NOT_LOADED))
3802 TimeTaker timer1("emerge load", &emerge_load_time);
3803 MapNode n = m_map->getNode(a.MinEdge + p);
3807 catch(InvalidPositionException &e)
3809 m_flags[i] = VOXELFLAG_INEXISTENT;
3817 TODO: Add an option to only update eg. water and air nodes.
3818 This will make it interfere less with important stuff if
3821 void MapVoxelManipulator::blitBack
3822 (core::map<v3s16, MapBlock*> & modified_blocks)
3824 if(m_area.getExtent() == v3s16(0,0,0))
3827 //TimeTaker timer1("blitBack");
3830 Initialize block cache
3832 v3s16 blockpos_last;
3833 MapBlock *block = NULL;
3834 bool block_checked_in_modified = false;
3836 for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
3837 for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
3838 for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
3842 u8 f = m_flags[m_area.index(p)];
3843 if(f & (VOXELFLAG_NOT_LOADED|VOXELFLAG_INEXISTENT))
3846 MapNode &n = m_data[m_area.index(p)];
3848 v3s16 blockpos = getNodeBlockPos(p);
3853 if(block == NULL || blockpos != blockpos_last){
3854 block = m_map->getBlockNoCreate(blockpos);
3855 blockpos_last = blockpos;
3856 block_checked_in_modified = false;
3859 // Calculate relative position in block
3860 v3s16 relpos = p - blockpos * MAP_BLOCKSIZE;
3862 // Don't continue if nothing has changed here
3863 if(block->getNode(relpos) == n)
3866 //m_map->setNode(m_area.MinEdge + p, n);
3867 block->setNode(relpos, n);
3870 Make sure block is in modified_blocks
3872 if(block_checked_in_modified == false)
3874 modified_blocks[blockpos] = block;
3875 block_checked_in_modified = true;
3878 catch(InvalidPositionException &e)