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),
41 m_sector_mutex.Init();
42 m_camera_mutex.Init();
43 assert(m_sector_mutex.IsInitialized());
44 assert(m_camera_mutex.IsInitialized());
46 // Get this so that the player can stay on it at first
47 //getSector(v2s16(0,0));
55 /*updater.setRun(false);
56 while(updater.IsRunning())
62 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
63 for(; i.atEnd() == false; i++)
65 MapSector *sector = i.getNode()->getValue();
70 MapSector * Map::getSectorNoGenerate(v2s16 p)
72 JMutexAutoLock lock(m_sector_mutex);
74 if(m_sector_cache != NULL && p == m_sector_cache_p){
75 MapSector * sector = m_sector_cache;
76 // Reset inactivity timer
77 sector->usage_timer = 0.0;
81 core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
82 // If sector doesn't exist, throw an exception
85 throw InvalidPositionException();
88 MapSector *sector = n->getValue();
90 // Cache the last result
92 m_sector_cache = sector;
94 //MapSector * ref(sector);
96 // Reset inactivity timer
97 sector->usage_timer = 0.0;
101 MapBlock * Map::getBlockNoCreate(v3s16 p3d)
103 v2s16 p2d(p3d.X, p3d.Z);
104 MapSector * sector = getSectorNoGenerate(p2d);
106 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
111 MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
115 v2s16 p2d(p3d.X, p3d.Z);
116 MapSector * sector = getSectorNoGenerate(p2d);
117 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
120 catch(InvalidPositionException &e)
126 f32 Map::getGroundHeight(v2s16 p, bool generate)
129 v2s16 sectorpos = getNodeSectorPos(p);
130 MapSector * sref = getSectorNoGenerate(sectorpos);
131 v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
132 f32 y = sref->getGroundHeight(relpos);
135 catch(InvalidPositionException &e)
137 return GROUNDHEIGHT_NOTFOUND_SETVALUE;
141 void Map::setGroundHeight(v2s16 p, f32 y, bool generate)
143 /*m_dout<<DTIME<<"Map::setGroundHeight(("
145 <<"), "<<y<<")"<<std::endl;*/
146 v2s16 sectorpos = getNodeSectorPos(p);
147 MapSector * sref = getSectorNoGenerate(sectorpos);
148 v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
149 //sref->mutex.Lock();
150 sref->setGroundHeight(relpos, y);
151 //sref->mutex.Unlock();
154 bool Map::isNodeUnderground(v3s16 p)
156 v3s16 blockpos = getNodeBlockPos(p);
158 MapBlock * block = getBlockNoCreate(blockpos);
159 return block->getIsUnderground();
161 catch(InvalidPositionException &e)
168 Goes recursively through the neighbours of the node.
170 Alters only transparent nodes.
172 If the lighting of the neighbour is lower than the lighting of
173 the node was (before changing it to 0 at the step before), the
174 lighting of the neighbour is set to 0 and then the same stuff
175 repeats for the neighbour.
177 The ending nodes of the routine are stored in light_sources.
178 This is useful when a light is removed. In such case, this
179 routine can be called for the light node and then again for
180 light_sources to re-light the area without the removed light.
182 values of from_nodes are lighting values.
184 void Map::unspreadLight(enum LightBank bank,
185 core::map<v3s16, u8> & from_nodes,
186 core::map<v3s16, bool> & light_sources,
187 core::map<v3s16, MapBlock*> & modified_blocks)
190 v3s16(0,0,1), // back
192 v3s16(1,0,0), // right
193 v3s16(0,0,-1), // front
194 v3s16(0,-1,0), // bottom
195 v3s16(-1,0,0), // left
198 if(from_nodes.size() == 0)
201 u32 blockchangecount = 0;
203 core::map<v3s16, u8> unlighted_nodes;
204 core::map<v3s16, u8>::Iterator j;
205 j = from_nodes.getIterator();
208 Initialize block cache
211 MapBlock *block = NULL;
212 // Cache this a bit, too
213 bool block_checked_in_modified = false;
215 for(; j.atEnd() == false; j++)
217 v3s16 pos = j.getNode()->getKey();
218 v3s16 blockpos = getNodeBlockPos(pos);
220 // Only fetch a new block if the block position has changed
222 if(block == NULL || blockpos != blockpos_last){
223 block = getBlockNoCreate(blockpos);
224 blockpos_last = blockpos;
226 block_checked_in_modified = false;
230 catch(InvalidPositionException &e)
238 // Calculate relative position in block
239 v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
241 // Get node straight from the block
242 MapNode n = block->getNode(relpos);
244 u8 oldlight = j.getNode()->getValue();
246 // Loop through 6 neighbors
247 for(u16 i=0; i<6; i++)
249 // Get the position of the neighbor node
250 v3s16 n2pos = pos + dirs[i];
252 // Get the block where the node is located
253 v3s16 blockpos = getNodeBlockPos(n2pos);
257 // Only fetch a new block if the block position has changed
259 if(block == NULL || blockpos != blockpos_last){
260 block = getBlockNoCreate(blockpos);
261 blockpos_last = blockpos;
263 block_checked_in_modified = false;
267 catch(InvalidPositionException &e)
272 // Calculate relative position in block
273 v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
274 // Get node straight from the block
275 MapNode n2 = block->getNode(relpos);
277 bool changed = false;
279 //TODO: Optimize output by optimizing light_sources?
282 If the neighbor is dimmer than what was specified
283 as oldlight (the light of the previous node)
285 if(n2.getLight(bank) < oldlight)
288 And the neighbor is transparent and it has some light
290 if(n2.light_propagates() && n2.getLight(bank) != 0)
293 Set light to 0 and add to queue
296 u8 current_light = n2.getLight(bank);
297 n2.setLight(bank, 0);
298 block->setNode(relpos, n2);
300 unlighted_nodes.insert(n2pos, current_light);
304 Remove from light_sources if it is there
305 NOTE: This doesn't happen nearly at all
307 /*if(light_sources.find(n2pos))
309 std::cout<<"Removed from light_sources"<<std::endl;
310 light_sources.remove(n2pos);
315 if(light_sources.find(n2pos) != NULL)
316 light_sources.remove(n2pos);*/
319 light_sources.insert(n2pos, true);
322 // Add to modified_blocks
323 if(changed == true && block_checked_in_modified == false)
325 // If the block is not found in modified_blocks, add.
326 if(modified_blocks.find(blockpos) == NULL)
328 modified_blocks.insert(blockpos, block);
330 block_checked_in_modified = true;
333 catch(InvalidPositionException &e)
340 /*dstream<<"unspreadLight(): Changed block "
341 <<blockchangecount<<" times"
342 <<" for "<<from_nodes.size()<<" nodes"
345 if(unlighted_nodes.size() > 0)
346 unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
350 A single-node wrapper of the above
352 void Map::unLightNeighbors(enum LightBank bank,
353 v3s16 pos, u8 lightwas,
354 core::map<v3s16, bool> & light_sources,
355 core::map<v3s16, MapBlock*> & modified_blocks)
357 core::map<v3s16, u8> from_nodes;
358 from_nodes.insert(pos, lightwas);
360 unspreadLight(bank, from_nodes, light_sources, modified_blocks);
364 Lights neighbors of from_nodes, collects all them and then
367 void Map::spreadLight(enum LightBank bank,
368 core::map<v3s16, bool> & from_nodes,
369 core::map<v3s16, MapBlock*> & modified_blocks)
371 const v3s16 dirs[6] = {
372 v3s16(0,0,1), // back
374 v3s16(1,0,0), // right
375 v3s16(0,0,-1), // front
376 v3s16(0,-1,0), // bottom
377 v3s16(-1,0,0), // left
380 if(from_nodes.size() == 0)
383 u32 blockchangecount = 0;
385 core::map<v3s16, bool> lighted_nodes;
386 core::map<v3s16, bool>::Iterator j;
387 j = from_nodes.getIterator();
390 Initialize block cache
393 MapBlock *block = NULL;
394 // Cache this a bit, too
395 bool block_checked_in_modified = false;
397 for(; j.atEnd() == false; j++)
398 //for(; j != from_nodes.end(); j++)
400 v3s16 pos = j.getNode()->getKey();
402 //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
403 v3s16 blockpos = getNodeBlockPos(pos);
405 // Only fetch a new block if the block position has changed
407 if(block == NULL || blockpos != blockpos_last){
408 block = getBlockNoCreate(blockpos);
409 blockpos_last = blockpos;
411 block_checked_in_modified = false;
415 catch(InvalidPositionException &e)
423 // Calculate relative position in block
424 v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
426 // Get node straight from the block
427 MapNode n = block->getNode(relpos);
429 u8 oldlight = n.getLight(bank);
430 u8 newlight = diminish_light(oldlight);
432 // Loop through 6 neighbors
433 for(u16 i=0; i<6; i++){
434 // Get the position of the neighbor node
435 v3s16 n2pos = pos + dirs[i];
437 // Get the block where the node is located
438 v3s16 blockpos = getNodeBlockPos(n2pos);
442 // Only fetch a new block if the block position has changed
444 if(block == NULL || blockpos != blockpos_last){
445 block = getBlockNoCreate(blockpos);
446 blockpos_last = blockpos;
448 block_checked_in_modified = false;
452 catch(InvalidPositionException &e)
457 // Calculate relative position in block
458 v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
459 // Get node straight from the block
460 MapNode n2 = block->getNode(relpos);
462 bool changed = false;
464 If the neighbor is brighter than the current node,
465 add to list (it will light up this node on its turn)
467 if(n2.getLight(bank) > undiminish_light(oldlight))
469 lighted_nodes.insert(n2pos, true);
470 //lighted_nodes.push_back(n2pos);
474 If the neighbor is dimmer than how much light this node
475 would spread on it, add to list
477 if(n2.getLight(bank) < newlight)
479 if(n2.light_propagates())
481 n2.setLight(bank, newlight);
482 block->setNode(relpos, n2);
483 lighted_nodes.insert(n2pos, true);
484 //lighted_nodes.push_back(n2pos);
489 // Add to modified_blocks
490 if(changed == true && block_checked_in_modified == false)
492 // If the block is not found in modified_blocks, add.
493 if(modified_blocks.find(blockpos) == NULL)
495 modified_blocks.insert(blockpos, block);
497 block_checked_in_modified = true;
500 catch(InvalidPositionException &e)
507 /*dstream<<"spreadLight(): Changed block "
508 <<blockchangecount<<" times"
509 <<" for "<<from_nodes.size()<<" nodes"
512 if(lighted_nodes.size() > 0)
513 spreadLight(bank, lighted_nodes, modified_blocks);
517 A single-node source variation of the above.
519 void Map::lightNeighbors(enum LightBank bank,
521 core::map<v3s16, MapBlock*> & modified_blocks)
523 core::map<v3s16, bool> from_nodes;
524 from_nodes.insert(pos, true);
525 spreadLight(bank, from_nodes, modified_blocks);
528 v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
531 v3s16(0,0,1), // back
533 v3s16(1,0,0), // right
534 v3s16(0,0,-1), // front
535 v3s16(0,-1,0), // bottom
536 v3s16(-1,0,0), // left
539 u8 brightest_light = 0;
540 v3s16 brightest_pos(0,0,0);
541 bool found_something = false;
543 // Loop through 6 neighbors
544 for(u16 i=0; i<6; i++){
545 // Get the position of the neighbor node
546 v3s16 n2pos = p + dirs[i];
551 catch(InvalidPositionException &e)
555 if(n2.getLight(bank) > brightest_light || found_something == false){
556 brightest_light = n2.getLight(bank);
557 brightest_pos = n2pos;
558 found_something = true;
562 if(found_something == false)
563 throw InvalidPositionException();
565 return brightest_pos;
569 Propagates sunlight down from a node.
570 Starting point gets sunlight.
572 Returns the lowest y value of where the sunlight went.
574 Mud is turned into grass in where the sunlight stops.
576 s16 Map::propagateSunlight(v3s16 start,
577 core::map<v3s16, MapBlock*> & modified_blocks)
582 v3s16 pos(start.X, y, start.Z);
584 v3s16 blockpos = getNodeBlockPos(pos);
587 block = getBlockNoCreate(blockpos);
589 catch(InvalidPositionException &e)
594 v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
595 MapNode n = block->getNode(relpos);
597 if(n.sunlight_propagates())
599 n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
600 block->setNode(relpos, n);
602 modified_blocks.insert(blockpos, block);
606 // Turn mud into grass
607 if(n.d == CONTENT_MUD)
610 block->setNode(relpos, n);
611 modified_blocks.insert(blockpos, block);
614 // Sunlight goes no further
621 void Map::updateLighting(enum LightBank bank,
622 core::map<v3s16, MapBlock*> & a_blocks,
623 core::map<v3s16, MapBlock*> & modified_blocks)
625 /*m_dout<<DTIME<<"Map::updateLighting(): "
626 <<a_blocks.getSize()<<" blocks... ";*/
630 u32 count_was = modified_blocks.size();
632 core::map<v3s16, bool> light_sources;
634 core::map<v3s16, u8> unlight_from;
636 core::map<v3s16, MapBlock*>::Iterator i;
637 i = a_blocks.getIterator();
638 for(; i.atEnd() == false; i++)
640 MapBlock *block = i.getNode()->getValue();
644 // Don't bother with dummy blocks.
648 v3s16 pos = block->getPos();
649 modified_blocks.insert(pos, block);
652 Clear all light from block
654 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
655 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
656 for(s16 y=0; y<MAP_BLOCKSIZE; y++)
661 MapNode n = block->getNode(v3s16(x,y,z));
662 u8 oldlight = n.getLight(bank);
664 block->setNode(v3s16(x,y,z), n);
666 // Collect borders for unlighting
667 if(x==0 || x == MAP_BLOCKSIZE-1
668 || y==0 || y == MAP_BLOCKSIZE-1
669 || z==0 || z == MAP_BLOCKSIZE-1)
671 v3s16 p_map = p + v3s16(
674 MAP_BLOCKSIZE*pos.Z);
675 unlight_from.insert(p_map, oldlight);
678 catch(InvalidPositionException &e)
681 This would happen when dealing with a
685 dstream<<"updateLighting(): InvalidPositionException"
690 if(bank == LIGHTBANK_DAY)
692 bool bottom_valid = block->propagateSunlight(light_sources);
694 // If bottom is valid, we're done.
698 else if(bank == LIGHTBANK_NIGHT)
707 /*dstream<<"Bottom for sunlight-propagated block ("
708 <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
711 // Else get the block below and loop to it
715 block = getBlockNoCreate(pos);
717 catch(InvalidPositionException &e)
726 //TimeTaker timer("unspreadLight");
727 unspreadLight(bank, unlight_from, light_sources, modified_blocks);
732 u32 diff = modified_blocks.size() - count_was;
733 count_was = modified_blocks.size();
734 dstream<<"unspreadLight modified "<<diff<<std::endl;
737 // TODO: Spread light from propagated sunlight?
738 // Yes, add it to light_sources... somehow.
739 // It has to be added at somewhere above, in the loop.
741 // NOTE: This actually works fine without doing so
742 // - Find out why it works
745 //TimeTaker timer("spreadLight");
746 spreadLight(bank, light_sources, modified_blocks);
751 u32 diff = modified_blocks.size() - count_was;
752 count_was = modified_blocks.size();
753 dstream<<"spreadLight modified "<<diff<<std::endl;
756 //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
759 void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
760 core::map<v3s16, MapBlock*> & modified_blocks)
762 updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
763 updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
766 Update information about whether day and night light differ
768 for(core::map<v3s16, MapBlock*>::Iterator
769 i = modified_blocks.getIterator();
770 i.atEnd() == false; i++)
772 MapBlock *block = i.getNode()->getValue();
773 block->updateDayNightDiff();
778 This is called after changing a node from transparent to opaque.
779 The lighting value of the node should be left as-is after changing
780 other values. This sets the lighting value to 0.
782 /*void Map::nodeAddedUpdate(v3s16 p, u8 lightwas,
783 core::map<v3s16, MapBlock*> &modified_blocks)*/
784 void Map::addNodeAndUpdate(v3s16 p, MapNode n,
785 core::map<v3s16, MapBlock*> &modified_blocks)
788 m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
789 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
792 From this node to nodes underneath:
793 If lighting is sunlight (1.0), unlight neighbours and
798 v3s16 toppos = p + v3s16(0,1,0);
799 v3s16 bottompos = p + v3s16(0,-1,0);
801 bool node_under_sunlight = true;
802 core::map<v3s16, bool> light_sources;
805 If there is a node at top and it doesn't have sunlight,
806 there has not been any sunlight going down.
808 Otherwise there probably is.
811 MapNode topnode = getNode(toppos);
813 if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
814 node_under_sunlight = false;
816 catch(InvalidPositionException &e)
820 if(n.d != CONTENT_TORCH)
823 If there is grass below, change it to mud
826 MapNode bottomnode = getNode(bottompos);
828 if(bottomnode.d == CONTENT_GRASS
829 || bottomnode.d == CONTENT_GRASS_FOOTSTEPS)
831 bottomnode.d = CONTENT_MUD;
832 setNode(bottompos, bottomnode);
835 catch(InvalidPositionException &e)
840 enum LightBank banks[] =
845 for(s32 i=0; i<2; i++)
847 enum LightBank bank = banks[i];
849 u8 lightwas = getNode(p).getLight(bank);
851 // Add the block of the added node to modified_blocks
852 v3s16 blockpos = getNodeBlockPos(p);
853 MapBlock * block = getBlockNoCreate(blockpos);
854 assert(block != NULL);
855 modified_blocks.insert(blockpos, block);
857 if(isValidPosition(p) == false)
860 // Unlight neighbours of node.
861 // This means setting light of all consequent dimmer nodes
863 // This also collects the nodes at the border which will spread
864 // light again into this.
865 unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
873 If node is under sunlight, take all sunlighted nodes under
874 it and clear light from them and from where the light has
876 TODO: This could be optimized by mass-unlighting instead
879 if(node_under_sunlight)
883 //m_dout<<DTIME<<"y="<<y<<std::endl;
884 v3s16 n2pos(p.X, y, p.Z);
890 catch(InvalidPositionException &e)
895 if(n2.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
897 //m_dout<<DTIME<<"doing"<<std::endl;
898 unLightNeighbors(LIGHTBANK_DAY,
899 n2pos, n2.getLight(LIGHTBANK_DAY),
900 light_sources, modified_blocks);
901 n2.setLight(LIGHTBANK_DAY, 0);
909 for(s32 i=0; i<2; i++)
911 enum LightBank bank = banks[i];
914 Spread light from all nodes that might be capable of doing so
915 TODO: Convert to spreadLight
917 spreadLight(bank, light_sources, modified_blocks);
921 Update information about whether day and night light differ
923 for(core::map<v3s16, MapBlock*>::Iterator
924 i = modified_blocks.getIterator();
925 i.atEnd() == false; i++)
927 MapBlock *block = i.getNode()->getValue();
928 block->updateDayNightDiff();
934 void Map::removeNodeAndUpdate(v3s16 p,
935 core::map<v3s16, MapBlock*> &modified_blocks)
938 m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
939 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
941 bool node_under_sunlight = true;
943 v3s16 toppos = p + v3s16(0,1,0);
945 // Node will be replaced with this
946 u8 replace_material = CONTENT_AIR;
949 If there is a node at top and it doesn't have sunlight,
950 there will be no sunlight going down.
953 MapNode topnode = getNode(toppos);
955 if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
956 node_under_sunlight = false;
958 catch(InvalidPositionException &e)
962 core::map<v3s16, bool> light_sources;
964 enum LightBank banks[] =
969 for(s32 i=0; i<2; i++)
971 enum LightBank bank = banks[i];
974 Unlight neighbors (in case the node is a light source)
976 unLightNeighbors(bank, p,
977 getNode(p).getLight(bank),
978 light_sources, modified_blocks);
983 This also clears the lighting.
987 n.d = replace_material;
990 for(s32 i=0; i<2; i++)
992 enum LightBank bank = banks[i];
997 spreadLight(bank, light_sources, modified_blocks);
1000 // Add the block of the removed node to modified_blocks
1001 v3s16 blockpos = getNodeBlockPos(p);
1002 MapBlock * block = getBlockNoCreate(blockpos);
1003 assert(block != NULL);
1004 modified_blocks.insert(blockpos, block);
1007 If the removed node was under sunlight, propagate the
1008 sunlight down from it and then light all neighbors
1009 of the propagated blocks.
1011 if(node_under_sunlight)
1013 s16 ybottom = propagateSunlight(p, modified_blocks);
1014 /*m_dout<<DTIME<<"Node was under sunlight. "
1015 "Propagating sunlight";
1016 m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
1018 for(; y >= ybottom; y--)
1020 v3s16 p2(p.X, y, p.Z);
1021 /*m_dout<<DTIME<<"lighting neighbors of node ("
1022 <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
1024 lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
1029 // Set the lighting of this node to 0
1030 // TODO: Is this needed? Lighting is cleared up there already.
1032 MapNode n = getNode(p);
1033 n.setLight(LIGHTBANK_DAY, 0);
1036 catch(InvalidPositionException &e)
1042 for(s32 i=0; i<2; i++)
1044 enum LightBank bank = banks[i];
1046 // Get the brightest neighbour node and propagate light from it
1047 v3s16 n2p = getBrightestNeighbour(bank, p);
1049 MapNode n2 = getNode(n2p);
1050 lightNeighbors(bank, n2p, modified_blocks);
1052 catch(InvalidPositionException &e)
1058 Update information about whether day and night light differ
1060 for(core::map<v3s16, MapBlock*>::Iterator
1061 i = modified_blocks.getIterator();
1062 i.atEnd() == false; i++)
1064 MapBlock *block = i.getNode()->getValue();
1065 block->updateDayNightDiff();
1070 void Map::expireMeshes(bool only_daynight_diffed)
1072 TimeTaker timer("expireMeshes()");
1074 core::map<v2s16, MapSector*>::Iterator si;
1075 si = m_sectors.getIterator();
1076 for(; si.atEnd() == false; si++)
1078 MapSector *sector = si.getNode()->getValue();
1080 core::list< MapBlock * > sectorblocks;
1081 sector->getBlocks(sectorblocks);
1083 core::list< MapBlock * >::Iterator i;
1084 for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
1086 MapBlock *block = *i;
1088 if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false)
1094 JMutexAutoLock lock(block->mesh_mutex);
1095 if(block->mesh != NULL)
1097 /*block->mesh->drop();
1098 block->mesh = NULL;*/
1099 block->setMeshExpired(true);
1106 void Map::updateMeshes(v3s16 blockpos, u32 daynight_ratio)
1108 assert(mapType() == MAPTYPE_CLIENT);
1111 v3s16 p = blockpos + v3s16(0,0,0);
1112 MapBlock *b = getBlockNoCreate(p);
1113 b->updateMesh(daynight_ratio);
1115 catch(InvalidPositionException &e){}
1118 v3s16 p = blockpos + v3s16(-1,0,0);
1119 MapBlock *b = getBlockNoCreate(p);
1120 b->updateMesh(daynight_ratio);
1122 catch(InvalidPositionException &e){}
1124 v3s16 p = blockpos + v3s16(0,-1,0);
1125 MapBlock *b = getBlockNoCreate(p);
1126 b->updateMesh(daynight_ratio);
1128 catch(InvalidPositionException &e){}
1130 v3s16 p = blockpos + v3s16(0,0,-1);
1131 MapBlock *b = getBlockNoCreate(p);
1132 b->updateMesh(daynight_ratio);
1134 catch(InvalidPositionException &e){}
1137 v3s16 p = blockpos + v3s16(1,0,0);
1138 MapBlock *b = getBlockNoCreate(p);
1139 b->updateMesh(daynight_ratio);
1141 catch(InvalidPositionException &e){}
1143 v3s16 p = blockpos + v3s16(0,1,0);
1144 MapBlock *b = getBlockNoCreate(p);
1145 b->updateMesh(daynight_ratio);
1147 catch(InvalidPositionException &e){}
1149 v3s16 p = blockpos + v3s16(0,0,1);
1150 MapBlock *b = getBlockNoCreate(p);
1151 b->updateMesh(daynight_ratio);
1153 catch(InvalidPositionException &e){}*/
1158 bool Map::dayNightDiffed(v3s16 blockpos)
1161 v3s16 p = blockpos + v3s16(0,0,0);
1162 MapBlock *b = getBlockNoCreate(p);
1163 if(b->dayNightDiffed())
1166 catch(InvalidPositionException &e){}
1169 v3s16 p = blockpos + v3s16(-1,0,0);
1170 MapBlock *b = getBlockNoCreate(p);
1171 if(b->dayNightDiffed())
1174 catch(InvalidPositionException &e){}
1176 v3s16 p = blockpos + v3s16(0,-1,0);
1177 MapBlock *b = getBlockNoCreate(p);
1178 if(b->dayNightDiffed())
1181 catch(InvalidPositionException &e){}
1183 v3s16 p = blockpos + v3s16(0,0,-1);
1184 MapBlock *b = getBlockNoCreate(p);
1185 if(b->dayNightDiffed())
1188 catch(InvalidPositionException &e){}
1191 v3s16 p = blockpos + v3s16(1,0,0);
1192 MapBlock *b = getBlockNoCreate(p);
1193 if(b->dayNightDiffed())
1196 catch(InvalidPositionException &e){}
1198 v3s16 p = blockpos + v3s16(0,1,0);
1199 MapBlock *b = getBlockNoCreate(p);
1200 if(b->dayNightDiffed())
1203 catch(InvalidPositionException &e){}
1205 v3s16 p = blockpos + v3s16(0,0,1);
1206 MapBlock *b = getBlockNoCreate(p);
1207 if(b->dayNightDiffed())
1210 catch(InvalidPositionException &e){}
1216 Updates usage timers
1218 void Map::timerUpdate(float dtime)
1220 JMutexAutoLock lock(m_sector_mutex);
1222 core::map<v2s16, MapSector*>::Iterator si;
1224 si = m_sectors.getIterator();
1225 for(; si.atEnd() == false; si++)
1227 MapSector *sector = si.getNode()->getValue();
1228 sector->usage_timer += dtime;
1232 void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
1235 Wait for caches to be removed before continuing.
1237 This disables the existence of caches while locked
1239 SharedPtr<JMutexAutoLock> cachelock(m_blockcachelock.waitCaches());
1241 core::list<v2s16>::Iterator j;
1242 for(j=list.begin(); j!=list.end(); j++)
1244 MapSector *sector = m_sectors[*j];
1247 sector->deleteBlocks();
1252 If sector is in sector cache, remove it from there
1254 if(m_sector_cache == sector)
1256 m_sector_cache = NULL;
1259 Remove from map and delete
1261 m_sectors.remove(*j);
1267 u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
1268 core::list<v3s16> *deleted_blocks)
1270 JMutexAutoLock lock(m_sector_mutex);
1272 core::list<v2s16> sector_deletion_queue;
1273 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
1274 for(; i.atEnd() == false; i++)
1276 MapSector *sector = i.getNode()->getValue();
1278 Delete sector from memory if it hasn't been used in a long time
1280 if(sector->usage_timer > timeout)
1282 sector_deletion_queue.push_back(i.getNode()->getKey());
1284 if(deleted_blocks != NULL)
1286 // Collect positions of blocks of sector
1287 MapSector *sector = i.getNode()->getValue();
1288 core::list<MapBlock*> blocks;
1289 sector->getBlocks(blocks);
1290 for(core::list<MapBlock*>::Iterator i = blocks.begin();
1291 i != blocks.end(); i++)
1293 deleted_blocks->push_back((*i)->getPos());
1298 deleteSectors(sector_deletion_queue, only_blocks);
1299 return sector_deletion_queue.getSize();
1302 void Map::PrintInfo(std::ostream &out)
1311 ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
1316 Experimental and debug stuff
1320 dstream<<"Generating map point attribute lists"<<std::endl;
1322 PointAttributeList *list_baseheight = m_padb.getList("hm_baseheight");
1323 PointAttributeList *list_randmax = m_padb.getList("hm_randmax");
1324 PointAttributeList *list_randfactor = m_padb.getList("hm_randfactor");
1325 PointAttributeList *list_plants_amount = m_padb.getList("plants_amount");
1326 PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
1329 NOTE: BEWARE: Too big amount of these will make map generation
1330 slow. Especially those that are read by every block emerge.
1333 for(u32 i=0; i<15000; i++)
1335 /*u32 lim = MAP_GENERATION_LIMIT;
1339 u32 lim = 1000 + MAP_GENERATION_LIMIT * i / 15000;
1342 -lim + myrand()%(lim*2),
1344 -lim + myrand()%(lim*2)
1346 /*float plants_amount = (float)(myrand()%1050) / 1000.0;
1347 plants_amount = pow(plants_amount, 5);
1348 list_plants_amount->addPoint(p, Attribute(plants_amount));*/
1350 float plants_amount = 0;
1353 plants_amount = 1.5;
1355 else if(myrand()%4 == 0)
1357 plants_amount = 0.5;
1359 else if(myrand()%2 == 0)
1361 plants_amount = 0.03;
1365 plants_amount = 0.0;
1369 list_plants_amount->addPoint(p, Attribute(plants_amount));
1372 for(u32 i=0; i<1000; i++)
1374 /*u32 lim = MAP_GENERATION_LIMIT;
1378 u32 lim = 500 + MAP_GENERATION_LIMIT * i / 1000;
1381 -lim + myrand()%(lim*2),
1383 -lim + myrand()%(lim*2)
1386 float caves_amount = 0;
1391 else if(myrand()%3 == 0)
1397 caves_amount = 0.05;
1400 list_caves_amount->addPoint(p, Attribute(caves_amount));
1403 for(u32 i=0; i<5000; i++)
1405 /*u32 lim = MAP_GENERATION_LIMIT;
1409 u32 lim = 1000 + MAP_GENERATION_LIMIT * i / 5000;
1412 -lim + (myrand()%(lim*2)),
1414 -lim + (myrand()%(lim*2))
1417 /*s32 bh_i = (myrand()%200) - 50;
1418 float baseheight = (float)bh_i;
1422 float randmax = (float)(myrand()%(int)(10.*pow(m, 1./e)))/10.;
1423 randmax = pow(randmax, e);
1425 //float randmax = (float)(myrand()%60);
1426 float randfactor = (float)(myrand()%450) / 1000.0 + 0.4;*/
1428 float baseheight = 0;
1430 float randfactor = 0;
1438 else if(myrand()%6 == 0)
1444 else if(myrand()%4 == 0)
1450 else if(myrand()%3 == 0)
1463 list_baseheight->addPoint(p, Attribute(baseheight));
1464 list_randmax->addPoint(p, Attribute(randmax));
1465 list_randfactor->addPoint(p, Attribute(randfactor));
1468 /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(5));
1469 list_randmax->addPoint(v3s16(0,0,0), Attribute(20));
1470 list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.6));*/
1473 /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(0));
1474 list_randmax->addPoint(v3s16(0,0,0), Attribute(10));
1475 list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.65));*/
1479 Try to load map; if not found, create a new one.
1482 m_savedir = savedir;
1483 m_map_saving_enabled = false;
1487 // If directory exists, check contents and load if possible
1488 if(fs::PathExists(m_savedir))
1490 // If directory is empty, it is safe to save into it.
1491 if(fs::GetDirListing(m_savedir).size() == 0)
1493 dstream<<DTIME<<"Server: Empty save directory is valid."
1495 m_map_saving_enabled = true;
1499 // Load master heightmap
1500 loadMasterHeightmap();
1502 // Load sector (0,0) and throw and exception on fail
1503 if(loadSectorFull(v2s16(0,0)) == false)
1504 throw LoadError("Failed to load sector (0,0)");
1506 dstream<<DTIME<<"Server: Successfully loaded master "
1507 "heightmap and sector (0,0) from "<<savedir<<
1508 ", assuming valid save directory."
1511 m_map_saving_enabled = true;
1512 // Map loaded, not creating new one
1516 // If directory doesn't exist, it is safe to save to it
1518 m_map_saving_enabled = true;
1521 catch(std::exception &e)
1523 dstream<<DTIME<<"Server: Failed to load map from "<<savedir
1524 <<", exception: "<<e.what()<<std::endl;
1525 dstream<<DTIME<<"Please remove the map or fix it."<<std::endl;
1526 dstream<<DTIME<<"WARNING: Map saving will be disabled."<<std::endl;
1529 dstream<<DTIME<<"Initializing new map."<<std::endl;
1531 // Create master heightmap
1532 /*ValueGenerator *maxgen =
1533 ValueGenerator::deSerialize(hmp.randmax);
1534 ValueGenerator *factorgen =
1535 ValueGenerator::deSerialize(hmp.randfactor);
1536 ValueGenerator *basegen =
1537 ValueGenerator::deSerialize(hmp.base);
1538 m_heightmap = new UnlimitedHeightmap
1539 (hmp.blocksize, maxgen, factorgen, basegen, &m_padb);*/
1541 /*m_heightmap = new UnlimitedHeightmap
1542 (hmp.blocksize, &m_padb);*/
1544 m_heightmap = new UnlimitedHeightmap
1547 // Set map parameters
1550 // Create zero sector
1551 emergeSector(v2s16(0,0));
1553 // Initially write whole map
1557 ServerMap::~ServerMap()
1561 if(m_map_saving_enabled)
1564 // Save only changed parts
1566 dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
1570 dstream<<DTIME<<"Server: map not saved"<<std::endl;
1573 catch(std::exception &e)
1575 dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
1576 <<", exception: "<<e.what()<<std::endl;
1579 if(m_heightmap != NULL)
1583 MapSector * ServerMap::emergeSector(v2s16 p2d)
1585 DSTACK("%s: p2d=(%d,%d)",
1588 // Check that it doesn't exist already
1590 return getSectorNoGenerate(p2d);
1592 catch(InvalidPositionException &e)
1597 Try to load the sector from disk.
1599 if(loadSectorFull(p2d) == true)
1601 return getSectorNoGenerate(p2d);
1605 If there is no master heightmap, throw.
1607 if(m_heightmap == NULL)
1609 throw InvalidPositionException("emergeSector(): no heightmap");
1613 Do not generate over-limit
1615 if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
1616 || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
1617 || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
1618 || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
1619 throw InvalidPositionException("emergeSector(): pos. over limit");
1622 Generate sector and heightmaps
1625 // Number of heightmaps in sector in each direction
1626 u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
1628 // Heightmap side width
1629 s16 hm_d = MAP_BLOCKSIZE / hm_split;
1631 ServerMapSector *sector = new ServerMapSector(this, p2d, hm_split);
1633 // Sector position on map in nodes
1634 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
1636 /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
1637 " heightmaps and objects"<<std::endl;*/
1640 Calculate some information about local properties
1643 v2s16 mhm_p = p2d * hm_split;
1645 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
1646 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
1647 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
1648 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
1651 float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0;
1652 float avgslope = 0.0;
1653 avgslope += fabs(avgheight - corners[0]);
1654 avgslope += fabs(avgheight - corners[1]);
1655 avgslope += fabs(avgheight - corners[2]);
1656 avgslope += fabs(avgheight - corners[3]);
1658 avgslope /= MAP_BLOCKSIZE;
1659 //dstream<<"avgslope="<<avgslope<<std::endl;
1661 float pitness = 0.0;
1663 a = m_heightmap->getSlope(p2d+v2s16(0,0));
1666 a = m_heightmap->getSlope(p2d+v2s16(0,1));
1669 a = m_heightmap->getSlope(p2d+v2s16(1,1));
1672 a = m_heightmap->getSlope(p2d+v2s16(1,0));
1676 pitness /= MAP_BLOCKSIZE;
1677 //dstream<<"pitness="<<pitness<<std::endl;
1680 Get local attributes
1683 //dstream<<"emergeSector(): Reading point attribute lists"<<std::endl;
1685 // Get plant amount from attributes
1686 PointAttributeList *palist = m_padb.getList("plants_amount");
1688 /*float local_plants_amount =
1689 palist->getNearAttr(nodepos2d).getFloat();*/
1690 float local_plants_amount =
1691 palist->getInterpolatedFloat(nodepos2d);
1693 //dstream<<"emergeSector(): done."<<std::endl;
1696 Generate sector heightmap
1699 // Loop through sub-heightmaps
1700 for(s16 y=0; y<hm_split; y++)
1701 for(s16 x=0; x<hm_split; x++)
1703 v2s16 p_in_sector = v2s16(x,y);
1704 v2s16 mhm_p = p2d * hm_split + p_in_sector;
1706 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
1707 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
1708 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
1709 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
1712 /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
1713 <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
1716 FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
1718 sector->setHeightmap(p_in_sector, hm);
1720 //TODO: Make these values configurable
1721 //hm->generateContinued(0.0, 0.0, corners);
1722 //hm->generateContinued(0.25, 0.2, corners);
1723 //hm->generateContinued(0.5, 0.2, corners);
1724 //hm->generateContinued(1.0, 0.2, corners);
1725 //hm->generateContinued(2.0, 0.2, corners);
1726 hm->generateContinued(2.0 * avgslope, 0.5, corners);
1735 core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
1736 sector->setObjects(objects);
1739 Plant some trees if there is not much slope
1742 // Avgslope is the derivative of a hill
1743 //float t = avgslope * avgslope;
1745 float a = MAP_BLOCKSIZE * m_params.plants_amount * local_plants_amount;
1747 //float something = 0.17*0.17;
1748 float something = 0.3;
1750 tree_max = a / (t/something);
1754 u32 count = (myrand()%(tree_max+1));
1755 //u32 count = tree_max;
1756 for(u32 i=0; i<count; i++)
1758 s16 x = (myrand()%(MAP_BLOCKSIZE-2))+1;
1759 s16 z = (myrand()%(MAP_BLOCKSIZE-2))+1;
1760 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
1763 objects->insert(v3s16(x, y, z),
1764 SECTOR_OBJECT_TREE_1);
1768 Plant some bushes if sector is pit-like
1771 // Pitness usually goes at around -0.5...0.5
1773 u32 a = MAP_BLOCKSIZE * 3.0 * m_params.plants_amount * local_plants_amount;
1775 bush_max = (pitness*a*4);
1778 u32 count = (myrand()%(bush_max+1));
1779 for(u32 i=0; i<count; i++)
1781 s16 x = myrand()%(MAP_BLOCKSIZE-0)+0;
1782 s16 z = myrand()%(MAP_BLOCKSIZE-0)+0;
1783 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
1786 objects->insert(v3s16(x, y, z),
1787 SECTOR_OBJECT_BUSH_1);
1791 Add ravine (randomly)
1793 if(m_params.ravines_amount > 0.001)
1795 if(myrand()%(s32)(200.0 / m_params.ravines_amount) == 0)
1798 s16 x = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
1799 s16 z = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
1802 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
1803 objects->insert(v3s16(x, y, z),
1804 SECTOR_OBJECT_RAVINE);
1811 JMutexAutoLock lock(m_sector_mutex);
1812 m_sectors.insert(p2d, sector);
1817 MapBlock * ServerMap::emergeBlock(
1819 bool only_from_disk,
1820 core::map<v3s16, MapBlock*> &changed_blocks,
1821 core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
1824 DSTACK("%s: p=(%d,%d,%d), only_from_disk=%d",
1826 p.X, p.Y, p.Z, only_from_disk);
1828 /*dstream<<"ServerMap::emergeBlock(): "
1829 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1830 <<", only_from_disk="<<only_from_disk<<std::endl;*/
1831 v2s16 p2d(p.X, p.Z);
1834 This will create or load a sector if not found in memory.
1835 If block exists on disk, it will be loaded.
1837 NOTE: On old save formats, this will be slow, as it generates
1838 lighting on blocks for them.
1840 ServerMapSector *sector = (ServerMapSector*)emergeSector(p2d);
1841 assert(sector->getId() == MAPSECTOR_SERVER);
1843 // Try to get a block from the sector
1844 MapBlock *block = NULL;
1845 bool not_on_disk = false;
1847 block = sector->getBlockNoCreate(block_y);
1848 if(block->isDummy() == true)
1853 catch(InvalidPositionException &e)
1859 If block was not found on disk and not going to generate a
1860 new one, make sure there is a dummy block in place.
1862 if(not_on_disk && only_from_disk)
1866 // Create dummy block
1867 block = new MapBlock(this, p, true);
1869 // Add block to sector
1870 sector->insertBlock(block);
1876 //dstream<<"Not found on disk, generating."<<std::endl;
1877 //TimeTaker("emergeBlock()", g_irrlicht);
1880 Do not generate over-limit
1882 if(blockpos_over_limit(p))
1883 throw InvalidPositionException("emergeBlock(): pos. over limit");
1888 Go on generating the block.
1890 TODO: If a dungeon gets generated so that it's side gets
1891 revealed to the outside air, the lighting should be
1896 If block doesn't exist, create one.
1897 If it exists, it is a dummy. In that case unDummify() it.
1899 NOTE: This already sets the map as the parent of the block
1903 block = sector->createBlankBlockNoInsert(block_y);
1907 // Remove the block so that nobody can get a half-generated one.
1908 sector->removeBlock(block);
1909 // Allocate the block to contain the generated data
1913 u8 water_material = CONTENT_WATER;
1914 if(g_settings.getBool("endless_water"))
1915 water_material = CONTENT_OCEAN;
1917 s32 lowest_ground_y = 32767;
1918 s32 highest_ground_y = -32768;
1921 //sector->printHeightmaps();
1923 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1924 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1926 //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
1928 float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
1929 //assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
1930 if(surface_y_f < GROUNDHEIGHT_VALID_MINVALUE)
1932 dstream<<"WARNING: Surface height not found in sector "
1933 "for block that is being emerged"<<std::endl;
1937 s16 surface_y = surface_y_f;
1938 //avg_ground_y += surface_y;
1939 if(surface_y < lowest_ground_y)
1940 lowest_ground_y = surface_y;
1941 if(surface_y > highest_ground_y)
1942 highest_ground_y = surface_y;
1944 s32 surface_depth = 0;
1946 float slope = sector->getSlope(v2s16(x0,z0)).getLength();
1948 //float min_slope = 0.45;
1949 //float max_slope = 0.85;
1950 float min_slope = 0.60;
1951 float max_slope = 1.20;
1952 float min_slope_depth = 5.0;
1953 float max_slope_depth = 0;
1955 if(slope < min_slope)
1956 surface_depth = min_slope_depth;
1957 else if(slope > max_slope)
1958 surface_depth = max_slope_depth;
1960 surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
1962 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1964 s16 real_y = block_y * MAP_BLOCKSIZE + y0;
1969 NOTE: If there are some man-made structures above the
1970 newly created block, they won't be taken into account.
1972 if(real_y > surface_y)
1973 n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
1979 // If node is over heightmap y, it's air or water
1980 if(real_y > surface_y)
1982 // If under water level, it's water
1983 if(real_y < WATER_LEVEL)
1985 n.d = water_material;
1986 n.setLight(LIGHTBANK_DAY,
1987 diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
1993 // Else it's ground or dungeons (air)
1996 // If it's surface_depth under ground, it's stone
1997 if(real_y <= surface_y - surface_depth)
1999 n.d = CONTENT_STONE;
2003 // It is mud if it is under the first ground
2004 // level or under water
2005 if(real_y < WATER_LEVEL || real_y <= surface_y - 1)
2011 n.d = CONTENT_GRASS;
2014 //n.d = CONTENT_MUD;
2016 /*// If under water level, it's mud
2017 if(real_y < WATER_LEVEL)
2019 // Only the topmost node is grass
2020 else if(real_y <= surface_y - 1)
2023 n.d = CONTENT_GRASS;*/
2027 block->setNode(v3s16(x0,y0,z0), n);
2032 Calculate some helper variables
2035 // Completely underground if the highest part of block is under lowest
2037 // This has to be very sure; it's probably one too strict now but
2038 // that's just better.
2039 bool completely_underground =
2040 block_y * MAP_BLOCKSIZE + MAP_BLOCKSIZE < lowest_ground_y;
2042 bool some_part_underground = block_y * MAP_BLOCKSIZE <= highest_ground_y;
2044 bool mostly_underwater_surface = false;
2045 if(highest_ground_y < WATER_LEVEL
2046 && some_part_underground && !completely_underground)
2047 mostly_underwater_surface = true;
2050 Get local attributes
2053 //dstream<<"emergeBlock(): Getting local attributes"<<std::endl;
2055 float caves_amount = 0;
2059 NOTE: BEWARE: Too big amount of attribute points slows verything
2061 1 interpolation from 5000 points takes 2-3ms.
2063 //TimeTaker timer("emergeBlock() local attribute retrieval");
2064 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
2065 PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
2066 caves_amount = list_caves_amount->getInterpolatedFloat(nodepos2d);
2069 //dstream<<"emergeBlock(): Done"<<std::endl;
2075 // Initialize temporary table
2076 const s32 ued = MAP_BLOCKSIZE;
2077 bool underground_emptiness[ued*ued*ued];
2078 for(s32 i=0; i<ued*ued*ued; i++)
2080 underground_emptiness[i] = 0;
2087 Initialize orp and ors. Try to find if some neighboring
2088 MapBlock has a tunnel ended in its side
2092 (float)(myrand()%ued)+0.5,
2093 (float)(myrand()%ued)+0.5,
2094 (float)(myrand()%ued)+0.5
2097 bool found_existing = false;
2103 for(s16 y=0; y<ued; y++)
2104 for(s16 x=0; x<ued; x++)
2106 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2107 if(getNode(ap).d == CONTENT_AIR)
2109 orp = v3f(x+1,y+1,0);
2110 found_existing = true;
2111 goto continue_generating;
2115 catch(InvalidPositionException &e){}
2121 for(s16 y=0; y<ued; y++)
2122 for(s16 x=0; x<ued; x++)
2124 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2125 if(getNode(ap).d == CONTENT_AIR)
2127 orp = v3f(x+1,y+1,ued-1);
2128 found_existing = true;
2129 goto continue_generating;
2133 catch(InvalidPositionException &e){}
2139 for(s16 y=0; y<ued; y++)
2140 for(s16 z=0; z<ued; z++)
2142 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2143 if(getNode(ap).d == CONTENT_AIR)
2145 orp = v3f(0,y+1,z+1);
2146 found_existing = true;
2147 goto continue_generating;
2151 catch(InvalidPositionException &e){}
2157 for(s16 y=0; y<ued; y++)
2158 for(s16 z=0; z<ued; z++)
2160 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2161 if(getNode(ap).d == CONTENT_AIR)
2163 orp = v3f(ued-1,y+1,z+1);
2164 found_existing = true;
2165 goto continue_generating;
2169 catch(InvalidPositionException &e){}
2175 for(s16 x=0; x<ued; x++)
2176 for(s16 z=0; z<ued; z++)
2178 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2179 if(getNode(ap).d == CONTENT_AIR)
2181 orp = v3f(x+1,0,z+1);
2182 found_existing = true;
2183 goto continue_generating;
2187 catch(InvalidPositionException &e){}
2193 for(s16 x=0; x<ued; x++)
2194 for(s16 z=0; z<ued; z++)
2196 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
2197 if(getNode(ap).d == CONTENT_AIR)
2199 orp = v3f(x+1,ued-1,z+1);
2200 found_existing = true;
2201 goto continue_generating;
2205 catch(InvalidPositionException &e){}
2207 continue_generating:
2210 Choose whether to actually generate dungeon
2212 bool do_generate_dungeons = true;
2213 // Don't generate if no part is underground
2214 if(!some_part_underground)
2216 do_generate_dungeons = false;
2218 // Don't generate if mostly underwater surface
2219 else if(mostly_underwater_surface)
2221 do_generate_dungeons = false;
2223 // Partly underground = cave
2224 else if(!completely_underground)
2226 do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
2228 // Found existing dungeon underground
2229 else if(found_existing && completely_underground)
2231 do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
2233 // Underground and no dungeons found
2236 do_generate_dungeons = (rand() % 300 <= (s32)(caves_amount*100));
2239 if(do_generate_dungeons)
2242 Generate some tunnel starting from orp and ors
2244 for(u16 i=0; i<3; i++)
2247 (float)(myrand()%ued)+0.5,
2248 (float)(myrand()%ued)+0.5,
2249 (float)(myrand()%ued)+0.5
2253 s16 rs = (myrand()%(max_d-min_d+1))+min_d;
2257 for(float f=0; f<1.0; f+=0.04)
2259 v3f fp = orp + vec * f;
2260 v3s16 cp(fp.X, fp.Y, fp.Z);
2262 s16 d1 = d0 + rs - 1;
2263 for(s16 z0=d0; z0<=d1; z0++)
2265 s16 si = rs - abs(z0);
2266 for(s16 x0=-si; x0<=si-1; x0++)
2268 s16 si2 = rs - abs(x0);
2269 for(s16 y0=-si2+1; y0<=si2-1; y0++)
2275 if(isInArea(p, ued) == false)
2277 underground_emptiness[ued*ued*z + ued*y + x] = 1;
2289 // Set to true if has caves.
2290 // Set when some non-air is changed to air when making caves.
2291 bool has_dungeons = false;
2294 Apply temporary cave data to block
2297 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
2298 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
2300 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
2302 MapNode n = block->getNode(v3s16(x0,y0,z0));
2305 if(underground_emptiness[
2306 ued*ued*(z0*ued/MAP_BLOCKSIZE)
2307 +ued*(y0*ued/MAP_BLOCKSIZE)
2308 +(x0*ued/MAP_BLOCKSIZE)])
2310 if(is_ground_content(n.d))
2313 has_dungeons = true;
2319 block->setNode(v3s16(x0,y0,z0), n);
2324 This is used for guessing whether or not the block should
2325 receive sunlight from the top if the top block doesn't exist
2327 block->setIsUnderground(completely_underground);
2330 Force lighting update if some part of block is partly
2331 underground and has caves.
2333 /*if(some_part_underground && !completely_underground && has_dungeons)
2335 //dstream<<"Half-ground caves"<<std::endl;
2336 lighting_invalidated_blocks[block->getPos()] = block;
2339 // DEBUG: Always update lighting
2340 //lighting_invalidated_blocks[block->getPos()] = block;
2346 if(some_part_underground)
2348 s16 underground_level = (lowest_ground_y/MAP_BLOCKSIZE - block_y)+1;
2353 for(s16 i=0; i<underground_level/4 + 1; i++)
2355 if(myrand()%50 == 0)
2358 (myrand()%(MAP_BLOCKSIZE-2))+1,
2359 (myrand()%(MAP_BLOCKSIZE-2))+1,
2360 (myrand()%(MAP_BLOCKSIZE-2))+1
2366 //if(is_ground_content(block->getNode(cp).d))
2367 if(block->getNode(cp).d == CONTENT_STONE)
2369 block->setNode(cp, n);
2371 for(u16 i=0; i<26; i++)
2373 //if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
2374 if(block->getNode(cp+g_26dirs[i]).d == CONTENT_STONE)
2376 block->setNode(cp+g_26dirs[i], n);
2384 u16 coal_amount = 30.0 * g_settings.getFloat("coal_amount");
2385 u16 coal_rareness = 60 / coal_amount;
2386 if(coal_rareness == 0)
2388 if(myrand()%coal_rareness == 0)
2390 u16 a = myrand() % 16;
2391 u16 amount = coal_amount * a*a*a / 1000;
2392 for(s16 i=0; i<amount; i++)
2395 (myrand()%(MAP_BLOCKSIZE-2))+1,
2396 (myrand()%(MAP_BLOCKSIZE-2))+1,
2397 (myrand()%(MAP_BLOCKSIZE-2))+1
2401 n.d = CONTENT_COALSTONE;
2403 //dstream<<"Adding coalstone"<<std::endl;
2405 //if(is_ground_content(block->getNode(cp).d))
2406 if(block->getNode(cp).d == CONTENT_STONE)
2408 block->setNode(cp, n);
2410 for(u16 i=0; i<26; i++)
2412 //if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
2413 if(block->getNode(cp+g_26dirs[i]).d == CONTENT_STONE)
2415 block->setNode(cp+g_26dirs[i], n);
2422 Create a few rats in empty blocks underground
2424 if(completely_underground)
2426 //for(u16 i=0; i<2; i++)
2429 (myrand()%(MAP_BLOCKSIZE-2))+1,
2430 (myrand()%(MAP_BLOCKSIZE-2))+1,
2431 (myrand()%(MAP_BLOCKSIZE-2))+1
2434 // Check that the place is empty
2435 //if(!is_ground_content(block->getNode(cp).d))
2438 RatObject *obj = new RatObject(NULL, -1, intToFloat(cp));
2439 block->addObject(obj);
2445 Add block to sector.
2447 sector->insertBlock(block);
2453 // An y-wise container of changed blocks
2454 core::map<s16, MapBlock*> changed_blocks_sector;
2457 Check if any sector's objects can be placed now.
2460 core::map<v3s16, u8> *objects = sector->getObjects();
2461 core::list<v3s16> objects_to_remove;
2462 for(core::map<v3s16, u8>::Iterator i = objects->getIterator();
2463 i.atEnd() == false; i++)
2465 v3s16 p = i.getNode()->getKey();
2467 u8 d = i.getNode()->getValue();
2469 // Ground level point (user for stuff that is on ground)
2471 bool ground_found = true;
2473 // Search real ground level
2477 MapNode n = sector->getNode(gp);
2479 // If not air, go one up and continue to placing the tree
2480 if(n.d != CONTENT_AIR)
2486 // If air, go one down
2487 gp += v3s16(0,-1,0);
2489 }catch(InvalidPositionException &e)
2491 // Ground not found.
2492 ground_found = false;
2493 // This is most close to ground
2500 if(d == SECTOR_OBJECT_TEST)
2502 if(sector->isValidArea(p + v3s16(0,0,0),
2503 p + v3s16(0,0,0), &changed_blocks_sector))
2506 n.d = CONTENT_TORCH;
2507 sector->setNode(p, n);
2508 objects_to_remove.push_back(p);
2511 else if(d == SECTOR_OBJECT_TREE_1)
2513 if(ground_found == false)
2516 v3s16 p_min = gp + v3s16(-1,0,-1);
2517 v3s16 p_max = gp + v3s16(1,5,1);
2518 if(sector->isValidArea(p_min, p_max,
2519 &changed_blocks_sector))
2523 sector->setNode(gp+v3s16(0,0,0), n);
2524 sector->setNode(gp+v3s16(0,1,0), n);
2525 sector->setNode(gp+v3s16(0,2,0), n);
2526 sector->setNode(gp+v3s16(0,3,0), n);
2528 n.d = CONTENT_LEAVES;
2530 if(rand()%4!=0) sector->setNode(gp+v3s16(0,5,0), n);
2532 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,0), n);
2533 if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,0), n);
2534 if(rand()%3!=0) sector->setNode(gp+v3s16(0,5,-1), n);
2535 if(rand()%3!=0) sector->setNode(gp+v3s16(0,5,1), n);
2536 /*if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,1), n);
2537 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,1), n);
2538 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,-1), n);
2539 if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,-1), n);*/
2541 sector->setNode(gp+v3s16(0,4,0), n);
2543 sector->setNode(gp+v3s16(-1,4,0), n);
2544 sector->setNode(gp+v3s16(1,4,0), n);
2545 sector->setNode(gp+v3s16(0,4,-1), n);
2546 sector->setNode(gp+v3s16(0,4,1), n);
2547 sector->setNode(gp+v3s16(1,4,1), n);
2548 sector->setNode(gp+v3s16(-1,4,1), n);
2549 sector->setNode(gp+v3s16(-1,4,-1), n);
2550 sector->setNode(gp+v3s16(1,4,-1), n);
2552 sector->setNode(gp+v3s16(-1,3,0), n);
2553 sector->setNode(gp+v3s16(1,3,0), n);
2554 sector->setNode(gp+v3s16(0,3,-1), n);
2555 sector->setNode(gp+v3s16(0,3,1), n);
2556 sector->setNode(gp+v3s16(1,3,1), n);
2557 sector->setNode(gp+v3s16(-1,3,1), n);
2558 sector->setNode(gp+v3s16(-1,3,-1), n);
2559 sector->setNode(gp+v3s16(1,3,-1), n);
2561 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,0), n);
2562 if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,0), n);
2563 if(rand()%3!=0) sector->setNode(gp+v3s16(0,2,-1), n);
2564 if(rand()%3!=0) sector->setNode(gp+v3s16(0,2,1), n);
2565 /*if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,1), n);
2566 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,1), n);
2567 if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,-1), n);
2568 if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,-1), n);*/
2570 // Objects are identified by wanted position
2571 objects_to_remove.push_back(p);
2573 // Lighting has to be recalculated for this one.
2574 sector->getBlocksInArea(p_min, p_max,
2575 lighting_invalidated_blocks);
2578 else if(d == SECTOR_OBJECT_BUSH_1)
2580 if(ground_found == false)
2583 if(sector->isValidArea(gp + v3s16(0,0,0),
2584 gp + v3s16(0,0,0), &changed_blocks_sector))
2587 n.d = CONTENT_LEAVES;
2588 sector->setNode(gp+v3s16(0,0,0), n);
2590 // Objects are identified by wanted position
2591 objects_to_remove.push_back(p);
2594 else if(d == SECTOR_OBJECT_RAVINE)
2597 v3s16 p_min = p + v3s16(-6,maxdepth,-6);
2598 v3s16 p_max = p + v3s16(6,6,6);
2599 if(sector->isValidArea(p_min, p_max,
2600 &changed_blocks_sector))
2603 n.d = CONTENT_STONE;
2606 s16 depth = maxdepth + (myrand()%10);
2608 s16 minz = -6 - (-2);
2610 for(s16 x=-6; x<=6; x++)
2612 z += -1 + (myrand()%3);
2617 for(s16 y=depth+(myrand()%2); y<=6; y++)
2619 /*std::cout<<"("<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
2622 v3s16 p2 = p + v3s16(x,y,z-2);
2623 if(is_ground_content(sector->getNode(p2).d)
2624 && !is_mineral(sector->getNode(p2).d))
2625 sector->setNode(p2, n);
2628 v3s16 p2 = p + v3s16(x,y,z-1);
2629 if(is_ground_content(sector->getNode(p2).d)
2630 && !is_mineral(sector->getNode(p2).d))
2631 sector->setNode(p2, n2);
2634 v3s16 p2 = p + v3s16(x,y,z+0);
2635 if(is_ground_content(sector->getNode(p2).d)
2636 && !is_mineral(sector->getNode(p2).d))
2637 sector->setNode(p2, n2);
2640 v3s16 p2 = p + v3s16(x,y,z+1);
2641 if(is_ground_content(sector->getNode(p2).d)
2642 && !is_mineral(sector->getNode(p2).d))
2643 sector->setNode(p2, n);
2646 //if(sector->getNode(p+v3s16(x,y,z+1)).solidness()==2)
2647 //if(p.Y+y <= sector->getGroundHeight(p2d+v2s16(x,z-2))+0.5)
2651 objects_to_remove.push_back(p);
2653 // Lighting has to be recalculated for this one.
2654 sector->getBlocksInArea(p_min, p_max,
2655 lighting_invalidated_blocks);
2660 dstream<<"ServerMap::emergeBlock(): "
2661 "Invalid heightmap object"
2666 catch(InvalidPositionException &e)
2668 dstream<<"WARNING: "<<__FUNCTION_NAME
2669 <<": while inserting object "<<(int)d
2670 <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<"):"
2671 <<" InvalidPositionException.what()="
2672 <<e.what()<<std::endl;
2673 // This is not too fatal and seems to happen sometimes.
2678 for(core::list<v3s16>::Iterator i = objects_to_remove.begin();
2679 i != objects_to_remove.end(); i++)
2681 objects->remove(*i);
2685 Initially update sunlight
2689 core::map<v3s16, bool> light_sources;
2690 bool black_air_left = false;
2691 bool bottom_invalid =
2692 block->propagateSunlight(light_sources, true, &black_air_left);
2694 // If sunlight didn't reach everywhere and part of block is
2695 // above ground, lighting has to be properly updated
2696 if(black_air_left && some_part_underground)
2698 lighting_invalidated_blocks[block->getPos()] = block;
2703 Translate sector's changed blocks to global changed blocks
2706 for(core::map<s16, MapBlock*>::Iterator
2707 i = changed_blocks_sector.getIterator();
2708 i.atEnd() == false; i++)
2710 MapBlock *block = i.getNode()->getValue();
2712 changed_blocks.insert(block->getPos(), block);
2721 <<"lighting_invalidated_blocks.size()"
2725 <<" "<<lighting_invalidated_blocks.size()
2726 <<", "<<has_dungeons
2727 <<", "<<completely_underground
2728 <<", "<<some_part_underground
2733 Debug mode operation
2737 // Don't calculate lighting at all
2738 lighting_invalidated_blocks.clear();
2744 void ServerMap::createDir(std::string path)
2746 if(fs::CreateDir(path) == false)
2748 m_dout<<DTIME<<"ServerMap: Failed to create directory "
2749 <<"\""<<path<<"\""<<std::endl;
2750 throw BaseException("ServerMap failed to create directory");
2754 std::string ServerMap::getSectorSubDir(v2s16 pos)
2757 snprintf(cc, 9, "%.4x%.4x",
2758 (unsigned int)pos.X&0xffff,
2759 (unsigned int)pos.Y&0xffff);
2761 return std::string(cc);
2764 std::string ServerMap::getSectorDir(v2s16 pos)
2766 return m_savedir + "/sectors/" + getSectorSubDir(pos);
2769 v2s16 ServerMap::getSectorPos(std::string dirname)
2771 if(dirname.size() != 8)
2772 throw InvalidFilenameException("Invalid sector directory name");
2774 int r = sscanf(dirname.c_str(), "%4x%4x", &x, &y);
2776 throw InvalidFilenameException("Invalid sector directory name");
2777 v2s16 pos((s16)x, (s16)y);
2781 v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
2783 v2s16 p2d = getSectorPos(sectordir);
2785 if(blockfile.size() != 4){
2786 throw InvalidFilenameException("Invalid block filename");
2789 int r = sscanf(blockfile.c_str(), "%4x", &y);
2791 throw InvalidFilenameException("Invalid block filename");
2792 return v3s16(p2d.X, y, p2d.Y);
2796 #define ENABLE_SECTOR_SAVING 1
2797 #define ENABLE_SECTOR_LOADING 1
2798 #define ENABLE_BLOCK_SAVING 1
2799 #define ENABLE_BLOCK_LOADING 1
2801 void ServerMap::save(bool only_changed)
2803 DSTACK(__FUNCTION_NAME);
2804 if(m_map_saving_enabled == false)
2806 dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
2810 if(only_changed == false)
2811 dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
2814 saveMasterHeightmap();
2816 u32 sector_meta_count = 0;
2817 u32 block_count = 0;
2820 JMutexAutoLock lock(m_sector_mutex);
2822 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
2823 for(; i.atEnd() == false; i++)
2825 ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue();
2826 assert(sector->getId() == MAPSECTOR_SERVER);
2828 if(ENABLE_SECTOR_SAVING)
2830 if(sector->differs_from_disk || only_changed == false)
2832 saveSectorMeta(sector);
2833 sector_meta_count++;
2836 if(ENABLE_BLOCK_SAVING)
2838 core::list<MapBlock*> blocks;
2839 sector->getBlocks(blocks);
2840 core::list<MapBlock*>::Iterator j;
2841 for(j=blocks.begin(); j!=blocks.end(); j++)
2843 MapBlock *block = *j;
2844 if(block->getChangedFlag() || only_changed == false)
2856 Only print if something happened or saved whole map
2858 if(only_changed == false || sector_meta_count != 0
2859 || block_count != 0)
2861 dstream<<DTIME<<"ServerMap: Written: "
2862 <<sector_meta_count<<" sector metadata files, "
2863 <<block_count<<" block files"
2868 void ServerMap::loadAll()
2870 DSTACK(__FUNCTION_NAME);
2871 dstream<<DTIME<<"ServerMap: Loading map..."<<std::endl;
2873 loadMasterHeightmap();
2875 std::vector<fs::DirListNode> list = fs::GetDirListing(m_savedir+"/sectors/");
2877 dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
2879 JMutexAutoLock lock(m_sector_mutex);
2882 s32 printed_counter = -100000;
2883 s32 count = list.size();
2885 std::vector<fs::DirListNode>::iterator i;
2886 for(i=list.begin(); i!=list.end(); i++)
2888 if(counter > printed_counter + 10)
2890 dstream<<DTIME<<counter<<"/"<<count<<std::endl;
2891 printed_counter = counter;
2895 MapSector *sector = NULL;
2897 // We want directories
2901 sector = loadSectorMeta(i->name);
2903 catch(InvalidFilenameException &e)
2905 // This catches unknown crap in directory
2908 if(ENABLE_BLOCK_LOADING)
2910 std::vector<fs::DirListNode> list2 = fs::GetDirListing
2911 (m_savedir+"/sectors/"+i->name);
2912 std::vector<fs::DirListNode>::iterator i2;
2913 for(i2=list2.begin(); i2!=list2.end(); i2++)
2919 loadBlock(i->name, i2->name, sector);
2921 catch(InvalidFilenameException &e)
2923 // This catches unknown crap in directory
2928 dstream<<DTIME<<"ServerMap: Map loaded."<<std::endl;
2931 void ServerMap::saveMasterHeightmap()
2933 DSTACK(__FUNCTION_NAME);
2934 createDir(m_savedir);
2936 std::string fullpath = m_savedir + "/master_heightmap";
2937 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
2938 if(o.good() == false)
2939 throw FileNotGoodException("Cannot open master heightmap");
2941 // Format used for writing
2942 u8 version = SER_FMT_VER_HIGHEST;
2945 SharedBuffer<u8> hmdata = m_heightmap->serialize(version);
2947 [0] u8 serialization version
2948 [1] X master heightmap
2950 u32 fullsize = 1 + hmdata.getSize();
2951 SharedBuffer<u8> data(fullsize);
2954 memcpy(&data[1], *hmdata, hmdata.getSize());
2956 o.write((const char*)*data, fullsize);
2959 m_heightmap->serialize(o, version);
2962 void ServerMap::loadMasterHeightmap()
2964 DSTACK(__FUNCTION_NAME);
2965 std::string fullpath = m_savedir + "/master_heightmap";
2966 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
2967 if(is.good() == false)
2968 throw FileNotGoodException("Cannot open master heightmap");
2970 if(m_heightmap != NULL)
2973 m_heightmap = UnlimitedHeightmap::deSerialize(is, &m_padb);
2976 void ServerMap::saveSectorMeta(ServerMapSector *sector)
2978 DSTACK(__FUNCTION_NAME);
2979 // Format used for writing
2980 u8 version = SER_FMT_VER_HIGHEST;
2982 v2s16 pos = sector->getPos();
2983 createDir(m_savedir);
2984 createDir(m_savedir+"/sectors");
2985 std::string dir = getSectorDir(pos);
2988 std::string fullpath = dir + "/heightmap";
2989 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
2990 if(o.good() == false)
2991 throw FileNotGoodException("Cannot open master heightmap");
2993 sector->serialize(o, version);
2995 sector->differs_from_disk = false;
2998 MapSector* ServerMap::loadSectorMeta(std::string dirname)
3000 DSTACK(__FUNCTION_NAME);
3002 v2s16 p2d = getSectorPos(dirname);
3003 std::string dir = m_savedir + "/sectors/" + dirname;
3005 std::string fullpath = dir + "/heightmap";
3006 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3007 if(is.good() == false)
3008 throw FileNotGoodException("Cannot open sector heightmap");
3010 ServerMapSector *sector = ServerMapSector::deSerialize
3011 (is, this, p2d, &m_hwrapper, m_sectors);
3013 sector->differs_from_disk = false;
3018 bool ServerMap::loadSectorFull(v2s16 p2d)
3020 DSTACK(__FUNCTION_NAME);
3021 std::string sectorsubdir = getSectorSubDir(p2d);
3023 MapSector *sector = NULL;
3025 JMutexAutoLock lock(m_sector_mutex);
3028 sector = loadSectorMeta(sectorsubdir);
3030 catch(InvalidFilenameException &e)
3034 catch(FileNotGoodException &e)
3038 catch(std::exception &e)
3043 if(ENABLE_BLOCK_LOADING)
3045 std::vector<fs::DirListNode> list2 = fs::GetDirListing
3046 (m_savedir+"/sectors/"+sectorsubdir);
3047 std::vector<fs::DirListNode>::iterator i2;
3048 for(i2=list2.begin(); i2!=list2.end(); i2++)
3054 loadBlock(sectorsubdir, i2->name, sector);
3056 catch(InvalidFilenameException &e)
3058 // This catches unknown crap in directory
3066 bool ServerMap::deFlushSector(v2s16 p2d)
3068 DSTACK(__FUNCTION_NAME);
3069 // See if it already exists in memory
3071 MapSector *sector = getSectorNoGenerate(p2d);
3074 catch(InvalidPositionException &e)
3077 Try to load the sector from disk.
3079 if(loadSectorFull(p2d) == true)
3088 void ServerMap::saveBlock(MapBlock *block)
3090 DSTACK(__FUNCTION_NAME);
3092 Dummy blocks are not written
3094 if(block->isDummy())
3096 /*v3s16 p = block->getPos();
3097 dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
3098 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
3102 // Format used for writing
3103 u8 version = SER_FMT_VER_HIGHEST;
3105 v3s16 p3d = block->getPos();
3106 v2s16 p2d(p3d.X, p3d.Z);
3107 createDir(m_savedir);
3108 createDir(m_savedir+"/sectors");
3109 std::string dir = getSectorDir(p2d);
3112 // Block file is map/sectors/xxxxxxxx/xxxx
3114 snprintf(cc, 5, "%.4x", (unsigned int)p3d.Y&0xffff);
3115 std::string fullpath = dir + "/" + cc;
3116 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
3117 if(o.good() == false)
3118 throw FileNotGoodException("Cannot open block data");
3121 [0] u8 serialization version
3124 o.write((char*)&version, 1);
3126 block->serialize(o, version);
3129 Versions up from 9 have block objects.
3133 block->serializeObjects(o, version);
3136 // We just wrote it to the disk
3137 block->resetChangedFlag();
3140 void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
3142 DSTACK(__FUNCTION_NAME);
3146 // Block file is map/sectors/xxxxxxxx/xxxx
3147 std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
3148 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
3149 if(is.good() == false)
3150 throw FileNotGoodException("Cannot open block file");
3152 v3s16 p3d = getBlockPos(sectordir, blockfile);
3153 v2s16 p2d(p3d.X, p3d.Z);
3155 assert(sector->getPos() == p2d);
3157 u8 version = SER_FMT_VER_INVALID;
3158 is.read((char*)&version, 1);
3160 /*u32 block_size = MapBlock::serializedLength(version);
3161 SharedBuffer<u8> data(block_size);
3162 is.read((char*)*data, block_size);*/
3164 // This will always return a sector because we're the server
3165 //MapSector *sector = emergeSector(p2d);
3167 MapBlock *block = NULL;
3168 bool created_new = false;
3170 block = sector->getBlockNoCreate(p3d.Y);
3172 catch(InvalidPositionException &e)
3174 block = sector->createBlankBlockNoInsert(p3d.Y);
3178 // deserialize block data
3179 block->deSerialize(is, version);
3182 Versions up from 9 have block objects.
3186 block->updateObjects(is, version, NULL, 0);
3190 sector->insertBlock(block);
3193 Convert old formats to new and save
3196 // Save old format blocks in new format
3197 if(version < SER_FMT_VER_HIGHEST)
3202 // We just loaded it from the disk, so it's up-to-date.
3203 block->resetChangedFlag();
3206 catch(SerializationError &e)
3208 dstream<<"WARNING: Invalid block data on disk "
3209 "(SerializationError). Ignoring."
3214 // Gets from master heightmap
3215 void ServerMap::getSectorCorners(v2s16 p2d, s16 *corners)
3217 assert(m_heightmap != NULL);
3225 corners[0] = m_heightmap->getGroundHeight
3226 ((p2d+v2s16(0,0))*SECTOR_HEIGHTMAP_SPLIT);
3227 corners[1] = m_heightmap->getGroundHeight
3228 ((p2d+v2s16(1,0))*SECTOR_HEIGHTMAP_SPLIT);
3229 corners[2] = m_heightmap->getGroundHeight
3230 ((p2d+v2s16(1,1))*SECTOR_HEIGHTMAP_SPLIT);
3231 corners[3] = m_heightmap->getGroundHeight
3232 ((p2d+v2s16(0,1))*SECTOR_HEIGHTMAP_SPLIT);
3235 void ServerMap::PrintInfo(std::ostream &out)
3246 ClientMap::ClientMap(
3248 MapDrawControl &control,
3249 scene::ISceneNode* parent,
3250 scene::ISceneManager* mgr,
3254 scene::ISceneNode(parent, mgr, id),
3261 /*m_box = core::aabbox3d<f32>(0,0,0,
3262 map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
3263 /*m_box = core::aabbox3d<f32>(0,0,0,
3264 map->getSizeNodes().X * BS,
3265 map->getSizeNodes().Y * BS,
3266 map->getSizeNodes().Z * BS);*/
3267 m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
3268 BS*1000000,BS*1000000,BS*1000000);
3270 //setPosition(v3f(BS,BS,BS));
3273 ClientMap::~ClientMap()
3275 JMutexAutoLock lock(mesh_mutex);
3284 MapSector * ClientMap::emergeSector(v2s16 p2d)
3286 DSTACK(__FUNCTION_NAME);
3287 // Check that it doesn't exist already
3289 return getSectorNoGenerate(p2d);
3291 catch(InvalidPositionException &e)
3295 // Create a sector with no heightmaps
3296 ClientMapSector *sector = new ClientMapSector(this, p2d);
3299 JMutexAutoLock lock(m_sector_mutex);
3300 m_sectors.insert(p2d, sector);
3306 void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
3308 DSTACK(__FUNCTION_NAME);
3309 ClientMapSector *sector = NULL;
3311 JMutexAutoLock lock(m_sector_mutex);
3313 core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
3317 sector = (ClientMapSector*)n->getValue();
3318 assert(sector->getId() == MAPSECTOR_CLIENT);
3322 sector = new ClientMapSector(this, p2d);
3324 JMutexAutoLock lock(m_sector_mutex);
3325 m_sectors.insert(p2d, sector);
3329 sector->deSerialize(is);
3332 void ClientMap::OnRegisterSceneNode()
3336 SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
3337 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
3340 ISceneNode::OnRegisterSceneNode();
3343 void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
3345 //m_dout<<DTIME<<"Rendering map..."<<std::endl;
3346 DSTACK(__FUNCTION_NAME);
3348 bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
3351 Get time for measuring timeout.
3353 Measuring time is very useful for long delays when the
3354 machine is swapping a lot.
3356 int time1 = time(0);
3358 u32 daynight_ratio = m_client->getDayNightRatio();
3360 m_camera_mutex.Lock();
3361 v3f camera_position = m_camera_position;
3362 v3f camera_direction = m_camera_direction;
3363 m_camera_mutex.Unlock();
3366 Get all blocks and draw all visible ones
3369 v3s16 cam_pos_nodes(
3370 camera_position.X / BS,
3371 camera_position.Y / BS,
3372 camera_position.Z / BS);
3374 v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
3376 v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
3377 v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
3379 // Take a fair amount as we will be dropping more out later
3381 p_nodes_min.X / MAP_BLOCKSIZE - 1,
3382 p_nodes_min.Y / MAP_BLOCKSIZE - 1,
3383 p_nodes_min.Z / MAP_BLOCKSIZE - 1);
3385 p_nodes_max.X / MAP_BLOCKSIZE + 1,
3386 p_nodes_max.Y / MAP_BLOCKSIZE + 1,
3387 p_nodes_max.Z / MAP_BLOCKSIZE + 1);
3389 u32 vertex_count = 0;
3391 // For limiting number of mesh updates per frame
3392 u32 mesh_update_count = 0;
3394 u32 blocks_would_have_drawn = 0;
3395 u32 blocks_drawn = 0;
3397 //NOTE: The sectors map should be locked but we're not doing it
3398 // because it'd cause too much delays
3400 int timecheck_counter = 0;
3401 core::map<v2s16, MapSector*>::Iterator si;
3402 si = m_sectors.getIterator();
3403 for(; si.atEnd() == false; si++)
3406 timecheck_counter++;
3407 if(timecheck_counter > 50)
3409 int time2 = time(0);
3410 if(time2 > time1 + 4)
3412 dstream<<"ClientMap::renderMap(): "
3413 "Rendering takes ages, returning."
3420 MapSector *sector = si.getNode()->getValue();
3421 v2s16 sp = sector->getPos();
3423 if(m_control.range_all == false)
3425 if(sp.X < p_blocks_min.X
3426 || sp.X > p_blocks_max.X
3427 || sp.Y < p_blocks_min.Z
3428 || sp.Y > p_blocks_max.Z)
3432 core::list< MapBlock * > sectorblocks;
3433 sector->getBlocks(sectorblocks);
3439 core::list< MapBlock * >::Iterator i;
3440 for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
3442 MapBlock *block = *i;
3445 Compare block position to camera position, skip
3446 if not seen on display
3449 float range = 100000 * BS;
3450 if(m_control.range_all == false)
3451 range = m_control.wanted_range * BS;
3453 if(isBlockInSight(block->getPos(), camera_position,
3454 camera_direction, range) == false)
3460 v3s16 blockpos_nodes = block->getPosRelative();
3462 // Block center position
3464 ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
3465 ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
3466 ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
3469 // Block position relative to camera
3470 v3f blockpos_relative = blockpos - camera_position;
3472 // Distance in camera direction (+=front, -=back)
3473 f32 dforward = blockpos_relative.dotProduct(camera_direction);
3476 f32 d = blockpos_relative.getLength();
3478 if(m_control.range_all == false)
3480 // If block is far away, don't draw it
3481 if(d > m_control.wanted_range * BS)
3485 // Maximum radius of a block
3486 f32 block_max_radius = 0.5*1.44*1.44*MAP_BLOCKSIZE*BS;
3488 // If block is (nearly) touching the camera, don't
3489 // bother validating further (that is, render it anyway)
3490 if(d > block_max_radius * 1.5)
3492 // Cosine of the angle between the camera direction
3493 // and the block direction (camera_direction is an unit vector)
3494 f32 cosangle = dforward / d;
3496 // Compensate for the size of the block
3497 // (as the block has to be shown even if it's a bit off FOV)
3498 // This is an estimate.
3499 cosangle += block_max_radius / dforward;
3501 // If block is not in the field of view, skip it
3502 //if(cosangle < cos(FOV_ANGLE/2))
3503 if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
3508 v3s16 blockpos_nodes = block->getPosRelative();
3510 // Block center position
3512 ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
3513 ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
3514 ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
3517 // Block position relative to camera
3518 v3f blockpos_relative = blockpos - camera_position;
3521 f32 d = blockpos_relative.getLength();
3524 Draw the faces of the block
3527 bool mesh_expired = false;
3530 JMutexAutoLock lock(block->mesh_mutex);
3532 mesh_expired = block->getMeshExpired();
3534 // Mesh has not been expired and there is no mesh:
3535 // block has no content
3536 if(block->mesh == NULL && mesh_expired == false)
3540 f32 faraway = BS*50;
3541 //f32 faraway = m_control.wanted_range * BS;
3544 This has to be done with the mesh_mutex unlocked
3546 // Pretty random but this should work somewhat nicely
3547 if(mesh_expired && (
3548 (mesh_update_count < 3
3549 && (d < faraway || mesh_update_count < 2)
3552 (m_control.range_all && mesh_update_count < 20)
3555 /*if(mesh_expired && mesh_update_count < 6
3556 && (d < faraway || mesh_update_count < 3))*/
3558 mesh_update_count++;
3560 // Mesh has been expired: generate new mesh
3561 //block->updateMeshes(daynight_i);
3562 block->updateMesh(daynight_ratio);
3564 mesh_expired = false;
3568 Don't draw an expired mesh that is far away
3570 /*if(mesh_expired && d >= faraway)
3573 // Instead, delete it
3574 JMutexAutoLock lock(block->mesh_mutex);
3577 block->mesh->drop();
3580 // And continue to next block
3585 JMutexAutoLock lock(block->mesh_mutex);
3587 scene::SMesh *mesh = block->mesh;
3592 blocks_would_have_drawn++;
3593 if(blocks_drawn >= m_control.wanted_max_blocks
3594 && m_control.range_all == false
3595 && d > m_control.wanted_min_range * BS)
3599 u32 c = mesh->getMeshBufferCount();
3601 for(u32 i=0; i<c; i++)
3603 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
3604 const video::SMaterial& material = buf->getMaterial();
3605 video::IMaterialRenderer* rnd =
3606 driver->getMaterialRenderer(material.MaterialType);
3607 bool transparent = (rnd && rnd->isTransparent());
3608 // Render transparent on transparent pass and likewise.
3609 if(transparent == is_transparent_pass)
3611 driver->setMaterial(buf->getMaterial());
3612 driver->drawMeshBuffer(buf);
3613 vertex_count += buf->getVertexCount();
3617 } // foreach sectorblocks
3620 m_control.blocks_drawn = blocks_drawn;
3621 m_control.blocks_would_have_drawn = blocks_would_have_drawn;
3623 /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
3624 <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
3627 v3s16 ClientMap::setTempMod(v3s16 p, NodeMod mod)
3630 Add it to all blocks touching it
3633 v3s16(0,0,0), // this
3634 v3s16(0,0,1), // back
3635 v3s16(0,1,0), // top
3636 v3s16(1,0,0), // right
3637 v3s16(0,0,-1), // front
3638 v3s16(0,-1,0), // bottom
3639 v3s16(-1,0,0), // left
3641 for(u16 i=0; i<7; i++)
3643 v3s16 p2 = p + dirs[i];
3644 // Block position of neighbor (or requested) node
3645 v3s16 blockpos = getNodeBlockPos(p2);
3646 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
3647 if(blockref == NULL)
3649 // Relative position of requested node
3650 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
3651 blockref->setTempMod(relpos, mod);
3653 return getNodeBlockPos(p);
3655 v3s16 ClientMap::clearTempMod(v3s16 p)
3658 v3s16(0,0,0), // this
3659 v3s16(0,0,1), // back
3660 v3s16(0,1,0), // top
3661 v3s16(1,0,0), // right
3662 v3s16(0,0,-1), // front
3663 v3s16(0,-1,0), // bottom
3664 v3s16(-1,0,0), // left
3666 for(u16 i=0; i<7; i++)
3668 v3s16 p2 = p + dirs[i];
3669 // Block position of neighbor (or requested) node
3670 v3s16 blockpos = getNodeBlockPos(p2);
3671 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
3672 if(blockref == NULL)
3674 // Relative position of requested node
3675 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
3676 blockref->clearTempMod(relpos);
3678 return getNodeBlockPos(p);
3681 void ClientMap::PrintInfo(std::ostream &out)
3692 MapVoxelManipulator::MapVoxelManipulator(Map *map)
3697 MapVoxelManipulator::~MapVoxelManipulator()
3699 /*dstream<<"MapVoxelManipulator: blocks: "<<m_loaded_blocks.size()
3704 void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id)
3706 TimeTaker timer1("emerge", &emerge_time);
3708 // Units of these are MapBlocks
3709 v3s16 p_min = getNodeBlockPos(a.MinEdge);
3710 v3s16 p_max = getNodeBlockPos(a.MaxEdge);
3712 VoxelArea block_area_nodes
3713 (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
3715 addArea(block_area_nodes);
3717 for(s32 z=p_min.Z; z<=p_max.Z; z++)
3718 for(s32 y=p_min.Y; y<=p_max.Y; y++)
3719 for(s32 x=p_min.X; x<=p_max.X; x++)
3722 core::map<v3s16, bool>::Node *n;
3723 n = m_loaded_blocks.find(p);
3727 bool block_data_inexistent = false;
3730 TimeTaker timer1("emerge load", &emerge_load_time);
3732 /*dstream<<"Loading block (caller_id="<<caller_id<<")"
3733 <<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
3736 dstream<<std::endl;*/
3738 MapBlock *block = m_map->getBlockNoCreate(p);
3739 if(block->isDummy())
3740 block_data_inexistent = true;
3742 block->copyTo(*this);
3744 catch(InvalidPositionException &e)
3746 block_data_inexistent = true;
3749 if(block_data_inexistent)
3751 VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
3752 // Fill with VOXELFLAG_INEXISTENT
3753 for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
3754 for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
3756 s32 i = m_area.index(a.MinEdge.X,y,z);
3757 memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE);
3761 m_loaded_blocks.insert(p, true);
3764 //dstream<<"emerge done"<<std::endl;
3769 void MapVoxelManipulator::emerge(VoxelArea a)
3771 TimeTaker timer1("emerge", &emerge_time);
3773 v3s16 size = a.getExtent();
3775 VoxelArea padded = a;
3776 padded.pad(m_area.getExtent() / 4);
3779 for(s16 z=0; z<size.Z; z++)
3780 for(s16 y=0; y<size.Y; y++)
3781 for(s16 x=0; x<size.X; x++)
3784 s32 i = m_area.index(a.MinEdge + p);
3785 // Don't touch nodes that have already been loaded
3786 if(!(m_flags[i] & VOXELFLAG_NOT_LOADED))
3790 TimeTaker timer1("emerge load", &emerge_load_time);
3791 MapNode n = m_map->getNode(a.MinEdge + p);
3795 catch(InvalidPositionException &e)
3797 m_flags[i] = VOXELFLAG_INEXISTENT;
3805 TODO: Add an option to only update eg. water and air nodes.
3806 This will make it interfere less with important stuff if
3809 void MapVoxelManipulator::blitBack
3810 (core::map<v3s16, MapBlock*> & modified_blocks)
3812 if(m_area.getExtent() == v3s16(0,0,0))
3815 //TimeTaker timer1("blitBack");
3818 Initialize block cache
3820 v3s16 blockpos_last;
3821 MapBlock *block = NULL;
3822 bool block_checked_in_modified = false;
3824 for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
3825 for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
3826 for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
3830 u8 f = m_flags[m_area.index(p)];
3831 if(f & (VOXELFLAG_NOT_LOADED|VOXELFLAG_INEXISTENT))
3834 MapNode &n = m_data[m_area.index(p)];
3836 v3s16 blockpos = getNodeBlockPos(p);
3841 if(block == NULL || blockpos != blockpos_last){
3842 block = m_map->getBlockNoCreate(blockpos);
3843 blockpos_last = blockpos;
3844 block_checked_in_modified = false;
3847 // Calculate relative position in block
3848 v3s16 relpos = p - blockpos * MAP_BLOCKSIZE;
3850 // Don't continue if nothing has changed here
3851 if(block->getNode(relpos) == n)
3854 //m_map->setNode(m_area.MinEdge + p, n);
3855 block->setNode(relpos, n);
3858 Make sure block is in modified_blocks
3860 if(block_checked_in_modified == false)
3862 modified_blocks[blockpos] = block;
3863 block_checked_in_modified = true;
3866 catch(InvalidPositionException &e)