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"
34 Map::Map(std::ostream &dout):
36 m_camera_position(0,0,0),
37 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::getSectorNoGenerateNoExNoLock(v2s16 p)
72 if(m_sector_cache != NULL && p == m_sector_cache_p){
73 MapSector * sector = m_sector_cache;
74 // Reset inactivity timer
75 sector->usage_timer = 0.0;
79 core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
84 MapSector *sector = n->getValue();
86 // Cache the last result
88 m_sector_cache = sector;
90 // Reset inactivity timer
91 sector->usage_timer = 0.0;
95 MapSector * Map::getSectorNoGenerateNoEx(v2s16 p)
97 JMutexAutoLock lock(m_sector_mutex);
99 return getSectorNoGenerateNoExNoLock(p);
102 MapSector * Map::getSectorNoGenerate(v2s16 p)
104 MapSector *sector = getSectorNoGenerateNoEx(p);
106 throw InvalidPositionException();
111 MapBlock * Map::getBlockNoCreate(v3s16 p3d)
113 v2s16 p2d(p3d.X, p3d.Z);
114 MapSector * sector = getSectorNoGenerate(p2d);
116 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
121 MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
125 v2s16 p2d(p3d.X, p3d.Z);
126 MapSector * sector = getSectorNoGenerate(p2d);
127 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
130 catch(InvalidPositionException &e)
136 /*MapBlock * Map::getBlockCreate(v3s16 p3d)
138 v2s16 p2d(p3d.X, p3d.Z);
139 MapSector * sector = getSectorCreate(p2d);
141 MapBlock *block = sector->getBlockNoCreate(p3d.Y);
144 block = sector->createBlankBlock(p3d.Y);
148 f32 Map::getGroundHeight(v2s16 p, bool generate)
151 v2s16 sectorpos = getNodeSectorPos(p);
152 MapSector * sref = getSectorNoGenerate(sectorpos);
153 v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
154 f32 y = sref->getGroundHeight(relpos);
157 catch(InvalidPositionException &e)
159 return GROUNDHEIGHT_NOTFOUND_SETVALUE;
163 void Map::setGroundHeight(v2s16 p, f32 y, bool generate)
165 /*m_dout<<DTIME<<"Map::setGroundHeight(("
167 <<"), "<<y<<")"<<std::endl;*/
168 v2s16 sectorpos = getNodeSectorPos(p);
169 MapSector * sref = getSectorNoGenerate(sectorpos);
170 v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
171 //sref->mutex.Lock();
172 sref->setGroundHeight(relpos, y);
173 //sref->mutex.Unlock();
176 bool Map::isNodeUnderground(v3s16 p)
178 v3s16 blockpos = getNodeBlockPos(p);
180 MapBlock * block = getBlockNoCreate(blockpos);
181 return block->getIsUnderground();
183 catch(InvalidPositionException &e)
190 Goes recursively through the neighbours of the node.
192 Alters only transparent nodes.
194 If the lighting of the neighbour is lower than the lighting of
195 the node was (before changing it to 0 at the step before), the
196 lighting of the neighbour is set to 0 and then the same stuff
197 repeats for the neighbour.
199 The ending nodes of the routine are stored in light_sources.
200 This is useful when a light is removed. In such case, this
201 routine can be called for the light node and then again for
202 light_sources to re-light the area without the removed light.
204 values of from_nodes are lighting values.
206 void Map::unspreadLight(enum LightBank bank,
207 core::map<v3s16, u8> & from_nodes,
208 core::map<v3s16, bool> & light_sources,
209 core::map<v3s16, MapBlock*> & modified_blocks)
212 v3s16(0,0,1), // back
214 v3s16(1,0,0), // right
215 v3s16(0,0,-1), // front
216 v3s16(0,-1,0), // bottom
217 v3s16(-1,0,0), // left
220 if(from_nodes.size() == 0)
223 u32 blockchangecount = 0;
225 core::map<v3s16, u8> unlighted_nodes;
226 core::map<v3s16, u8>::Iterator j;
227 j = from_nodes.getIterator();
230 Initialize block cache
233 MapBlock *block = NULL;
234 // Cache this a bit, too
235 bool block_checked_in_modified = false;
237 for(; j.atEnd() == false; j++)
239 v3s16 pos = j.getNode()->getKey();
240 v3s16 blockpos = getNodeBlockPos(pos);
242 // Only fetch a new block if the block position has changed
244 if(block == NULL || blockpos != blockpos_last){
245 block = getBlockNoCreate(blockpos);
246 blockpos_last = blockpos;
248 block_checked_in_modified = false;
252 catch(InvalidPositionException &e)
260 // Calculate relative position in block
261 v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
263 // Get node straight from the block
264 MapNode n = block->getNode(relpos);
266 u8 oldlight = j.getNode()->getValue();
268 // Loop through 6 neighbors
269 for(u16 i=0; i<6; i++)
271 // Get the position of the neighbor node
272 v3s16 n2pos = pos + dirs[i];
274 // Get the block where the node is located
275 v3s16 blockpos = getNodeBlockPos(n2pos);
279 // Only fetch a new block if the block position has changed
281 if(block == NULL || blockpos != blockpos_last){
282 block = getBlockNoCreate(blockpos);
283 blockpos_last = blockpos;
285 block_checked_in_modified = false;
289 catch(InvalidPositionException &e)
294 // Calculate relative position in block
295 v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
296 // Get node straight from the block
297 MapNode n2 = block->getNode(relpos);
299 bool changed = false;
301 //TODO: Optimize output by optimizing light_sources?
304 If the neighbor is dimmer than what was specified
305 as oldlight (the light of the previous node)
307 if(n2.getLight(bank) < oldlight)
310 And the neighbor is transparent and it has some light
312 if(n2.light_propagates() && n2.getLight(bank) != 0)
315 Set light to 0 and add to queue
318 u8 current_light = n2.getLight(bank);
319 n2.setLight(bank, 0);
320 block->setNode(relpos, n2);
322 unlighted_nodes.insert(n2pos, current_light);
326 Remove from light_sources if it is there
327 NOTE: This doesn't happen nearly at all
329 /*if(light_sources.find(n2pos))
331 std::cout<<"Removed from light_sources"<<std::endl;
332 light_sources.remove(n2pos);
337 if(light_sources.find(n2pos) != NULL)
338 light_sources.remove(n2pos);*/
341 light_sources.insert(n2pos, true);
344 // Add to modified_blocks
345 if(changed == true && block_checked_in_modified == false)
347 // If the block is not found in modified_blocks, add.
348 if(modified_blocks.find(blockpos) == NULL)
350 modified_blocks.insert(blockpos, block);
352 block_checked_in_modified = true;
355 catch(InvalidPositionException &e)
362 /*dstream<<"unspreadLight(): Changed block "
363 <<blockchangecount<<" times"
364 <<" for "<<from_nodes.size()<<" nodes"
367 if(unlighted_nodes.size() > 0)
368 unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
372 A single-node wrapper of the above
374 void Map::unLightNeighbors(enum LightBank bank,
375 v3s16 pos, u8 lightwas,
376 core::map<v3s16, bool> & light_sources,
377 core::map<v3s16, MapBlock*> & modified_blocks)
379 core::map<v3s16, u8> from_nodes;
380 from_nodes.insert(pos, lightwas);
382 unspreadLight(bank, from_nodes, light_sources, modified_blocks);
386 Lights neighbors of from_nodes, collects all them and then
389 void Map::spreadLight(enum LightBank bank,
390 core::map<v3s16, bool> & from_nodes,
391 core::map<v3s16, MapBlock*> & modified_blocks)
393 const v3s16 dirs[6] = {
394 v3s16(0,0,1), // back
396 v3s16(1,0,0), // right
397 v3s16(0,0,-1), // front
398 v3s16(0,-1,0), // bottom
399 v3s16(-1,0,0), // left
402 if(from_nodes.size() == 0)
405 u32 blockchangecount = 0;
407 core::map<v3s16, bool> lighted_nodes;
408 core::map<v3s16, bool>::Iterator j;
409 j = from_nodes.getIterator();
412 Initialize block cache
415 MapBlock *block = NULL;
416 // Cache this a bit, too
417 bool block_checked_in_modified = false;
419 for(; j.atEnd() == false; j++)
420 //for(; j != from_nodes.end(); j++)
422 v3s16 pos = j.getNode()->getKey();
424 //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
425 v3s16 blockpos = getNodeBlockPos(pos);
427 // Only fetch a new block if the block position has changed
429 if(block == NULL || blockpos != blockpos_last){
430 block = getBlockNoCreate(blockpos);
431 blockpos_last = blockpos;
433 block_checked_in_modified = false;
437 catch(InvalidPositionException &e)
445 // Calculate relative position in block
446 v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
448 // Get node straight from the block
449 MapNode n = block->getNode(relpos);
451 u8 oldlight = n.getLight(bank);
452 u8 newlight = diminish_light(oldlight);
454 // Loop through 6 neighbors
455 for(u16 i=0; i<6; i++){
456 // Get the position of the neighbor node
457 v3s16 n2pos = pos + dirs[i];
459 // Get the block where the node is located
460 v3s16 blockpos = getNodeBlockPos(n2pos);
464 // Only fetch a new block if the block position has changed
466 if(block == NULL || blockpos != blockpos_last){
467 block = getBlockNoCreate(blockpos);
468 blockpos_last = blockpos;
470 block_checked_in_modified = false;
474 catch(InvalidPositionException &e)
479 // Calculate relative position in block
480 v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
481 // Get node straight from the block
482 MapNode n2 = block->getNode(relpos);
484 bool changed = false;
486 If the neighbor is brighter than the current node,
487 add to list (it will light up this node on its turn)
489 if(n2.getLight(bank) > undiminish_light(oldlight))
491 lighted_nodes.insert(n2pos, true);
492 //lighted_nodes.push_back(n2pos);
496 If the neighbor is dimmer than how much light this node
497 would spread on it, add to list
499 if(n2.getLight(bank) < newlight)
501 if(n2.light_propagates())
503 n2.setLight(bank, newlight);
504 block->setNode(relpos, n2);
505 lighted_nodes.insert(n2pos, true);
506 //lighted_nodes.push_back(n2pos);
511 // Add to modified_blocks
512 if(changed == true && block_checked_in_modified == false)
514 // If the block is not found in modified_blocks, add.
515 if(modified_blocks.find(blockpos) == NULL)
517 modified_blocks.insert(blockpos, block);
519 block_checked_in_modified = true;
522 catch(InvalidPositionException &e)
529 /*dstream<<"spreadLight(): Changed block "
530 <<blockchangecount<<" times"
531 <<" for "<<from_nodes.size()<<" nodes"
534 if(lighted_nodes.size() > 0)
535 spreadLight(bank, lighted_nodes, modified_blocks);
539 A single-node source variation of the above.
541 void Map::lightNeighbors(enum LightBank bank,
543 core::map<v3s16, MapBlock*> & modified_blocks)
545 core::map<v3s16, bool> from_nodes;
546 from_nodes.insert(pos, true);
547 spreadLight(bank, from_nodes, modified_blocks);
550 v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
553 v3s16(0,0,1), // back
555 v3s16(1,0,0), // right
556 v3s16(0,0,-1), // front
557 v3s16(0,-1,0), // bottom
558 v3s16(-1,0,0), // left
561 u8 brightest_light = 0;
562 v3s16 brightest_pos(0,0,0);
563 bool found_something = false;
565 // Loop through 6 neighbors
566 for(u16 i=0; i<6; i++){
567 // Get the position of the neighbor node
568 v3s16 n2pos = p + dirs[i];
573 catch(InvalidPositionException &e)
577 if(n2.getLight(bank) > brightest_light || found_something == false){
578 brightest_light = n2.getLight(bank);
579 brightest_pos = n2pos;
580 found_something = true;
584 if(found_something == false)
585 throw InvalidPositionException();
587 return brightest_pos;
591 Propagates sunlight down from a node.
592 Starting point gets sunlight.
594 Returns the lowest y value of where the sunlight went.
596 Mud is turned into grass in where the sunlight stops.
598 s16 Map::propagateSunlight(v3s16 start,
599 core::map<v3s16, MapBlock*> & modified_blocks)
604 v3s16 pos(start.X, y, start.Z);
606 v3s16 blockpos = getNodeBlockPos(pos);
609 block = getBlockNoCreate(blockpos);
611 catch(InvalidPositionException &e)
616 v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
617 MapNode n = block->getNode(relpos);
619 if(n.sunlight_propagates())
621 n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
622 block->setNode(relpos, n);
624 modified_blocks.insert(blockpos, block);
628 // Turn mud into grass
629 if(n.d == CONTENT_MUD)
632 block->setNode(relpos, n);
633 modified_blocks.insert(blockpos, block);
636 // Sunlight goes no further
643 void Map::updateLighting(enum LightBank bank,
644 core::map<v3s16, MapBlock*> & a_blocks,
645 core::map<v3s16, MapBlock*> & modified_blocks)
647 /*m_dout<<DTIME<<"Map::updateLighting(): "
648 <<a_blocks.size()<<" blocks."<<std::endl;*/
650 //TimeTaker timer("updateLighting");
654 //u32 count_was = modified_blocks.size();
656 core::map<v3s16, MapBlock*> blocks_to_update;
658 core::map<v3s16, bool> light_sources;
660 core::map<v3s16, u8> unlight_from;
662 core::map<v3s16, MapBlock*>::Iterator i;
663 i = a_blocks.getIterator();
664 for(; i.atEnd() == false; i++)
666 MapBlock *block = i.getNode()->getValue();
670 // Don't bother with dummy blocks.
674 v3s16 pos = block->getPos();
675 modified_blocks.insert(pos, block);
677 blocks_to_update.insert(pos, block);
680 Clear all light from block
682 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
683 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
684 for(s16 y=0; y<MAP_BLOCKSIZE; y++)
689 MapNode n = block->getNode(v3s16(x,y,z));
690 u8 oldlight = n.getLight(bank);
692 block->setNode(v3s16(x,y,z), n);
694 // Collect borders for unlighting
695 if(x==0 || x == MAP_BLOCKSIZE-1
696 || y==0 || y == MAP_BLOCKSIZE-1
697 || z==0 || z == MAP_BLOCKSIZE-1)
699 v3s16 p_map = p + v3s16(
702 MAP_BLOCKSIZE*pos.Z);
703 unlight_from.insert(p_map, oldlight);
706 catch(InvalidPositionException &e)
709 This would happen when dealing with a
713 dstream<<"updateLighting(): InvalidPositionException"
718 if(bank == LIGHTBANK_DAY)
720 bool bottom_valid = block->propagateSunlight(light_sources);
722 // If bottom is valid, we're done.
726 else if(bank == LIGHTBANK_NIGHT)
728 // For night lighting, sunlight is not propagated
733 // Invalid lighting bank
737 /*dstream<<"Bottom for sunlight-propagated block ("
738 <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
741 // Bottom sunlight is not valid; get the block and loop to it
745 block = getBlockNoCreate(pos);
747 catch(InvalidPositionException &e)
757 TimeTaker timer("unspreadLight");
758 unspreadLight(bank, unlight_from, light_sources, modified_blocks);
763 u32 diff = modified_blocks.size() - count_was;
764 count_was = modified_blocks.size();
765 dstream<<"unspreadLight modified "<<diff<<std::endl;
769 TimeTaker timer("spreadLight");
770 spreadLight(bank, light_sources, modified_blocks);
775 u32 diff = modified_blocks.size() - count_was;
776 count_was = modified_blocks.size();
777 dstream<<"spreadLight modified "<<diff<<std::endl;
782 //MapVoxelManipulator vmanip(this);
784 // Make a manual voxel manipulator and load all the blocks
785 // that touch the requested blocks
786 ManualMapVoxelManipulator vmanip(this);
787 core::map<v3s16, MapBlock*>::Iterator i;
788 i = blocks_to_update.getIterator();
789 for(; i.atEnd() == false; i++)
791 MapBlock *block = i.getNode()->getValue();
792 v3s16 p = block->getPos();
794 // Add all surrounding blocks
795 vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1));
798 Add all surrounding blocks that have up-to-date lighting
799 NOTE: This doesn't quite do the job (not everything
800 appropriate is lighted)
802 /*for(s16 z=-1; z<=1; z++)
803 for(s16 y=-1; y<=1; y++)
804 for(s16 x=-1; x<=1; x++)
807 MapBlock *block = getBlockNoCreateNoEx(p);
812 if(block->getLightingExpired())
814 vmanip.initialEmerge(p, p);
817 // Lighting of block will be updated completely
818 block->setLightingExpired(false);
822 //TimeTaker timer("unSpreadLight");
823 vmanip.unspreadLight(bank, unlight_from, light_sources);
826 //TimeTaker timer("spreadLight");
827 vmanip.spreadLight(bank, light_sources);
830 //TimeTaker timer("blitBack");
831 vmanip.blitBack(modified_blocks);
833 /*dstream<<"emerge_time="<<emerge_time<<std::endl;
837 //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
840 void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
841 core::map<v3s16, MapBlock*> & modified_blocks)
843 updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
844 updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
847 Update information about whether day and night light differ
849 for(core::map<v3s16, MapBlock*>::Iterator
850 i = modified_blocks.getIterator();
851 i.atEnd() == false; i++)
853 MapBlock *block = i.getNode()->getValue();
854 block->updateDayNightDiff();
859 This is called after changing a node from transparent to opaque.
860 The lighting value of the node should be left as-is after changing
861 other values. This sets the lighting value to 0.
863 /*void Map::nodeAddedUpdate(v3s16 p, u8 lightwas,
864 core::map<v3s16, MapBlock*> &modified_blocks)*/
865 void Map::addNodeAndUpdate(v3s16 p, MapNode n,
866 core::map<v3s16, MapBlock*> &modified_blocks)
869 m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
870 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
873 From this node to nodes underneath:
874 If lighting is sunlight (1.0), unlight neighbours and
879 v3s16 toppos = p + v3s16(0,1,0);
880 v3s16 bottompos = p + v3s16(0,-1,0);
882 bool node_under_sunlight = true;
883 core::map<v3s16, bool> light_sources;
886 If there is a node at top and it doesn't have sunlight,
887 there has not been any sunlight going down.
889 Otherwise there probably is.
892 MapNode topnode = getNode(toppos);
894 if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
895 node_under_sunlight = false;
897 catch(InvalidPositionException &e)
901 if(n.d != CONTENT_TORCH)
904 If there is grass below, change it to mud
907 MapNode bottomnode = getNode(bottompos);
909 if(bottomnode.d == CONTENT_GRASS
910 || bottomnode.d == CONTENT_GRASS_FOOTSTEPS)
912 bottomnode.d = CONTENT_MUD;
913 setNode(bottompos, bottomnode);
916 catch(InvalidPositionException &e)
921 enum LightBank banks[] =
926 for(s32 i=0; i<2; i++)
928 enum LightBank bank = banks[i];
930 u8 lightwas = getNode(p).getLight(bank);
932 // Add the block of the added node to modified_blocks
933 v3s16 blockpos = getNodeBlockPos(p);
934 MapBlock * block = getBlockNoCreate(blockpos);
935 assert(block != NULL);
936 modified_blocks.insert(blockpos, block);
938 if(isValidPosition(p) == false)
941 // Unlight neighbours of node.
942 // This means setting light of all consequent dimmer nodes
944 // This also collects the nodes at the border which will spread
945 // light again into this.
946 unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
954 If node is under sunlight, take all sunlighted nodes under
955 it and clear light from them and from where the light has
957 TODO: This could be optimized by mass-unlighting instead
960 if(node_under_sunlight)
964 //m_dout<<DTIME<<"y="<<y<<std::endl;
965 v3s16 n2pos(p.X, y, p.Z);
971 catch(InvalidPositionException &e)
976 if(n2.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
978 //m_dout<<DTIME<<"doing"<<std::endl;
979 unLightNeighbors(LIGHTBANK_DAY,
980 n2pos, n2.getLight(LIGHTBANK_DAY),
981 light_sources, modified_blocks);
982 n2.setLight(LIGHTBANK_DAY, 0);
990 for(s32 i=0; i<2; i++)
992 enum LightBank bank = banks[i];
995 Spread light from all nodes that might be capable of doing so
996 TODO: Convert to spreadLight
998 spreadLight(bank, light_sources, modified_blocks);
1002 Update information about whether day and night light differ
1004 for(core::map<v3s16, MapBlock*>::Iterator
1005 i = modified_blocks.getIterator();
1006 i.atEnd() == false; i++)
1008 MapBlock *block = i.getNode()->getValue();
1009 block->updateDayNightDiff();
1013 Add neighboring liquid nodes and the node itself if it is
1014 liquid (=water node was added) to transform queue.
1017 v3s16(0,0,0), // self
1018 v3s16(0,0,1), // back
1019 v3s16(0,1,0), // top
1020 v3s16(1,0,0), // right
1021 v3s16(0,0,-1), // front
1022 v3s16(0,-1,0), // bottom
1023 v3s16(-1,0,0), // left
1025 for(u16 i=0; i<7; i++)
1030 v3s16 p2 = p + dirs[i];
1032 MapNode n2 = getNode(p2);
1033 if(content_liquid(n2.d))
1035 m_transforming_liquid.push_back(p2);
1038 }catch(InvalidPositionException &e)
1046 void Map::removeNodeAndUpdate(v3s16 p,
1047 core::map<v3s16, MapBlock*> &modified_blocks)
1049 /*PrintInfo(m_dout);
1050 m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
1051 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1053 bool node_under_sunlight = true;
1055 v3s16 toppos = p + v3s16(0,1,0);
1057 // Node will be replaced with this
1058 u8 replace_material = CONTENT_AIR;
1061 If there is a node at top and it doesn't have sunlight,
1062 there will be no sunlight going down.
1065 MapNode topnode = getNode(toppos);
1067 if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
1068 node_under_sunlight = false;
1070 catch(InvalidPositionException &e)
1074 core::map<v3s16, bool> light_sources;
1076 enum LightBank banks[] =
1081 for(s32 i=0; i<2; i++)
1083 enum LightBank bank = banks[i];
1086 Unlight neighbors (in case the node is a light source)
1088 unLightNeighbors(bank, p,
1089 getNode(p).getLight(bank),
1090 light_sources, modified_blocks);
1095 This also clears the lighting.
1099 n.d = replace_material;
1102 for(s32 i=0; i<2; i++)
1104 enum LightBank bank = banks[i];
1107 Recalculate lighting
1109 spreadLight(bank, light_sources, modified_blocks);
1112 // Add the block of the removed node to modified_blocks
1113 v3s16 blockpos = getNodeBlockPos(p);
1114 MapBlock * block = getBlockNoCreate(blockpos);
1115 assert(block != NULL);
1116 modified_blocks.insert(blockpos, block);
1119 If the removed node was under sunlight, propagate the
1120 sunlight down from it and then light all neighbors
1121 of the propagated blocks.
1123 if(node_under_sunlight)
1125 s16 ybottom = propagateSunlight(p, modified_blocks);
1126 /*m_dout<<DTIME<<"Node was under sunlight. "
1127 "Propagating sunlight";
1128 m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
1130 for(; y >= ybottom; y--)
1132 v3s16 p2(p.X, y, p.Z);
1133 /*m_dout<<DTIME<<"lighting neighbors of node ("
1134 <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
1136 lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
1141 // Set the lighting of this node to 0
1142 // TODO: Is this needed? Lighting is cleared up there already.
1144 MapNode n = getNode(p);
1145 n.setLight(LIGHTBANK_DAY, 0);
1148 catch(InvalidPositionException &e)
1154 for(s32 i=0; i<2; i++)
1156 enum LightBank bank = banks[i];
1158 // Get the brightest neighbour node and propagate light from it
1159 v3s16 n2p = getBrightestNeighbour(bank, p);
1161 MapNode n2 = getNode(n2p);
1162 lightNeighbors(bank, n2p, modified_blocks);
1164 catch(InvalidPositionException &e)
1170 Update information about whether day and night light differ
1172 for(core::map<v3s16, MapBlock*>::Iterator
1173 i = modified_blocks.getIterator();
1174 i.atEnd() == false; i++)
1176 MapBlock *block = i.getNode()->getValue();
1177 block->updateDayNightDiff();
1181 Add neighboring liquid nodes to transform queue.
1184 v3s16(0,0,1), // back
1185 v3s16(0,1,0), // top
1186 v3s16(1,0,0), // right
1187 v3s16(0,0,-1), // front
1188 v3s16(0,-1,0), // bottom
1189 v3s16(-1,0,0), // left
1191 for(u16 i=0; i<6; i++)
1196 v3s16 p2 = p + dirs[i];
1198 MapNode n2 = getNode(p2);
1199 if(content_liquid(n2.d))
1201 m_transforming_liquid.push_back(p2);
1204 }catch(InvalidPositionException &e)
1211 void Map::expireMeshes(bool only_daynight_diffed)
1213 TimeTaker timer("expireMeshes()");
1215 core::map<v2s16, MapSector*>::Iterator si;
1216 si = m_sectors.getIterator();
1217 for(; si.atEnd() == false; si++)
1219 MapSector *sector = si.getNode()->getValue();
1221 core::list< MapBlock * > sectorblocks;
1222 sector->getBlocks(sectorblocks);
1224 core::list< MapBlock * >::Iterator i;
1225 for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
1227 MapBlock *block = *i;
1229 if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false)
1235 JMutexAutoLock lock(block->mesh_mutex);
1236 if(block->mesh != NULL)
1238 /*block->mesh->drop();
1239 block->mesh = NULL;*/
1240 block->setMeshExpired(true);
1247 void Map::updateMeshes(v3s16 blockpos, u32 daynight_ratio)
1249 assert(mapType() == MAPTYPE_CLIENT);
1252 v3s16 p = blockpos + v3s16(0,0,0);
1253 MapBlock *b = getBlockNoCreate(p);
1254 b->updateMesh(daynight_ratio);
1256 catch(InvalidPositionException &e){}
1259 v3s16 p = blockpos + v3s16(-1,0,0);
1260 MapBlock *b = getBlockNoCreate(p);
1261 b->updateMesh(daynight_ratio);
1263 catch(InvalidPositionException &e){}
1265 v3s16 p = blockpos + v3s16(0,-1,0);
1266 MapBlock *b = getBlockNoCreate(p);
1267 b->updateMesh(daynight_ratio);
1269 catch(InvalidPositionException &e){}
1271 v3s16 p = blockpos + v3s16(0,0,-1);
1272 MapBlock *b = getBlockNoCreate(p);
1273 b->updateMesh(daynight_ratio);
1275 catch(InvalidPositionException &e){}
1278 v3s16 p = blockpos + v3s16(1,0,0);
1279 MapBlock *b = getBlockNoCreate(p);
1280 b->updateMesh(daynight_ratio);
1282 catch(InvalidPositionException &e){}
1284 v3s16 p = blockpos + v3s16(0,1,0);
1285 MapBlock *b = getBlockNoCreate(p);
1286 b->updateMesh(daynight_ratio);
1288 catch(InvalidPositionException &e){}
1290 v3s16 p = blockpos + v3s16(0,0,1);
1291 MapBlock *b = getBlockNoCreate(p);
1292 b->updateMesh(daynight_ratio);
1294 catch(InvalidPositionException &e){}*/
1299 bool Map::dayNightDiffed(v3s16 blockpos)
1302 v3s16 p = blockpos + v3s16(0,0,0);
1303 MapBlock *b = getBlockNoCreate(p);
1304 if(b->dayNightDiffed())
1307 catch(InvalidPositionException &e){}
1310 v3s16 p = blockpos + v3s16(-1,0,0);
1311 MapBlock *b = getBlockNoCreate(p);
1312 if(b->dayNightDiffed())
1315 catch(InvalidPositionException &e){}
1317 v3s16 p = blockpos + v3s16(0,-1,0);
1318 MapBlock *b = getBlockNoCreate(p);
1319 if(b->dayNightDiffed())
1322 catch(InvalidPositionException &e){}
1324 v3s16 p = blockpos + v3s16(0,0,-1);
1325 MapBlock *b = getBlockNoCreate(p);
1326 if(b->dayNightDiffed())
1329 catch(InvalidPositionException &e){}
1332 v3s16 p = blockpos + v3s16(1,0,0);
1333 MapBlock *b = getBlockNoCreate(p);
1334 if(b->dayNightDiffed())
1337 catch(InvalidPositionException &e){}
1339 v3s16 p = blockpos + v3s16(0,1,0);
1340 MapBlock *b = getBlockNoCreate(p);
1341 if(b->dayNightDiffed())
1344 catch(InvalidPositionException &e){}
1346 v3s16 p = blockpos + v3s16(0,0,1);
1347 MapBlock *b = getBlockNoCreate(p);
1348 if(b->dayNightDiffed())
1351 catch(InvalidPositionException &e){}
1357 Updates usage timers
1359 void Map::timerUpdate(float dtime)
1361 JMutexAutoLock lock(m_sector_mutex);
1363 core::map<v2s16, MapSector*>::Iterator si;
1365 si = m_sectors.getIterator();
1366 for(; si.atEnd() == false; si++)
1368 MapSector *sector = si.getNode()->getValue();
1369 sector->usage_timer += dtime;
1373 void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
1376 Wait for caches to be removed before continuing.
1378 This disables the existence of caches while locked
1380 //SharedPtr<JMutexAutoLock> cachelock(m_blockcachelock.waitCaches());
1382 core::list<v2s16>::Iterator j;
1383 for(j=list.begin(); j!=list.end(); j++)
1385 MapSector *sector = m_sectors[*j];
1388 sector->deleteBlocks();
1393 If sector is in sector cache, remove it from there
1395 if(m_sector_cache == sector)
1397 m_sector_cache = NULL;
1400 Remove from map and delete
1402 m_sectors.remove(*j);
1408 u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
1409 core::list<v3s16> *deleted_blocks)
1411 JMutexAutoLock lock(m_sector_mutex);
1413 core::list<v2s16> sector_deletion_queue;
1414 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
1415 for(; i.atEnd() == false; i++)
1417 MapSector *sector = i.getNode()->getValue();
1419 Delete sector from memory if it hasn't been used in a long time
1421 if(sector->usage_timer > timeout)
1423 sector_deletion_queue.push_back(i.getNode()->getKey());
1425 if(deleted_blocks != NULL)
1427 // Collect positions of blocks of sector
1428 MapSector *sector = i.getNode()->getValue();
1429 core::list<MapBlock*> blocks;
1430 sector->getBlocks(blocks);
1431 for(core::list<MapBlock*>::Iterator i = blocks.begin();
1432 i != blocks.end(); i++)
1434 deleted_blocks->push_back((*i)->getPos());
1439 deleteSectors(sector_deletion_queue, only_blocks);
1440 return sector_deletion_queue.getSize();
1443 void Map::PrintInfo(std::ostream &out)
1448 #define WATER_DROP_BOOST 4
1450 void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
1452 DSTACK(__FUNCTION_NAME);
1453 //TimeTaker timer("transformLiquids()");
1456 u32 initial_size = m_transforming_liquid.size();
1458 //dstream<<"transformLiquids(): initial_size="<<initial_size<<std::endl;
1460 while(m_transforming_liquid.size() != 0)
1463 Get a queued transforming liquid node
1465 v3s16 p0 = m_transforming_liquid.pop_front();
1467 MapNode n0 = getNode(p0);
1469 // Don't deal with non-liquids
1470 if(content_liquid(n0.d) == false)
1473 bool is_source = !content_flowing_liquid(n0.d);
1475 u8 liquid_level = 8;
1476 if(is_source == false)
1477 liquid_level = n0.param2 & 0x0f;
1479 // Turn possible source into non-source
1480 u8 nonsource_c = make_liquid_flowing(n0.d);
1483 If not source, check that some node flows into this one
1484 and what is the level of liquid in this one
1486 if(is_source == false)
1488 s8 new_liquid_level_max = -1;
1490 v3s16 dirs_from[5] = {
1491 v3s16(0,1,0), // top
1492 v3s16(0,0,1), // back
1493 v3s16(1,0,0), // right
1494 v3s16(0,0,-1), // front
1495 v3s16(-1,0,0), // left
1497 for(u16 i=0; i<5; i++)
1502 bool from_top = (i==0);
1504 v3s16 p2 = p0 + dirs_from[i];
1505 MapNode n2 = getNode(p2);
1507 if(content_liquid(n2.d))
1509 u8 n2_nonsource_c = make_liquid_flowing(n2.d);
1510 // Check that the liquids are the same type
1511 if(n2_nonsource_c != nonsource_c)
1513 dstream<<"WARNING: Not handling: different liquids"
1514 " collide"<<std::endl;
1517 bool n2_is_source = !content_flowing_liquid(n2.d);
1518 s8 n2_liquid_level = 8;
1519 if(n2_is_source == false)
1520 n2_liquid_level = n2.param2 & 0x07;
1522 s8 new_liquid_level = -1;
1525 //new_liquid_level = 7;
1526 if(n2_liquid_level >= 7 - WATER_DROP_BOOST)
1527 new_liquid_level = 7;
1529 new_liquid_level = n2_liquid_level + WATER_DROP_BOOST;
1531 else if(n2_liquid_level > 0)
1533 new_liquid_level = n2_liquid_level - 1;
1536 if(new_liquid_level > new_liquid_level_max)
1537 new_liquid_level_max = new_liquid_level;
1540 }catch(InvalidPositionException &e)
1546 If liquid level should be something else, update it and
1547 add all the neighboring water nodes to the transform queue.
1549 if(new_liquid_level_max != liquid_level)
1551 if(new_liquid_level_max == -1)
1553 // Remove water alltoghether
1560 n0.param2 = new_liquid_level_max;
1564 // Block has been modified
1566 v3s16 blockpos = getNodeBlockPos(p0);
1567 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1569 modified_blocks.insert(blockpos, block);
1573 Add neighboring non-source liquid nodes to transform queue.
1576 v3s16(0,0,1), // back
1577 v3s16(0,1,0), // top
1578 v3s16(1,0,0), // right
1579 v3s16(0,0,-1), // front
1580 v3s16(0,-1,0), // bottom
1581 v3s16(-1,0,0), // left
1583 for(u16 i=0; i<6; i++)
1588 v3s16 p2 = p0 + dirs[i];
1590 MapNode n2 = getNode(p2);
1591 if(content_flowing_liquid(n2.d))
1593 m_transforming_liquid.push_back(p2);
1596 }catch(InvalidPositionException &e)
1603 // Get a new one from queue if the node has turned into non-water
1604 if(content_liquid(n0.d) == false)
1608 Flow water from this node
1610 v3s16 dirs_to[5] = {
1611 v3s16(0,-1,0), // bottom
1612 v3s16(0,0,1), // back
1613 v3s16(1,0,0), // right
1614 v3s16(0,0,-1), // front
1615 v3s16(-1,0,0), // left
1617 for(u16 i=0; i<5; i++)
1622 bool to_bottom = (i == 0);
1624 // If liquid is at lowest possible height, it's not going
1625 // anywhere except down
1626 if(liquid_level == 0 && to_bottom == false)
1629 u8 liquid_next_level = 0;
1630 // If going to bottom
1633 //liquid_next_level = 7;
1634 if(liquid_level >= 7 - WATER_DROP_BOOST)
1635 liquid_next_level = 7;
1637 liquid_next_level = liquid_level + WATER_DROP_BOOST;
1640 liquid_next_level = liquid_level - 1;
1642 bool n2_changed = false;
1643 bool flowed = false;
1645 v3s16 p2 = p0 + dirs_to[i];
1647 MapNode n2 = getNode(p2);
1648 //dstream<<"[1] n2.param="<<(int)n2.param<<std::endl;
1650 if(content_liquid(n2.d))
1652 u8 n2_nonsource_c = make_liquid_flowing(n2.d);
1653 // Check that the liquids are the same type
1654 if(n2_nonsource_c != nonsource_c)
1656 dstream<<"WARNING: Not handling: different liquids"
1657 " collide"<<std::endl;
1660 bool n2_is_source = !content_flowing_liquid(n2.d);
1661 u8 n2_liquid_level = 8;
1662 if(n2_is_source == false)
1663 n2_liquid_level = n2.param2 & 0x07;
1672 // Just flow into the source, nothing changes.
1673 // n2_changed is not set because destination didn't change
1678 if(liquid_next_level > liquid_level)
1680 n2.param2 = liquid_next_level;
1688 else if(n2.d == CONTENT_AIR)
1691 n2.param2 = liquid_next_level;
1698 //dstream<<"[2] n2.param="<<(int)n2.param<<std::endl;
1702 m_transforming_liquid.push_back(p2);
1704 v3s16 blockpos = getNodeBlockPos(p2);
1705 MapBlock *block = getBlockNoCreateNoEx(blockpos);
1707 modified_blocks.insert(blockpos, block);
1710 // If n2_changed to bottom, don't flow anywhere else
1711 if(to_bottom && flowed && !is_source)
1714 }catch(InvalidPositionException &e)
1720 //if(loopcount >= 100000)
1721 if(loopcount >= initial_size * 1)
1724 //dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
1731 ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
1743 Experimental and debug stuff
1747 dstream<<"Generating map point attribute lists"<<std::endl;
1749 PointAttributeList *list_baseheight = m_padb.getList("hm_baseheight");
1750 PointAttributeList *list_randmax = m_padb.getList("hm_randmax");
1751 PointAttributeList *list_randfactor = m_padb.getList("hm_randfactor");
1752 PointAttributeList *list_plants_amount = m_padb.getList("plants_amount");
1753 PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
1757 NOTE: BEWARE: Too big amount of these will make map generation
1758 slow. Especially those that are read by every block emerge.
1766 for(u32 i=0; i<500; i++)
1768 /*u32 lim = MAP_GENERATION_LIMIT;
1772 u32 lim = 500 + MAP_GENERATION_LIMIT * i / 500;
1775 -lim + myrand()%(lim*2),
1777 -lim + myrand()%(lim*2)
1779 /*float plants_amount = (float)(myrand()%1050) / 1000.0;
1780 plants_amount = pow(plants_amount, 5);
1781 list_plants_amount->addPoint(p, Attribute(plants_amount));*/
1783 float plants_amount = 0;
1786 plants_amount = 1.5;
1788 else if(myrand()%4 == 0)
1790 plants_amount = 0.5;
1792 else if(myrand()%2 == 0)
1794 plants_amount = 0.03;
1798 plants_amount = 0.0;
1802 list_plants_amount->addPoint(p, Attribute(plants_amount));
1805 for(u32 i=0; i<500; i++)
1807 /*u32 lim = MAP_GENERATION_LIMIT;
1811 u32 lim = 500 + MAP_GENERATION_LIMIT * i / 500;
1814 -lim + myrand()%(lim*2),
1816 -lim + myrand()%(lim*2)
1819 float caves_amount = 0;
1824 else if(myrand()%3 == 0)
1830 caves_amount = 0.05;
1833 list_caves_amount->addPoint(p, Attribute(caves_amount));
1836 for(u32 i=0; i<500; i++)
1838 /*u32 lim = MAP_GENERATION_LIMIT;
1842 u32 lim = 500 + MAP_GENERATION_LIMIT * i / 500;
1845 -lim + (myrand()%(lim*2)),
1847 -lim + (myrand()%(lim*2))
1850 /*s32 bh_i = (myrand()%200) - 50;
1851 float baseheight = (float)bh_i;
1855 float randmax = (float)(myrand()%(int)(10.*pow(m, 1./e)))/10.;
1856 randmax = pow(randmax, e);
1858 //float randmax = (float)(myrand()%60);
1859 float randfactor = (float)(myrand()%450) / 1000.0 + 0.4;*/
1861 float baseheight = 0;
1863 float randfactor = 0;
1865 /*if(myrand()%5 == 0)
1871 else if(myrand()%6 == 0)
1877 else if(myrand()%4 == 0)
1883 else if(myrand()%3 == 0)
1909 list_baseheight->addPoint(p, Attribute(baseheight));
1910 list_randmax->addPoint(p, Attribute(randmax));
1911 list_randfactor->addPoint(p, Attribute(randfactor));
1915 // Add only one entry
1916 list_baseheight->addPoint(v3s16(0,0,0), Attribute(0));
1917 list_randmax->addPoint(v3s16(0,0,0), Attribute(30));
1918 list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.45));
1921 /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(0));
1922 list_randmax->addPoint(v3s16(0,0,0), Attribute(10));
1923 list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.65));*/
1927 Try to load map; if not found, create a new one.
1930 m_savedir = savedir;
1931 m_map_saving_enabled = false;
1935 // If directory exists, check contents and load if possible
1936 if(fs::PathExists(m_savedir))
1938 // If directory is empty, it is safe to save into it.
1939 if(fs::GetDirListing(m_savedir).size() == 0)
1941 dstream<<DTIME<<"Server: Empty save directory is valid."
1943 m_map_saving_enabled = true;
1947 // Load master heightmap
1948 loadMasterHeightmap();
1950 // Load sector (0,0) and throw and exception on fail
1951 if(loadSectorFull(v2s16(0,0)) == false)
1952 throw LoadError("Failed to load sector (0,0)");
1954 dstream<<DTIME<<"Server: Successfully loaded master "
1955 "heightmap and sector (0,0) from "<<savedir<<
1956 ", assuming valid save directory."
1959 m_map_saving_enabled = true;
1960 // Map loaded, not creating new one
1964 // If directory doesn't exist, it is safe to save to it
1966 m_map_saving_enabled = true;
1969 catch(std::exception &e)
1971 dstream<<DTIME<<"Server: Failed to load map from "<<savedir
1972 <<", exception: "<<e.what()<<std::endl;
1973 dstream<<DTIME<<"Please remove the map or fix it."<<std::endl;
1974 dstream<<DTIME<<"WARNING: Map saving will be disabled."<<std::endl;
1977 dstream<<DTIME<<"Initializing new map."<<std::endl;
1979 // Create master heightmap
1980 m_heightmap = new UnlimitedHeightmap
1983 // Set map parameters
1986 // Create zero sector
1987 emergeSector(v2s16(0,0));
1989 // Initially write whole map
1993 ServerMap::~ServerMap()
1997 if(m_map_saving_enabled)
2000 // Save only changed parts
2002 dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
2006 dstream<<DTIME<<"Server: map not saved"<<std::endl;
2009 catch(std::exception &e)
2011 dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
2012 <<", exception: "<<e.what()<<std::endl;
2015 if(m_heightmap != NULL)
2021 core::map<v2s16, MapChunk*>::Iterator i = m_chunks.getIterator();
2022 for(; i.atEnd() == false; i++)
2024 MapChunk *chunk = i.getNode()->getValue();
2030 MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos)
2032 // Return if chunk already exists
2033 MapChunk *chunk = getChunk(chunkpos);
2041 dstream<<"generateChunkRaw(): "
2042 <<"("<<chunkpos.X<<","<<chunkpos.Y<<")"
2045 TimeTaker timer("generateChunkRaw()");
2047 v2s16 sectorpos_base = chunk_to_sector(chunkpos);
2049 core::map<v3s16, MapBlock*> changed_blocks;
2050 core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
2052 u32 generated_block_count = 0;
2054 for(s16 y=0; y<m_chunksize; y++)
2056 /*dstream<<"Generating sectors "
2057 <<"("<<sectorpos_base.X<<"..."
2058 <<(sectorpos_base.X+m_chunksize-1)
2062 // With caves_amount attribute fetch: ~90ms (379ms peaks)
2063 // Without: ~38ms (396ms peaks)
2064 //TimeTaker timer("Chunk sector row");
2066 for(s16 x=0; x<m_chunksize; x++)
2068 v2s16 sectorpos = sectorpos_base + v2s16(x,y);
2070 /*dstream<<"Generating sector "
2071 <<"("<<sectorpos.X<<","<<sectorpos.Y<<")"
2075 ServerMapSector *sector = generateSector(sectorpos);
2078 Generate main blocks of sector
2081 for(s16 y2=-d/2; y2<d/2; y2++)
2085 // Check that the block doesn't exist already
2086 if(sector->getBlockNoCreateNoEx(y2))
2089 generateBlock(p, NULL, sector, changed_blocks,
2090 lighting_invalidated_blocks);
2092 generated_block_count++;
2097 dstream<<"generateChunkRaw generated "<<generated_block_count
2098 <<" blocks"<<std::endl;
2101 TimeTaker timer2("generateChunkRaw() lighting");
2103 core::map<v3s16, MapBlock*> lighting_modified_blocks;
2104 updateLighting(lighting_invalidated_blocks, lighting_modified_blocks);
2107 // Add chunk meta information
2108 chunk = new MapChunk();
2109 m_chunks.insert(chunkpos, chunk);
2113 MapChunk* ServerMap::generateChunk(v2s16 chunkpos)
2116 Generate chunk and neighbors
2118 for(s16 x=-1; x<=1; x++)
2119 for(s16 y=-1; y<=1; y++)
2121 generateChunkRaw(chunkpos + v2s16(x,y));
2127 MapChunk *chunk = getChunk(chunkpos);
2130 chunk->setIsVolatile(false);
2136 MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos)
2138 dstream<<"WARNING: No-op "<<__FUNCTION_NAME<<" called"<<std::endl;
2143 Some helper functions
2146 s16 find_ground_level(VoxelManipulator &vmanip, v2s16 p2d)
2148 v3s16 em = vmanip.m_area.getExtent();
2149 s16 y_nodes_max = vmanip.m_area.MaxEdge.Y;
2150 s16 y_nodes_min = vmanip.m_area.MinEdge.Y;
2151 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
2153 for(y=y_nodes_max; y>=y_nodes_min; y--)
2155 MapNode &n = vmanip.m_data[i];
2156 if(content_walkable(n.d))
2159 vmanip.m_area.add_y(em, i, -1);
2161 if(y >= y_nodes_min)
2167 void make_tree(VoxelManipulator &vmanip, v3s16 p0)
2169 MapNode treenode(CONTENT_TREE);
2170 MapNode leavesnode(CONTENT_LEAVES);
2172 s16 trunk_h = myrand_range(2, 6);
2174 for(s16 ii=0; ii<trunk_h; ii++)
2176 if(vmanip.m_area.contains(p1))
2177 vmanip.m_data[vmanip.m_area.index(p1)] = treenode;
2181 // p1 is now the last piece of the trunk
2184 VoxelArea leaves_a(v3s16(-2,-2,-2), v3s16(2,2,2));
2185 SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]);
2186 for(s32 i=0; i<leaves_a.getVolume(); i++)
2189 // Force leaves at near the end of the trunk
2192 for(s16 z=-d; z<=d; z++)
2193 for(s16 y=-d; y<=d; y++)
2194 for(s16 x=-d; x<=d; x++)
2196 leaves_d[leaves_a.index(v3s16(x,y,z))] = 1;
2200 // Add leaves randomly
2201 for(u32 iii=0; iii<7; iii++)
2206 myrand_range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X-d),
2207 myrand_range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y-d),
2208 myrand_range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z-d)
2211 for(s16 z=0; z<=d; z++)
2212 for(s16 y=0; y<=d; y++)
2213 for(s16 x=0; x<=d; x++)
2215 leaves_d[leaves_a.index(p+v3s16(x,y,z))] = 1;
2219 // Blit leaves to vmanip
2220 for(s16 z=leaves_a.MinEdge.Z; z<=leaves_a.MaxEdge.Z; z++)
2221 for(s16 y=leaves_a.MinEdge.Y; y<=leaves_a.MaxEdge.Y; y++)
2222 for(s16 x=leaves_a.MinEdge.X; x<=leaves_a.MaxEdge.X; x++)
2226 if(vmanip.m_area.contains(p) == false)
2228 u32 vi = vmanip.m_area.index(p);
2229 if(vmanip.m_data[vi].d != CONTENT_AIR)
2231 u32 i = leaves_a.index(x,y,z);
2232 if(leaves_d[i] == 1)
2233 vmanip.m_data[vi] = leavesnode;
2237 MapChunk* ServerMap::generateChunk(v2s16 chunkpos)
2239 TimeTaker timer("generateChunk()");
2241 // The distance how far into the neighbors the generator is allowed to go
2242 s16 max_spread_amount = m_chunksize * MAP_BLOCKSIZE / 2 + 2;
2243 // Minimum amount of space left on sides for mud to fall in
2244 s16 min_mud_fall_space = 2;
2245 // Maximum diameter of stone obstacles in X and Z
2246 s16 stone_obstacle_max_size = m_chunksize*MAP_BLOCKSIZE/2;
2247 assert(stone_obstacle_max_size/2 <= max_spread_amount-min_mud_fall_space);
2249 s16 y_blocks_min = -4;
2250 s16 y_blocks_max = 3;
2251 s16 h_blocks = y_blocks_max - y_blocks_min + 1;
2252 s16 y_nodes_min = y_blocks_min * MAP_BLOCKSIZE;
2253 s16 y_nodes_max = y_blocks_max * MAP_BLOCKSIZE + MAP_BLOCKSIZE - 1;
2255 v2s16 sectorpos_base = chunk_to_sector(chunkpos);
2256 s16 sectorpos_base_size = m_chunksize;
2257 v2s16 sectorpos_bigbase = chunk_to_sector(chunkpos - v2s16(1,1));
2258 s16 sectorpos_bigbase_size = m_chunksize * 3;
2260 v3s16 bigarea_blocks_min(
2261 sectorpos_bigbase.X,
2266 v3s16 bigarea_blocks_max(
2267 sectorpos_bigbase.X + sectorpos_bigbase_size - 1,
2268 y_blocks_min + sectorpos_bigbase_size - 1,
2269 sectorpos_bigbase.Y + sectorpos_bigbase_size - 1
2273 Create the whole area of this and the neighboring chunks
2276 TimeTaker timer("generateChunk() create area");
2278 for(s16 x=0; x<sectorpos_bigbase_size; x++)
2279 for(s16 z=0; z<sectorpos_bigbase_size; z++)
2281 v2s16 sectorpos = sectorpos_bigbase + v2s16(x,z);
2282 ServerMapSector *sector = createSector(sectorpos);
2285 for(s16 y=y_blocks_min; y<=y_blocks_max; y++)
2287 v3s16 blockpos(sectorpos.X, y, sectorpos.Y);
2288 MapBlock *block = createBlock(blockpos);
2290 // Lighting won't be calculated
2291 //block->setLightingExpired(true);
2292 // Lighting will be calculated
2293 block->setLightingExpired(false);
2296 TODO: Do this better.
2297 Block gets sunlight if this is true.
2299 This should be set to true when the top side of a block
2300 is completely exposed to the sky.
2302 block->setIsUnderground(y != y_blocks_max);
2308 Now we have a big empty area.
2310 Make a ManualMapVoxelManipulator that contains this and the
2314 ManualMapVoxelManipulator vmanip(this);
2315 // Add the area we just generated
2317 TimeTaker timer("generateChunk() initialEmerge");
2318 vmanip.initialEmerge(bigarea_blocks_min, bigarea_blocks_max);
2321 TimeTaker timer_generate("generateChunk() generate");
2324 Generate general ground level to full area
2327 for(s16 x=0; x<sectorpos_bigbase_size*MAP_BLOCKSIZE; x++)
2328 for(s16 z=0; z<sectorpos_bigbase_size*MAP_BLOCKSIZE; z++)
2331 v2s16 p2d = sectorpos_bigbase*MAP_BLOCKSIZE + v2s16(x,z);
2334 Skip of already generated
2337 v3s16 p(p2d.X, y_nodes_min, p2d.Y);
2338 if(vmanip.m_data[vmanip.m_area.index(p)].d != CONTENT_AIR)
2342 // Ground height at this point
2343 float surface_y_f = 0.0;
2345 A hack to get the ground height from the sector.
2349 v2s16 sectorpos = getContainerPos(p2d, MAP_BLOCKSIZE);
2350 v2s16 sector_relpos = p2d - sectorpos*MAP_BLOCKSIZE;
2351 MapSector *sector = getSectorNoGenerate(sectorpos);
2353 float h = sector->getGroundHeight(sector_relpos);
2354 if(h > GROUNDHEIGHT_VALID_MINVALUE)
2357 dstream<<"WARNING: "<<__FUNCTION_NAME
2358 <<": sector->getGroundHeight returned bad height"<<std::endl;
2360 // Convert to integer
2361 s16 surface_y = (s16)surface_y_f;
2364 Fill ground with stone
2367 // Use fast index incrementing
2368 v3s16 em = vmanip.m_area.getExtent();
2369 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_min, p2d.Y));
2370 for(s16 y=y_nodes_min; y<surface_y && y<=y_nodes_max; y++)
2372 vmanip.m_data[i].d = CONTENT_STONE;
2374 vmanip.m_area.add_y(em, i, 1);
2380 Add some random stone obstacles
2383 for(u32 ri=0; ri<10; ri++)
2385 // The size of these could actually be m_chunksize*MAP_BLOCKSIZE*2
2387 myrand_range(5, stone_obstacle_max_size),
2388 myrand_range(0, 15),
2389 myrand_range(5, stone_obstacle_max_size)
2392 myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1),
2393 myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1)
2396 for(s16 x=-ob_size.X/2; x<ob_size.X/2; x++)
2397 for(s16 z=-ob_size.Z/2; z<ob_size.Z/2; z++)
2399 // Node position in 2d
2400 v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + ob_place + v2s16(x,z);
2402 // Find stone ground level
2403 // (ignore everything else than mud in already generated chunks)
2404 // and mud amount over the stone level
2408 v3s16 em = vmanip.m_area.getExtent();
2409 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
2411 // Go to ground level
2412 for(y=y_nodes_max; y>=y_nodes_min; y--)
2414 MapNode &n = vmanip.m_data[i];
2415 /*if(content_walkable(n.d)
2416 && n.d != CONTENT_MUD
2417 && n.d != CONTENT_GRASS)
2419 if(n.d == CONTENT_STONE)
2422 if(n.d == CONTENT_MUD || n.d == CONTENT_GRASS)
2425 vmanip.m_area.add_y(em, i, -1);
2427 if(y >= y_nodes_min)
2430 surface_y = y_nodes_min;
2438 v3s16 em = vmanip.m_area.getExtent();
2439 s16 y_start = surface_y+1;
2440 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
2444 for(y=y_start; y<=y_nodes_max; y++)
2446 MapNode &n = vmanip.m_data[i];
2447 n.d = CONTENT_STONE;
2449 if(count >= ob_size.Y)
2452 vmanip.m_area.add_y(em, i, 1);
2456 for(; y<=y_nodes_max; y++)
2458 MapNode &n = vmanip.m_data[i];
2461 if(count >= mud_amount)
2464 vmanip.m_area.add_y(em, i, 1);
2474 for(u32 jj=0; jj<2; jj++)
2476 s16 max_tunnel_diameter = 8;
2478 // Allowed route area size in nodes
2480 sectorpos_base_size*MAP_BLOCKSIZE,
2481 h_blocks*MAP_BLOCKSIZE,
2482 sectorpos_base_size*MAP_BLOCKSIZE
2485 // Area starting point in nodes
2487 sectorpos_base.X*MAP_BLOCKSIZE,
2488 y_blocks_min*MAP_BLOCKSIZE,
2489 sectorpos_base.Y*MAP_BLOCKSIZE
2493 //(this should be more than the maximum radius of the tunnel)
2494 s16 more = max_spread_amount - max_tunnel_diameter/2 - 1;
2495 ar += v3s16(1,0,1) * more * 2;
2496 of -= v3s16(1,0,1) * more;
2498 // Randomize starting position
2500 (float)(myrand()%ar.X)+0.5,
2501 (float)(myrand()%ar.Y)+0.5,
2502 (float)(myrand()%ar.Z)+0.5
2505 MapNode airnode(CONTENT_AIR);
2508 Generate some tunnel starting from orp
2511 for(u16 j=0; j<10; j++)
2514 (float)(myrand()%ar.X)+0.5,
2515 (float)(myrand()%ar.Y)+0.5,
2516 (float)(myrand()%ar.Z)+0.5
2518 v3f vec = rp - orp;*/
2520 v3s16 maxlen(60, 10, 60);
2522 (float)(myrand()%(maxlen.X*2))-(float)maxlen.X,
2523 (float)(myrand()%(maxlen.Y*2))-(float)maxlen.Y,
2524 (float)(myrand()%(maxlen.Z*2))-(float)maxlen.Z
2529 else if(rp.X >= ar.X)
2533 else if(rp.Y >= ar.Y)
2537 else if(rp.Z >= ar.Z)
2543 s16 max_d = max_tunnel_diameter;
2544 s16 rs = myrand_range(min_d, max_d);
2546 for(float f=0; f<1.0; f+=1.0/vec.getLength())
2548 v3f fp = orp + vec * f;
2549 v3s16 cp(fp.X, fp.Y, fp.Z);
2551 s16 d1 = d0 + rs - 1;
2552 for(s16 z0=d0; z0<=d1; z0++)
2554 s16 si = rs - abs(z0);
2555 for(s16 x0=-si; x0<=si-1; x0++)
2557 s16 si2 = rs - abs(x0);
2558 for(s16 y0=-si2+1; y0<=si2-1; y0++)
2564 /*if(isInArea(p, ar) == false)
2566 // Check only height
2567 if(y < 0 || y >= ar.Y)
2571 assert(vmanip.m_area.contains(p));
2573 // Just set it to air, it will be changed to
2575 u32 i = vmanip.m_area.index(p);
2576 vmanip.m_data[i] = airnode;
2590 for(u32 jj=0; jj<1000; jj++)
2592 s16 max_vein_diameter = 3;
2594 // Allowed route area size in nodes
2596 sectorpos_base_size*MAP_BLOCKSIZE,
2597 h_blocks*MAP_BLOCKSIZE,
2598 sectorpos_base_size*MAP_BLOCKSIZE
2601 // Area starting point in nodes
2603 sectorpos_base.X*MAP_BLOCKSIZE,
2604 y_blocks_min*MAP_BLOCKSIZE,
2605 sectorpos_base.Y*MAP_BLOCKSIZE
2609 //(this should be more than the maximum radius of the tunnel)
2610 s16 more = max_spread_amount - max_vein_diameter/2 - 1;
2611 ar += v3s16(1,0,1) * more * 2;
2612 of -= v3s16(1,0,1) * more;
2614 // Randomize starting position
2616 (float)(myrand()%ar.X)+0.5,
2617 (float)(myrand()%ar.Y)+0.5,
2618 (float)(myrand()%ar.Z)+0.5
2621 // Randomize mineral
2622 u8 mineral = myrand_range(1, MINERAL_COUNT-1);
2625 Generate some vein starting from orp
2628 for(u16 j=0; j<2; j++)
2631 (float)(myrand()%ar.X)+0.5,
2632 (float)(myrand()%ar.Y)+0.5,
2633 (float)(myrand()%ar.Z)+0.5
2635 v3f vec = rp - orp;*/
2637 v3s16 maxlen(10, 10, 10);
2639 (float)(myrand()%(maxlen.X*2))-(float)maxlen.X,
2640 (float)(myrand()%(maxlen.Y*2))-(float)maxlen.Y,
2641 (float)(myrand()%(maxlen.Z*2))-(float)maxlen.Z
2646 else if(rp.X >= ar.X)
2650 else if(rp.Y >= ar.Y)
2654 else if(rp.Z >= ar.Z)
2660 s16 max_d = max_vein_diameter;
2661 s16 rs = myrand_range(min_d, max_d);
2663 for(float f=0; f<1.0; f+=1.0/vec.getLength())
2665 v3f fp = orp + vec * f;
2666 v3s16 cp(fp.X, fp.Y, fp.Z);
2668 s16 d1 = d0 + rs - 1;
2669 for(s16 z0=d0; z0<=d1; z0++)
2671 s16 si = rs - abs(z0);
2672 for(s16 x0=-si; x0<=si-1; x0++)
2674 s16 si2 = rs - abs(x0);
2675 for(s16 y0=-si2+1; y0<=si2-1; y0++)
2677 // Don't put mineral to every place
2685 /*if(isInArea(p, ar) == false)
2687 // Check only height
2688 if(y < 0 || y >= ar.Y)
2692 assert(vmanip.m_area.contains(p));
2694 // Just set it to air, it will be changed to
2696 u32 i = vmanip.m_area.index(p);
2697 MapNode *n = &vmanip.m_data[i];
2698 if(n->d == CONTENT_STONE)
2711 Add mud to the central chunk
2714 for(s16 x=0; x<sectorpos_base_size*MAP_BLOCKSIZE; x++)
2715 for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++)
2717 // Node position in 2d
2718 v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
2720 // Find ground level
2721 s16 surface_y = find_ground_level(vmanip, p2d);
2728 v3s16 em = vmanip.m_area.getExtent();
2729 s16 y_start = surface_y+1;
2730 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
2731 for(s16 y=y_start; y<=y_nodes_max; y++)
2733 MapNode &n = vmanip.m_data[i];
2739 vmanip.m_area.add_y(em, i, 1);
2746 Flow mud away from steep edges
2749 // Iterate a few times
2750 for(s16 k=0; k<4; k++)
2753 for(s16 x=0-max_spread_amount+1;
2754 x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount-1;
2756 for(s16 z=0-max_spread_amount+1;
2757 z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount-1;
2760 // Node position in 2d
2761 v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
2763 v3s16 em = vmanip.m_area.getExtent();
2764 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
2766 // Go to ground level
2767 for(y=y_nodes_max; y>=y_nodes_min; y--)
2769 MapNode &n = vmanip.m_data[i];
2770 //if(n.d != CONTENT_AIR)
2771 if(content_walkable(n.d))
2774 vmanip.m_area.add_y(em, i, -1);
2777 // If not mud, do nothing to it
2778 MapNode *n = &vmanip.m_data[i];
2779 if(n->d != CONTENT_MUD)
2783 v3s16(0,0,1), // back
2784 v3s16(1,0,0), // right
2785 v3s16(0,0,-1), // front
2786 v3s16(-1,0,0), // left
2791 for(u32 di=0; di<4; di++)
2793 v3s16 dirp = dirs4[di];
2795 // Check that side is air
2796 vmanip.m_area.add_p(em, i2, dirp);
2797 MapNode *n2 = &vmanip.m_data[i2];
2798 if(content_walkable(n2->d))
2800 // Check that under side is air
2801 vmanip.m_area.add_y(em, i2, -1);
2802 n2 = &vmanip.m_data[i2];
2803 if(content_walkable(n2->d))
2805 // Loop further down until not air
2807 vmanip.m_area.add_y(em, i2, -1);
2808 n2 = &vmanip.m_data[i2];
2809 }while(content_walkable(n2->d) == false);
2810 // Loop one up so that we're in air
2811 vmanip.m_area.add_y(em, i2, 1);
2812 n2 = &vmanip.m_data[i2];
2814 // Move mud to new place
2816 // Set old place to be air
2817 *n = MapNode(CONTENT_AIR);
2820 // Switch mud and other and change mud source to air
2821 //MapNode tempnode = *n2;
2824 // Force old mud position to be air
2836 Add water to the central chunk (and a bit more)
2839 for(s16 x=0-max_spread_amount;
2840 x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount;
2842 for(s16 z=0-max_spread_amount;
2843 z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount;
2846 // Node position in 2d
2847 v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
2849 // Find ground level
2850 s16 surface_y = find_ground_level(vmanip, p2d);
2852 // If ground level is over water level, skip.
2853 if(surface_y > WATER_LEVEL)
2860 v3s16 em = vmanip.m_area.getExtent();
2861 s16 y_start = WATER_LEVEL;
2862 u8 light = LIGHT_MAX;
2863 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
2864 for(s16 y=y_start; y>=y_nodes_min; y--)
2866 MapNode *n = &vmanip.m_data[i];
2868 // Fill gaps inside water, too
2869 if(n->d != CONTENT_AIR && n->d != CONTENT_WATERSOURCE
2870 && n->d != CONTENT_WATER)
2873 n->d = CONTENT_WATERSOURCE;
2874 n->setLight(LIGHTBANK_DAY, light);
2877 Add to transforming liquid queue (in case it'd
2880 v3s16 p = v3s16(p2d.X, y, p2d.Y);
2881 m_transforming_liquid.push_back(p);
2884 vmanip.m_area.add_y(em, i, -1);
2898 u32 count = myrand_range(0, tree_max);
2899 for(u32 i=0; i<count; i++)
2901 s16 x = myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1);
2902 s16 z = myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1);
2903 x += sectorpos_base.X*MAP_BLOCKSIZE;
2904 z += sectorpos_base.Y*MAP_BLOCKSIZE;
2905 s16 y = find_ground_level(vmanip, v2s16(x,z));
2906 // Don't make a tree under water level
2911 make_tree(vmanip, p);
2919 for(s16 x=0-4; x<sectorpos_base_size*MAP_BLOCKSIZE+4; x++)
2920 for(s16 z=0-4; z<sectorpos_base_size*MAP_BLOCKSIZE+4; z++)
2922 // Node position in 2d
2923 v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
2926 Find the lowest surface to which enough light ends up
2929 Basically just wait until not air and not leaves.
2933 v3s16 em = vmanip.m_area.getExtent();
2934 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_nodes_max, p2d.Y));
2936 // Go to ground level
2937 for(y=y_nodes_max; y>=y_nodes_min; y--)
2939 MapNode &n = vmanip.m_data[i];
2940 if(n.d != CONTENT_AIR
2941 && n.d != CONTENT_LEAVES)
2943 vmanip.m_area.add_y(em, i, -1);
2945 if(y >= y_nodes_min)
2948 surface_y = y_nodes_min;
2951 u32 i = vmanip.m_area.index(p2d.X, surface_y, p2d.Y);
2952 MapNode *n = &vmanip.m_data[i];
2953 if(n->d == CONTENT_MUD)
2954 n->d = CONTENT_GRASS;
2961 core::map<v3s16, bool> light_sources;
2963 /*for(s16 x=0; x<sectorpos_base_size*MAP_BLOCKSIZE; x++)
2964 for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++)*/
2965 for(s16 x=0-max_spread_amount;
2966 x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount;
2968 for(s16 z=0-max_spread_amount;
2969 z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount;
2972 // Node position in 2d
2973 v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z);
2976 Apply initial sunlight
2979 v3s16 em = vmanip.m_area.getExtent();
2980 s16 y_start = y_nodes_max;
2981 u8 light = LIGHT_SUN;
2982 u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y));
2983 for(s16 y=y_start; y>=y_nodes_min; y--)
2985 MapNode *n = &vmanip.m_data[i];
2987 if(light_propagates_content(n->d) == false)
2991 else if(light != LIGHT_SUN
2992 || sunlight_propagates_content(n->d) == false)
2998 n->setLight(LIGHTBANK_DAY, light);
2999 n->setLight(LIGHTBANK_NIGHT, 0);
3003 // Insert light source
3004 light_sources.insert(v3s16(p2d.X, y, p2d.Y), true);
3007 // Increment index by y
3008 vmanip.m_area.add_y(em, i, -1);
3013 // Spread light around
3015 TimeTaker timer("generateChunk() spreadLight");
3016 vmanip.spreadLight(LIGHTBANK_DAY, light_sources);
3023 timer_generate.stop();
3026 Blit generated stuff to map
3028 core::map<v3s16, MapBlock*> modified_blocks;
3030 TimeTaker timer("generateChunk() blitBackAll");
3031 vmanip.blitBackAll(&modified_blocks);
3034 Update day/night difference cache of the MapBlocks
3037 for(core::map<v3s16, MapBlock*>::Iterator i = modified_blocks.getIterator();
3038 i.atEnd() == false; i++)
3040 MapBlock *block = i.getNode()->getValue();
3041 block->updateDayNightDiff();
3047 Create chunks and set them volatile
3050 for(s16 x=-1; x<=1; x++)
3051 for(s16 y=-1; y<=1; y++)
3053 v2s16 chunkpos0 = chunkpos + v2s16(x,y);
3054 // Add chunk meta information
3055 MapChunk *chunk = getChunk(chunkpos);
3058 chunk = new MapChunk();
3059 m_chunks.insert(chunkpos0, chunk);
3061 chunk->setIsVolatile(true);
3065 Set central chunk non-volatile and return it
3067 MapChunk *chunk = getChunk(chunkpos);
3070 chunk->setIsVolatile(false);
3076 ServerMapSector * ServerMap::generateSector(v2s16 p2d)
3078 DSTACK("%s: p2d=(%d,%d)",
3082 // Check that it doesn't exist already
3083 ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d);
3088 If there is no master heightmap, throw.
3090 if(m_heightmap == NULL)
3092 throw InvalidPositionException("generateSector(): no heightmap");
3096 Do not generate over-limit
3098 if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
3099 || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
3100 || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
3101 || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
3102 throw InvalidPositionException("generateSector(): pos. over limit");
3105 Generate sector and heightmaps
3108 // Number of heightmaps in sector in each direction
3109 u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
3111 // Heightmap side width
3112 s16 hm_d = MAP_BLOCKSIZE / hm_split;
3114 sector = new ServerMapSector(this, p2d, hm_split);
3116 // Sector position on map in nodes
3117 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
3119 /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
3120 " heightmaps and objects"<<std::endl;*/
3123 Calculate some information about local properties
3126 v2s16 mhm_p = p2d * hm_split;
3128 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
3129 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
3130 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
3131 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
3134 float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0;
3135 float avgslope = 0.0;
3136 avgslope += fabs(avgheight - corners[0]);
3137 avgslope += fabs(avgheight - corners[1]);
3138 avgslope += fabs(avgheight - corners[2]);
3139 avgslope += fabs(avgheight - corners[3]);
3141 avgslope /= MAP_BLOCKSIZE;
3142 //dstream<<"avgslope="<<avgslope<<std::endl;
3144 float pitness = 0.0;
3146 a = m_heightmap->getSlope(p2d+v2s16(0,0));
3149 a = m_heightmap->getSlope(p2d+v2s16(0,1));
3152 a = m_heightmap->getSlope(p2d+v2s16(1,1));
3155 a = m_heightmap->getSlope(p2d+v2s16(1,0));
3159 pitness /= MAP_BLOCKSIZE;
3160 //dstream<<"pitness="<<pitness<<std::endl;
3163 Get local attributes
3166 float local_plants_amount = 0.5;
3170 //dstream<<"generateSector(): Reading point attribute lists"<<std::endl;
3171 //TimeTaker attrtimer("generateSector() attribute fetch");
3173 // Get plant amount from attributes
3174 PointAttributeList *palist = m_padb.getList("plants_amount");
3176 /*local_plants_amount =
3177 palist->getNearAttr(nodepos2d).getFloat();*/
3178 local_plants_amount =
3179 palist->getInterpolatedFloat(nodepos2d);
3184 Generate sector heightmap
3187 // Loop through sub-heightmaps
3188 for(s16 y=0; y<hm_split; y++)
3189 for(s16 x=0; x<hm_split; x++)
3191 v2s16 p_in_sector = v2s16(x,y);
3192 v2s16 mhm_p = p2d * hm_split + p_in_sector;
3194 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
3195 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
3196 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
3197 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
3200 /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
3201 <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
3204 FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
3206 sector->setHeightmap(p_in_sector, hm);
3208 //TODO: Make these values configurable
3209 //hm->generateContinued(0.0, 0.0, corners);
3210 //hm->generateContinued(0.25, 0.2, corners);
3211 //hm->generateContinued(0.5, 0.2, corners);
3212 //hm->generateContinued(1.0, 0.2, corners);
3213 //hm->generateContinued(2.0, 0.2, corners);
3214 //hm->generateContinued(2.0 * avgslope, 0.5, corners);
3215 hm->generateContinued(avgslope * MAP_BLOCKSIZE/8, 0.5, corners);
3224 core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
3225 sector->setObjects(objects);
3227 float area = MAP_BLOCKSIZE * MAP_BLOCKSIZE;
3230 Plant some trees if there is not much slope
3233 // Avgslope is the derivative of a hill
3234 //float t = avgslope * avgslope;
3236 float a = area/16 * m_params.plants_amount * local_plants_amount;
3238 //float something = 0.17*0.17;
3239 float something = 0.3;
3241 tree_max = a / (t/something);
3245 u32 count = (myrand()%(tree_max+1));
3246 //u32 count = tree_max;
3247 for(u32 i=0; i<count; i++)
3249 s16 x = (myrand()%(MAP_BLOCKSIZE-2))+1;
3250 s16 z = (myrand()%(MAP_BLOCKSIZE-2))+1;
3251 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
3254 objects->insert(v3s16(x, y, z),
3255 SECTOR_OBJECT_TREE_1);
3259 Plant some bushes if sector is pit-like
3262 // Pitness usually goes at around -0.5...0.5
3264 u32 a = area/16 * 3.0 * m_params.plants_amount * local_plants_amount;
3266 bush_max = (pitness*a*4);
3269 u32 count = (myrand()%(bush_max+1));
3270 for(u32 i=0; i<count; i++)
3272 s16 x = myrand()%(MAP_BLOCKSIZE-0)+0;
3273 s16 z = myrand()%(MAP_BLOCKSIZE-0)+0;
3274 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
3277 objects->insert(v3s16(x, y, z),
3278 SECTOR_OBJECT_BUSH_1);
3282 Add ravine (randomly)
3284 if(m_params.ravines_amount > 0.001)
3286 if(myrand()%(s32)(200.0 / m_params.ravines_amount) == 0)
3289 s16 x = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
3290 s16 z = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
3293 s16 y = sector->getGroundHeight(v2s16(x,z))+1;
3294 objects->insert(v3s16(x, y, z),
3295 SECTOR_OBJECT_RAVINE);
3302 m_sectors.insert(p2d, sector);
3308 ServerMapSector * ServerMap::createSector(v2s16 p2d)
3310 DSTACK("%s: p2d=(%d,%d)",
3315 Check if it exists already in memory
3317 ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d);
3322 Try to load it from disk (with blocks)
3324 if(loadSectorFull(p2d) == true)
3326 ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d);
3329 dstream<<"ServerMap::createSector(): loadSectorFull didn't make a sector"<<std::endl;
3330 throw InvalidPositionException("");
3336 If there is no master heightmap, throw.
3338 if(m_heightmap == NULL)
3340 throw InvalidPositionException("createSector(): no heightmap");
3344 Do not create over-limit
3346 if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
3347 || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
3348 || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
3349 || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
3350 throw InvalidPositionException("createSector(): pos. over limit");
3353 Generate blank sector
3356 // Number of heightmaps in sector in each direction
3357 u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
3359 // Heightmap side width
3360 s16 hm_d = MAP_BLOCKSIZE / hm_split;
3362 sector = new ServerMapSector(this, p2d, hm_split);
3364 // Sector position on map in nodes
3365 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
3367 /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
3368 " heightmaps and objects"<<std::endl;*/
3371 Generate sector heightmap
3374 v2s16 mhm_p = p2d * hm_split;
3375 /*f32 corners[4] = {
3376 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
3377 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
3378 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
3379 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
3382 // Loop through sub-heightmaps
3383 for(s16 y=0; y<hm_split; y++)
3384 for(s16 x=0; x<hm_split; x++)
3386 v2s16 p_in_sector = v2s16(x,y);
3387 v2s16 mhm_p = p2d * hm_split + p_in_sector;
3389 m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
3390 m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
3391 m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
3392 m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
3395 /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
3396 <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
3399 FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
3401 sector->setHeightmap(p_in_sector, hm);
3403 //hm->generateContinued(1.0, 0.5, corners);
3404 hm->generateContinued(0.5, 0.5, corners);
3409 // Add dummy objects
3410 core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
3411 sector->setObjects(objects);
3416 m_sectors.insert(p2d, sector);
3421 MapSector * ServerMap::emergeSector(v2s16 p2d)
3423 DSTACK("%s: p2d=(%d,%d)",
3430 v2s16 chunkpos = sector_to_chunk(p2d);
3431 bool chunk_exists = false;
3432 MapChunk *chunk = getChunk(chunkpos);
3433 if(chunk && chunk->getIsVolatile() == false)
3434 chunk_exists = true;
3437 If chunk is not fully generated, generate chunk
3439 if(chunk_exists == false)
3441 // Generate chunk and neighbors
3442 generateChunk(chunkpos);
3446 Return sector if it exists now
3448 MapSector *sector = getSectorNoGenerateNoEx(p2d);
3453 Try to load it from disk
3455 if(loadSectorFull(p2d) == true)
3457 MapSector *sector = getSectorNoGenerateNoEx(p2d);
3460 dstream<<"ServerMap::emergeSector(): loadSectorFull didn't make a sector"<<std::endl;
3461 throw InvalidPositionException("");
3467 generateChunk should have generated the sector
3474 //return generateSector();
3477 MapBlock * ServerMap::generateBlock(
3479 MapBlock *original_dummy,
3480 ServerMapSector *sector,
3481 core::map<v3s16, MapBlock*> &changed_blocks,
3482 core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
3485 DSTACK("%s: p=(%d,%d,%d)",
3489 /*dstream<<"generateBlock(): "
3490 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
3493 MapBlock *block = original_dummy;
3495 v2s16 p2d(p.X, p.Z);
3499 Do not generate over-limit
3501 if(blockpos_over_limit(p))
3503 dstream<<__FUNCTION_NAME<<": Block position over limit"<<std::endl;
3504 throw InvalidPositionException("generateBlock(): pos. over limit");
3508 If block doesn't exist, create one.
3509 If it exists, it is a dummy. In that case unDummify() it.
3511 NOTE: This already sets the map as the parent of the block
3515 block = sector->createBlankBlockNoInsert(block_y);
3519 // Remove the block so that nobody can get a half-generated one.
3520 sector->removeBlock(block);
3521 // Allocate the block to contain the generated data
3525 /*u8 water_material = CONTENT_WATER;
3526 if(g_settings.getBool("endless_water"))
3527 water_material = CONTENT_WATERSOURCE;*/
3528 u8 water_material = CONTENT_WATERSOURCE;
3530 s32 lowest_ground_y = 32767;
3531 s32 highest_ground_y = -32768;
3534 //sector->printHeightmaps();
3536 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
3537 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
3539 //dstream<<"generateBlock: x0="<<x0<<", z0="<<z0<<std::endl;
3541 float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
3542 //assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
3543 if(surface_y_f < GROUNDHEIGHT_VALID_MINVALUE)
3545 dstream<<"WARNING: Surface height not found in sector "
3546 "for block that is being emerged"<<std::endl;
3550 s16 surface_y = surface_y_f;
3551 //avg_ground_y += surface_y;
3552 if(surface_y < lowest_ground_y)
3553 lowest_ground_y = surface_y;
3554 if(surface_y > highest_ground_y)
3555 highest_ground_y = surface_y;
3557 s32 surface_depth = 0;
3559 float slope = sector->getSlope(v2s16(x0,z0)).getLength();
3561 //float min_slope = 0.45;
3562 //float max_slope = 0.85;
3563 float min_slope = 0.60;
3564 float max_slope = 1.20;
3565 float min_slope_depth = 5.0;
3566 float max_slope_depth = 0;
3568 if(slope < min_slope)
3569 surface_depth = min_slope_depth;
3570 else if(slope > max_slope)
3571 surface_depth = max_slope_depth;
3573 surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
3575 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
3577 s16 real_y = block_y * MAP_BLOCKSIZE + y0;
3582 NOTE: If there are some man-made structures above the
3583 newly created block, they won't be taken into account.
3585 if(real_y > surface_y)
3586 n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
3592 // If node is over heightmap y, it's air or water
3593 if(real_y > surface_y)
3595 // If under water level, it's water
3596 if(real_y < WATER_LEVEL)
3598 n.d = water_material;
3599 n.setLight(LIGHTBANK_DAY,
3600 diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
3602 Add to transforming liquid queue (in case it'd
3605 v3s16 real_pos = v3s16(x0,y0,z0) + p*MAP_BLOCKSIZE;
3606 m_transforming_liquid.push_back(real_pos);
3612 // Else it's ground or dungeons (air)
3615 // If it's surface_depth under ground, it's stone
3616 if(real_y <= surface_y - surface_depth)
3618 n.d = CONTENT_STONE;
3622 // It is mud if it is under the first ground
3623 // level or under water
3624 if(real_y < WATER_LEVEL || real_y <= surface_y - 1)
3630 n.d = CONTENT_GRASS;
3633 //n.d = CONTENT_MUD;
3635 /*// If under water level, it's mud
3636 if(real_y < WATER_LEVEL)
3638 // Only the topmost node is grass
3639 else if(real_y <= surface_y - 1)
3642 n.d = CONTENT_GRASS;*/
3646 block->setNode(v3s16(x0,y0,z0), n);
3651 Calculate some helper variables
3654 // Completely underground if the highest part of block is under lowest
3656 // This has to be very sure; it's probably one too strict now but
3657 // that's just better.
3658 bool completely_underground =
3659 block_y * MAP_BLOCKSIZE + MAP_BLOCKSIZE < lowest_ground_y;
3661 bool some_part_underground = block_y * MAP_BLOCKSIZE <= highest_ground_y;
3663 bool mostly_underwater_surface = false;
3664 if(highest_ground_y < WATER_LEVEL
3665 && some_part_underground && !completely_underground)
3666 mostly_underwater_surface = true;
3669 Get local attributes
3672 //dstream<<"generateBlock(): Getting local attributes"<<std::endl;
3674 float caves_amount = 0.5;
3679 NOTE: BEWARE: Too big amount of attribute points slows verything
3681 1 interpolation from 5000 points takes 2-3ms.
3683 //TimeTaker timer("generateBlock() local attribute retrieval");
3684 v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
3685 PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
3686 caves_amount = list_caves_amount->getInterpolatedFloat(nodepos2d);
3690 //dstream<<"generateBlock(): Done"<<std::endl;
3696 // Initialize temporary table
3697 const s32 ued = MAP_BLOCKSIZE;
3698 bool underground_emptiness[ued*ued*ued];
3699 for(s32 i=0; i<ued*ued*ued; i++)
3701 underground_emptiness[i] = 0;
3708 Initialize orp and ors. Try to find if some neighboring
3709 MapBlock has a tunnel ended in its side
3713 (float)(myrand()%ued)+0.5,
3714 (float)(myrand()%ued)+0.5,
3715 (float)(myrand()%ued)+0.5
3718 bool found_existing = false;
3724 for(s16 y=0; y<ued; y++)
3725 for(s16 x=0; x<ued; x++)
3727 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
3728 if(getNode(ap).d == CONTENT_AIR)
3730 orp = v3f(x+1,y+1,0);
3731 found_existing = true;
3732 goto continue_generating;
3736 catch(InvalidPositionException &e){}
3742 for(s16 y=0; y<ued; y++)
3743 for(s16 x=0; x<ued; x++)
3745 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
3746 if(getNode(ap).d == CONTENT_AIR)
3748 orp = v3f(x+1,y+1,ued-1);
3749 found_existing = true;
3750 goto continue_generating;
3754 catch(InvalidPositionException &e){}
3760 for(s16 y=0; y<ued; y++)
3761 for(s16 z=0; z<ued; z++)
3763 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
3764 if(getNode(ap).d == CONTENT_AIR)
3766 orp = v3f(0,y+1,z+1);
3767 found_existing = true;
3768 goto continue_generating;
3772 catch(InvalidPositionException &e){}
3778 for(s16 y=0; y<ued; y++)
3779 for(s16 z=0; z<ued; z++)
3781 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
3782 if(getNode(ap).d == CONTENT_AIR)
3784 orp = v3f(ued-1,y+1,z+1);
3785 found_existing = true;
3786 goto continue_generating;
3790 catch(InvalidPositionException &e){}
3796 for(s16 x=0; x<ued; x++)
3797 for(s16 z=0; z<ued; z++)
3799 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
3800 if(getNode(ap).d == CONTENT_AIR)
3802 orp = v3f(x+1,0,z+1);
3803 found_existing = true;
3804 goto continue_generating;
3808 catch(InvalidPositionException &e){}
3814 for(s16 x=0; x<ued; x++)
3815 for(s16 z=0; z<ued; z++)
3817 v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
3818 if(getNode(ap).d == CONTENT_AIR)
3820 orp = v3f(x+1,ued-1,z+1);
3821 found_existing = true;
3822 goto continue_generating;
3826 catch(InvalidPositionException &e){}
3828 continue_generating:
3831 Choose whether to actually generate dungeon
3833 bool do_generate_dungeons = true;
3834 // Don't generate if no part is underground
3835 if(!some_part_underground)
3837 do_generate_dungeons = false;
3839 // Don't generate if mostly underwater surface
3840 /*else if(mostly_underwater_surface)
3842 do_generate_dungeons = false;
3844 // Partly underground = cave
3845 else if(!completely_underground)
3847 do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
3849 // Found existing dungeon underground
3850 else if(found_existing && completely_underground)
3852 do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
3854 // Underground and no dungeons found
3857 do_generate_dungeons = (rand() % 300 <= (s32)(caves_amount*100));
3860 if(do_generate_dungeons)
3863 Generate some tunnel starting from orp and ors
3865 for(u16 i=0; i<3; i++)
3868 (float)(myrand()%ued)+0.5,
3869 (float)(myrand()%ued)+0.5,
3870 (float)(myrand()%ued)+0.5
3874 s16 rs = (myrand()%(max_d-min_d+1))+min_d;
3878 for(float f=0; f<1.0; f+=0.04)
3880 v3f fp = orp + vec * f;
3881 v3s16 cp(fp.X, fp.Y, fp.Z);
3883 s16 d1 = d0 + rs - 1;
3884 for(s16 z0=d0; z0<=d1; z0++)
3886 s16 si = rs - abs(z0);
3887 for(s16 x0=-si; x0<=si-1; x0++)
3889 s16 si2 = rs - abs(x0);
3890 for(s16 y0=-si2+1; y0<=si2-1; y0++)
3896 if(isInArea(p, ued) == false)
3898 underground_emptiness[ued*ued*z + ued*y + x] = 1;
3910 // Set to true if has caves.
3911 // Set when some non-air is changed to air when making caves.
3912 bool has_dungeons = false;
3915 Apply temporary cave data to block
3918 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
3919 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
3921 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
3923 MapNode n = block->getNode(v3s16(x0,y0,z0));
3926 if(underground_emptiness[
3927 ued*ued*(z0*ued/MAP_BLOCKSIZE)
3928 +ued*(y0*ued/MAP_BLOCKSIZE)
3929 +(x0*ued/MAP_BLOCKSIZE)])
3931 if(content_features(n.d).walkable/*is_ground_content(n.d)*/)
3934 has_dungeons = true;
3940 block->setNode(v3s16(x0,y0,z0), n);
3945 This is used for guessing whether or not the block should
3946 receive sunlight from the top if the block above doesn't exist
3948 block->setIsUnderground(completely_underground);
3951 Force lighting update if some part of block is partly
3952 underground and has caves.
3954 /*if(some_part_underground && !completely_underground && has_dungeons)
3956 //dstream<<"Half-ground caves"<<std::endl;
3957 lighting_invalidated_blocks[block->getPos()] = block;
3960 // DEBUG: Always update lighting
3961 //lighting_invalidated_blocks[block->getPos()] = block;
3967 if(some_part_underground)
3969 s16 underground_level = (lowest_ground_y/MAP_BLOCKSIZE - block_y)+1;
3974 for(s16 i=0; i<underground_level/4 + 1; i++)
3976 if(myrand()%50 == 0)
3979 (myrand()%(MAP_BLOCKSIZE-2))+1,
3980 (myrand()%(MAP_BLOCKSIZE-2))+1,
3981 (myrand()%(MAP_BLOCKSIZE-2))+1
3987 for(u16 i=0; i<27; i++)
3989 if(block->getNode(cp+g_27dirs[i]).d == CONTENT_STONE)
3991 block->setNode(cp+g_27dirs[i], n);
3999 u16 coal_amount = 30.0 * g_settings.getFloat("coal_amount");
4000 u16 coal_rareness = 60 / coal_amount;
4001 if(coal_rareness == 0)
4003 if(myrand()%coal_rareness == 0)
4005 u16 a = myrand() % 16;
4006 u16 amount = coal_amount * a*a*a / 1000;
4007 for(s16 i=0; i<amount; i++)
4010 (myrand()%(MAP_BLOCKSIZE-2))+1,
4011 (myrand()%(MAP_BLOCKSIZE-2))+1,
4012 (myrand()%(MAP_BLOCKSIZE-2))+1
4016 n.d = CONTENT_STONE;
4017 n.param = MINERAL_COAL;
4019 for(u16 i=0; i<27; i++)
4021 if(block->getNode(cp+g_27dirs[i]).d == CONTENT_STONE)
4023 block->setNode(cp+g_27dirs[i], n);
4031 //TODO: change to iron_amount or whatever
4032 u16 iron_amount = 30.0 * g_settings.getFloat("coal_amount");
4033 u16 iron_rareness = 60 / iron_amount;
4034 if(iron_rareness == 0)
4036 if(myrand()%iron_rareness == 0)
4038 u16 a = myrand() % 16;
4039 u16 amount = iron_amount * a*a*a / 1000;
4040 for(s16 i=0; i<amount; i++)
4043 (myrand()%(MAP_BLOCKSIZE-2))+1,
4044 (myrand()%(MAP_BLOCKSIZE-2))+1,
4045 (myrand()%(MAP_BLOCKSIZE-2))+1
4049 n.d = CONTENT_STONE;
4050 n.param = MINERAL_IRON;
4052 for(u16 i=0; i<27; i++)
4054 if(block->getNode(cp+g_27dirs[i]).d == CONTENT_STONE)
4056 block->setNode(cp+g_27dirs[i], n);
4063 Create a few rats in empty blocks underground
4065 if(completely_underground)
4067 //for(u16 i=0; i<2; i++)
4070 (myrand()%(MAP_BLOCKSIZE-2))+1,
4071 (myrand()%(MAP_BLOCKSIZE-2))+1,
4072 (myrand()%(MAP_BLOCKSIZE-2))+1
4075 // Check that the place is empty
4076 //if(!is_ground_content(block->getNode(cp).d))
4079 RatObject *obj = new RatObject(NULL, -1, intToFloat(cp));
4080 block->addObject(obj);
4086 Add block to sector.
4088 sector->insertBlock(block);
4094 // An y-wise container of changed blocks
4095 core::map<s16, MapBlock*> changed_blocks_sector;
4098 Check if any sector's objects can be placed now.
4101 core::map<v3s16, u8> *objects = sector->getObjects();
4102 core::list<v3s16> objects_to_remove;
4103 for(core::map<v3s16, u8>::Iterator i = objects->getIterator();
4104 i.atEnd() == false; i++)
4106 v3s16 p = i.getNode()->getKey();
4108 u8 d = i.getNode()->getValue();
4110 // Ground level point (user for stuff that is on ground)
4112 bool ground_found = true;
4114 // Search real ground level
4118 MapNode n = sector->getNode(gp);
4120 // If not air, go one up and continue to placing the tree
4121 if(n.d != CONTENT_AIR)
4127 // If air, go one down
4128 gp += v3s16(0,-1,0);
4130 }catch(InvalidPositionException &e)
4132 // Ground not found.
4133 ground_found = false;
4134 // This is most close to ground
4141 if(d == SECTOR_OBJECT_TEST)
4143 if(sector->isValidArea(p + v3s16(0,0,0),
4144 p + v3s16(0,0,0), &changed_blocks_sector))
4147 n.d = CONTENT_TORCH;
4148 sector->setNode(p, n);
4149 objects_to_remove.push_back(p);
4152 else if(d == SECTOR_OBJECT_TREE_1)
4154 if(ground_found == false)
4157 v3s16 p_min = gp + v3s16(-1,0,-1);
4158 v3s16 p_max = gp + v3s16(1,5,1);
4159 if(sector->isValidArea(p_min, p_max,
4160 &changed_blocks_sector))
4164 sector->setNode(gp+v3s16(0,0,0), n);
4165 sector->setNode(gp+v3s16(0,1,0), n);
4166 sector->setNode(gp+v3s16(0,2,0), n);
4167 sector->setNode(gp+v3s16(0,3,0), n);
4169 n.d = CONTENT_LEAVES;
4171 if(myrand()%4!=0) sector->setNode(gp+v3s16(0,5,0), n);
4173 if(myrand()%3!=0) sector->setNode(gp+v3s16(-1,5,0), n);
4174 if(myrand()%3!=0) sector->setNode(gp+v3s16(1,5,0), n);
4175 if(myrand()%3!=0) sector->setNode(gp+v3s16(0,5,-1), n);
4176 if(myrand()%3!=0) sector->setNode(gp+v3s16(0,5,1), n);
4177 /*if(myrand()%3!=0) sector->setNode(gp+v3s16(1,5,1), n);
4178 if(myrand()%3!=0) sector->setNode(gp+v3s16(-1,5,1), n);
4179 if(myrand()%3!=0) sector->setNode(gp+v3s16(-1,5,-1), n);
4180 if(myrand()%3!=0) sector->setNode(gp+v3s16(1,5,-1), n);*/
4182 sector->setNode(gp+v3s16(0,4,0), n);
4184 sector->setNode(gp+v3s16(-1,4,0), n);
4185 sector->setNode(gp+v3s16(1,4,0), n);
4186 sector->setNode(gp+v3s16(0,4,-1), n);
4187 sector->setNode(gp+v3s16(0,4,1), n);
4188 sector->setNode(gp+v3s16(1,4,1), n);
4189 sector->setNode(gp+v3s16(-1,4,1), n);
4190 sector->setNode(gp+v3s16(-1,4,-1), n);
4191 sector->setNode(gp+v3s16(1,4,-1), n);
4193 sector->setNode(gp+v3s16(-1,3,0), n);
4194 sector->setNode(gp+v3s16(1,3,0), n);
4195 sector->setNode(gp+v3s16(0,3,-1), n);
4196 sector->setNode(gp+v3s16(0,3,1), n);
4197 sector->setNode(gp+v3s16(1,3,1), n);
4198 sector->setNode(gp+v3s16(-1,3,1), n);
4199 sector->setNode(gp+v3s16(-1,3,-1), n);
4200 sector->setNode(gp+v3s16(1,3,-1), n);
4202 if(myrand()%3!=0) sector->setNode(gp+v3s16(-1,2,0), n);
4203 if(myrand()%3!=0) sector->setNode(gp+v3s16(1,2,0), n);
4204 if(myrand()%3!=0) sector->setNode(gp+v3s16(0,2,-1), n);
4205 if(myrand()%3!=0) sector->setNode(gp+v3s16(0,2,1), n);
4206 /*if(myrand()%3!=0) sector->setNode(gp+v3s16(1,2,1), n);
4207 if(myrand()%3!=0) sector->setNode(gp+v3s16(-1,2,1), n);
4208 if(myrand()%3!=0) sector->setNode(gp+v3s16(-1,2,-1), n);
4209 if(myrand()%3!=0) sector->setNode(gp+v3s16(1,2,-1), n);*/
4211 // Objects are identified by wanted position
4212 objects_to_remove.push_back(p);
4214 // Lighting has to be recalculated for this one.
4215 sector->getBlocksInArea(p_min, p_max,
4216 lighting_invalidated_blocks);
4219 else if(d == SECTOR_OBJECT_BUSH_1)
4221 if(ground_found == false)
4224 if(sector->isValidArea(gp + v3s16(0,0,0),
4225 gp + v3s16(0,0,0), &changed_blocks_sector))
4228 n.d = CONTENT_LEAVES;
4229 sector->setNode(gp+v3s16(0,0,0), n);
4231 // Objects are identified by wanted position
4232 objects_to_remove.push_back(p);
4235 else if(d == SECTOR_OBJECT_RAVINE)
4238 v3s16 p_min = p + v3s16(-6,maxdepth,-6);
4239 v3s16 p_max = p + v3s16(6,6,6);
4240 if(sector->isValidArea(p_min, p_max,
4241 &changed_blocks_sector))
4244 n.d = CONTENT_STONE;
4247 s16 depth = maxdepth + (myrand()%10);
4249 s16 minz = -6 - (-2);
4251 for(s16 x=-6; x<=6; x++)
4253 z += -1 + (myrand()%3);
4258 for(s16 y=depth+(myrand()%2); y<=6; y++)
4260 /*std::cout<<"("<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
4263 v3s16 p2 = p + v3s16(x,y,z-2);
4264 //if(is_ground_content(sector->getNode(p2).d))
4265 if(content_features(sector->getNode(p2).d).walkable)
4266 sector->setNode(p2, n);
4269 v3s16 p2 = p + v3s16(x,y,z-1);
4270 if(content_features(sector->getNode(p2).d).walkable)
4271 sector->setNode(p2, n2);
4274 v3s16 p2 = p + v3s16(x,y,z+0);
4275 if(content_features(sector->getNode(p2).d).walkable)
4276 sector->setNode(p2, n2);
4279 v3s16 p2 = p + v3s16(x,y,z+1);
4280 if(content_features(sector->getNode(p2).d).walkable)
4281 sector->setNode(p2, n);
4284 //if(sector->getNode(p+v3s16(x,y,z+1)).solidness()==2)
4285 //if(p.Y+y <= sector->getGroundHeight(p2d+v2s16(x,z-2))+0.5)
4289 objects_to_remove.push_back(p);
4291 // Lighting has to be recalculated for this one.
4292 sector->getBlocksInArea(p_min, p_max,
4293 lighting_invalidated_blocks);
4298 dstream<<"ServerMap::generateBlock(): "
4299 "Invalid heightmap object"
4304 catch(InvalidPositionException &e)
4306 dstream<<"WARNING: "<<__FUNCTION_NAME
4307 <<": while inserting object "<<(int)d
4308 <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<"):"
4309 <<" InvalidPositionException.what()="
4310 <<e.what()<<std::endl;
4311 // This is not too fatal and seems to happen sometimes.
4316 for(core::list<v3s16>::Iterator i = objects_to_remove.begin();
4317 i != objects_to_remove.end(); i++)
4319 objects->remove(*i);
4323 Translate sector's changed blocks to global changed blocks
4326 for(core::map<s16, MapBlock*>::Iterator
4327 i = changed_blocks_sector.getIterator();
4328 i.atEnd() == false; i++)
4330 MapBlock *block = i.getNode()->getValue();
4332 changed_blocks.insert(block->getPos(), block);
4335 block->setLightingExpired(true);
4342 <<"lighting_invalidated_blocks.size()"
4346 <<" "<<lighting_invalidated_blocks.size()
4347 <<", "<<has_dungeons
4348 <<", "<<completely_underground
4349 <<", "<<some_part_underground
4356 MapBlock * ServerMap::createBlock(v3s16 p)
4358 DSTACK("%s: p=(%d,%d,%d)",
4359 __FUNCTION_NAME, p.X, p.Y, p.Z);
4361 v2s16 p2d(p.X, p.Z);
4364 This will create or load a sector if not found in memory.
4365 If block exists on disk, it will be loaded.
4367 NOTE: On old save formats, this will be slow, as it generates
4368 lighting on blocks for them.
4370 ServerMapSector *sector;
4372 sector = (ServerMapSector*)createSector(p2d);
4373 assert(sector->getId() == MAPSECTOR_SERVER);
4375 /*catch(InvalidPositionException &e)
4377 dstream<<"createBlock: createSector() failed"<<std::endl;
4380 catch(std::exception &e)
4382 dstream<<"createBlock: createSector() failed: "
4383 <<e.what()<<std::endl;
4388 Try to get a block from the sector
4391 MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
4395 block = sector->createBlankBlock(block_y);
4399 MapBlock * ServerMap::emergeBlock(
4401 bool only_from_disk,
4402 core::map<v3s16, MapBlock*> &changed_blocks,
4403 core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
4406 DSTACK("%s: p=(%d,%d,%d), only_from_disk=%d",
4408 p.X, p.Y, p.Z, only_from_disk);
4410 v2s16 p2d(p.X, p.Z);
4413 This will create or load a sector if not found in memory.
4414 If block exists on disk, it will be loaded.
4416 NOTE: On old save formats, this will be slow, as it generates
4417 lighting on blocks for them.
4419 ServerMapSector *sector;
4421 sector = (ServerMapSector*)emergeSector(p2d);
4422 assert(sector->getId() == MAPSECTOR_SERVER);
4424 /*catch(InvalidPositionException &e)
4426 dstream<<"emergeBlock: emergeSector() failed"<<std::endl;
4429 catch(std::exception &e)
4431 dstream<<"emergeBlock: emergeSector() failed: "
4432 <<e.what()<<std::endl;
4437 Try to get a block from the sector
4440 bool does_not_exist = false;
4441 bool lighting_expired = false;
4442 MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
4446 does_not_exist = true;
4448 else if(block->isDummy() == true)
4450 does_not_exist = true;
4452 else if(block->getLightingExpired())
4454 lighting_expired = true;
4459 //dstream<<"emergeBlock(): Returning already valid block"<<std::endl;
4464 If block was not found on disk and not going to generate a
4465 new one, make sure there is a dummy block in place.
4467 if(only_from_disk && (does_not_exist || lighting_expired))
4469 //dstream<<"emergeBlock(): Was not on disk but not generating"<<std::endl;
4473 // Create dummy block
4474 block = new MapBlock(this, p, true);
4476 // Add block to sector
4477 sector->insertBlock(block);
4483 //dstream<<"Not found on disk, generating."<<std::endl;
4485 //TimeTaker("emergeBlock() generate");
4487 //dstream<<"emergeBlock(): Didn't find valid block -> making one"<<std::endl;
4490 If the block doesn't exist, generate the block.
4494 block = generateBlock(p, block, sector, changed_blocks,
4495 lighting_invalidated_blocks);
4498 if(lighting_expired)
4500 lighting_invalidated_blocks.insert(p, block);
4504 Initially update sunlight
4508 core::map<v3s16, bool> light_sources;
4509 bool black_air_left = false;
4510 bool bottom_invalid =
4511 block->propagateSunlight(light_sources, true,
4512 &black_air_left, true);
4514 // If sunlight didn't reach everywhere and part of block is
4515 // above ground, lighting has to be properly updated
4516 //if(black_air_left && some_part_underground)
4519 lighting_invalidated_blocks[block->getPos()] = block;
4524 lighting_invalidated_blocks[block->getPos()] = block;
4529 Debug mode operation
4531 bool haxmode = g_settings.getBool("haxmode");
4534 // Don't calculate lighting at all
4535 //lighting_invalidated_blocks.clear();
4541 void ServerMap::createDir(std::string path)
4543 if(fs::CreateDir(path) == false)
4545 m_dout<<DTIME<<"ServerMap: Failed to create directory "
4546 <<"\""<<path<<"\""<<std::endl;
4547 throw BaseException("ServerMap failed to create directory");
4551 std::string ServerMap::getSectorSubDir(v2s16 pos)
4554 snprintf(cc, 9, "%.4x%.4x",
4555 (unsigned int)pos.X&0xffff,
4556 (unsigned int)pos.Y&0xffff);
4558 return std::string(cc);
4561 std::string ServerMap::getSectorDir(v2s16 pos)
4563 return m_savedir + "/sectors/" + getSectorSubDir(pos);
4566 v2s16 ServerMap::getSectorPos(std::string dirname)
4568 if(dirname.size() != 8)
4569 throw InvalidFilenameException("Invalid sector directory name");
4571 int r = sscanf(dirname.c_str(), "%4x%4x", &x, &y);
4573 throw InvalidFilenameException("Invalid sector directory name");
4574 v2s16 pos((s16)x, (s16)y);
4578 v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
4580 v2s16 p2d = getSectorPos(sectordir);
4582 if(blockfile.size() != 4){
4583 throw InvalidFilenameException("Invalid block filename");
4586 int r = sscanf(blockfile.c_str(), "%4x", &y);
4588 throw InvalidFilenameException("Invalid block filename");
4589 return v3s16(p2d.X, y, p2d.Y);
4593 #define ENABLE_SECTOR_SAVING 1
4594 #define ENABLE_SECTOR_LOADING 1
4595 #define ENABLE_BLOCK_SAVING 1
4596 #define ENABLE_BLOCK_LOADING 1
4598 void ServerMap::save(bool only_changed)
4600 DSTACK(__FUNCTION_NAME);
4601 if(m_map_saving_enabled == false)
4603 dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
4607 if(only_changed == false)
4608 dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
4611 saveMasterHeightmap();
4613 u32 sector_meta_count = 0;
4614 u32 block_count = 0;
4617 JMutexAutoLock lock(m_sector_mutex);
4619 core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
4620 for(; i.atEnd() == false; i++)
4622 ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue();
4623 assert(sector->getId() == MAPSECTOR_SERVER);
4625 if(ENABLE_SECTOR_SAVING)
4627 if(sector->differs_from_disk || only_changed == false)
4629 saveSectorMeta(sector);
4630 sector_meta_count++;
4633 if(ENABLE_BLOCK_SAVING)
4635 core::list<MapBlock*> blocks;
4636 sector->getBlocks(blocks);
4637 core::list<MapBlock*>::Iterator j;
4638 for(j=blocks.begin(); j!=blocks.end(); j++)
4640 MapBlock *block = *j;
4641 if(block->getChangedFlag() || only_changed == false)
4653 Only print if something happened or saved whole map
4655 if(only_changed == false || sector_meta_count != 0
4656 || block_count != 0)
4658 dstream<<DTIME<<"ServerMap: Written: "
4659 <<sector_meta_count<<" sector metadata files, "
4660 <<block_count<<" block files"
4665 void ServerMap::loadAll()
4667 DSTACK(__FUNCTION_NAME);
4668 dstream<<DTIME<<"ServerMap: Loading map..."<<std::endl;
4670 loadMasterHeightmap();
4672 std::vector<fs::DirListNode> list = fs::GetDirListing(m_savedir+"/sectors/");
4674 dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
4676 JMutexAutoLock lock(m_sector_mutex);
4679 s32 printed_counter = -100000;
4680 s32 count = list.size();
4682 std::vector<fs::DirListNode>::iterator i;
4683 for(i=list.begin(); i!=list.end(); i++)
4685 if(counter > printed_counter + 10)
4687 dstream<<DTIME<<counter<<"/"<<count<<std::endl;
4688 printed_counter = counter;
4692 MapSector *sector = NULL;
4694 // We want directories
4698 sector = loadSectorMeta(i->name);
4700 catch(InvalidFilenameException &e)
4702 // This catches unknown crap in directory
4705 if(ENABLE_BLOCK_LOADING)
4707 std::vector<fs::DirListNode> list2 = fs::GetDirListing
4708 (m_savedir+"/sectors/"+i->name);
4709 std::vector<fs::DirListNode>::iterator i2;
4710 for(i2=list2.begin(); i2!=list2.end(); i2++)
4716 loadBlock(i->name, i2->name, sector);
4718 catch(InvalidFilenameException &e)
4720 // This catches unknown crap in directory
4725 dstream<<DTIME<<"ServerMap: Map loaded."<<std::endl;
4728 void ServerMap::saveMasterHeightmap()
4730 DSTACK(__FUNCTION_NAME);
4731 createDir(m_savedir);
4733 std::string fullpath = m_savedir + "/master_heightmap";
4734 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
4735 if(o.good() == false)
4736 throw FileNotGoodException("Cannot open master heightmap");
4738 // Format used for writing
4739 u8 version = SER_FMT_VER_HIGHEST;
4742 SharedBuffer<u8> hmdata = m_heightmap->serialize(version);
4744 [0] u8 serialization version
4745 [1] X master heightmap
4747 u32 fullsize = 1 + hmdata.getSize();
4748 SharedBuffer<u8> data(fullsize);
4751 memcpy(&data[1], *hmdata, hmdata.getSize());
4753 o.write((const char*)*data, fullsize);
4756 m_heightmap->serialize(o, version);
4759 void ServerMap::loadMasterHeightmap()
4761 DSTACK(__FUNCTION_NAME);
4762 std::string fullpath = m_savedir + "/master_heightmap";
4763 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
4764 if(is.good() == false)
4765 throw FileNotGoodException("Cannot open master heightmap");
4767 if(m_heightmap != NULL)
4770 m_heightmap = UnlimitedHeightmap::deSerialize(is, &m_padb);
4773 void ServerMap::saveSectorMeta(ServerMapSector *sector)
4775 DSTACK(__FUNCTION_NAME);
4776 // Format used for writing
4777 u8 version = SER_FMT_VER_HIGHEST;
4779 v2s16 pos = sector->getPos();
4780 createDir(m_savedir);
4781 createDir(m_savedir+"/sectors");
4782 std::string dir = getSectorDir(pos);
4785 std::string fullpath = dir + "/heightmap";
4786 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
4787 if(o.good() == false)
4788 throw FileNotGoodException("Cannot open master heightmap");
4790 sector->serialize(o, version);
4792 sector->differs_from_disk = false;
4795 MapSector* ServerMap::loadSectorMeta(std::string dirname)
4797 DSTACK(__FUNCTION_NAME);
4799 v2s16 p2d = getSectorPos(dirname);
4800 std::string dir = m_savedir + "/sectors/" + dirname;
4802 std::string fullpath = dir + "/heightmap";
4803 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
4804 if(is.good() == false)
4805 throw FileNotGoodException("Cannot open sector heightmap");
4807 ServerMapSector *sector = ServerMapSector::deSerialize
4808 (is, this, p2d, &m_hwrapper, m_sectors);
4810 sector->differs_from_disk = false;
4815 bool ServerMap::loadSectorFull(v2s16 p2d)
4817 DSTACK(__FUNCTION_NAME);
4818 std::string sectorsubdir = getSectorSubDir(p2d);
4820 MapSector *sector = NULL;
4822 JMutexAutoLock lock(m_sector_mutex);
4825 sector = loadSectorMeta(sectorsubdir);
4827 catch(InvalidFilenameException &e)
4831 catch(FileNotGoodException &e)
4835 catch(std::exception &e)
4840 if(ENABLE_BLOCK_LOADING)
4842 std::vector<fs::DirListNode> list2 = fs::GetDirListing
4843 (m_savedir+"/sectors/"+sectorsubdir);
4844 std::vector<fs::DirListNode>::iterator i2;
4845 for(i2=list2.begin(); i2!=list2.end(); i2++)
4851 loadBlock(sectorsubdir, i2->name, sector);
4853 catch(InvalidFilenameException &e)
4855 // This catches unknown crap in directory
4863 bool ServerMap::deFlushSector(v2s16 p2d)
4865 DSTACK(__FUNCTION_NAME);
4866 // See if it already exists in memory
4868 MapSector *sector = getSectorNoGenerate(p2d);
4871 catch(InvalidPositionException &e)
4874 Try to load the sector from disk.
4876 if(loadSectorFull(p2d) == true)
4885 void ServerMap::saveBlock(MapBlock *block)
4887 DSTACK(__FUNCTION_NAME);
4889 Dummy blocks are not written
4891 if(block->isDummy())
4893 /*v3s16 p = block->getPos();
4894 dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
4895 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
4899 // Format used for writing
4900 u8 version = SER_FMT_VER_HIGHEST;
4902 v3s16 p3d = block->getPos();
4903 v2s16 p2d(p3d.X, p3d.Z);
4904 createDir(m_savedir);
4905 createDir(m_savedir+"/sectors");
4906 std::string dir = getSectorDir(p2d);
4909 // Block file is map/sectors/xxxxxxxx/xxxx
4911 snprintf(cc, 5, "%.4x", (unsigned int)p3d.Y&0xffff);
4912 std::string fullpath = dir + "/" + cc;
4913 std::ofstream o(fullpath.c_str(), std::ios_base::binary);
4914 if(o.good() == false)
4915 throw FileNotGoodException("Cannot open block data");
4918 [0] u8 serialization version
4921 o.write((char*)&version, 1);
4923 block->serialize(o, version);
4926 Versions up from 9 have block objects.
4930 block->serializeObjects(o, version);
4933 // We just wrote it to the disk
4934 block->resetChangedFlag();
4937 void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
4939 DSTACK(__FUNCTION_NAME);
4943 // Block file is map/sectors/xxxxxxxx/xxxx
4944 std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
4945 std::ifstream is(fullpath.c_str(), std::ios_base::binary);
4946 if(is.good() == false)
4947 throw FileNotGoodException("Cannot open block file");
4949 v3s16 p3d = getBlockPos(sectordir, blockfile);
4950 v2s16 p2d(p3d.X, p3d.Z);
4952 assert(sector->getPos() == p2d);
4954 u8 version = SER_FMT_VER_INVALID;
4955 is.read((char*)&version, 1);
4957 /*u32 block_size = MapBlock::serializedLength(version);
4958 SharedBuffer<u8> data(block_size);
4959 is.read((char*)*data, block_size);*/
4961 // This will always return a sector because we're the server
4962 //MapSector *sector = emergeSector(p2d);
4964 MapBlock *block = NULL;
4965 bool created_new = false;
4967 block = sector->getBlockNoCreate(p3d.Y);
4969 catch(InvalidPositionException &e)
4971 block = sector->createBlankBlockNoInsert(p3d.Y);
4975 // deserialize block data
4976 block->deSerialize(is, version);
4979 Versions up from 9 have block objects.
4983 block->updateObjects(is, version, NULL, 0);
4987 sector->insertBlock(block);
4990 Convert old formats to new and save
4993 // Save old format blocks in new format
4994 if(version < SER_FMT_VER_HIGHEST)
4999 // We just loaded it from the disk, so it's up-to-date.
5000 block->resetChangedFlag();
5003 catch(SerializationError &e)
5005 dstream<<"WARNING: Invalid block data on disk "
5006 "(SerializationError). Ignoring."
5011 // Gets from master heightmap
5012 void ServerMap::getSectorCorners(v2s16 p2d, s16 *corners)
5014 assert(m_heightmap != NULL);
5022 corners[0] = m_heightmap->getGroundHeight
5023 ((p2d+v2s16(0,0))*SECTOR_HEIGHTMAP_SPLIT);
5024 corners[1] = m_heightmap->getGroundHeight
5025 ((p2d+v2s16(1,0))*SECTOR_HEIGHTMAP_SPLIT);
5026 corners[2] = m_heightmap->getGroundHeight
5027 ((p2d+v2s16(1,1))*SECTOR_HEIGHTMAP_SPLIT);
5028 corners[3] = m_heightmap->getGroundHeight
5029 ((p2d+v2s16(0,1))*SECTOR_HEIGHTMAP_SPLIT);
5032 void ServerMap::PrintInfo(std::ostream &out)
5043 ClientMap::ClientMap(
5045 MapDrawControl &control,
5046 scene::ISceneNode* parent,
5047 scene::ISceneManager* mgr,
5051 scene::ISceneNode(parent, mgr, id),
5058 /*m_box = core::aabbox3d<f32>(0,0,0,
5059 map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
5060 /*m_box = core::aabbox3d<f32>(0,0,0,
5061 map->getSizeNodes().X * BS,
5062 map->getSizeNodes().Y * BS,
5063 map->getSizeNodes().Z * BS);*/
5064 m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
5065 BS*1000000,BS*1000000,BS*1000000);
5067 //setPosition(v3f(BS,BS,BS));
5070 ClientMap::~ClientMap()
5072 JMutexAutoLock lock(mesh_mutex);
5081 MapSector * ClientMap::emergeSector(v2s16 p2d)
5083 DSTACK(__FUNCTION_NAME);
5084 // Check that it doesn't exist already
5086 return getSectorNoGenerate(p2d);
5088 catch(InvalidPositionException &e)
5092 // Create a sector with no heightmaps
5093 ClientMapSector *sector = new ClientMapSector(this, p2d);
5096 JMutexAutoLock lock(m_sector_mutex);
5097 m_sectors.insert(p2d, sector);
5103 void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
5105 DSTACK(__FUNCTION_NAME);
5106 ClientMapSector *sector = NULL;
5108 JMutexAutoLock lock(m_sector_mutex);
5110 core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
5114 sector = (ClientMapSector*)n->getValue();
5115 assert(sector->getId() == MAPSECTOR_CLIENT);
5119 sector = new ClientMapSector(this, p2d);
5121 JMutexAutoLock lock(m_sector_mutex);
5122 m_sectors.insert(p2d, sector);
5126 sector->deSerialize(is);
5129 void ClientMap::OnRegisterSceneNode()
5133 SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
5134 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
5137 ISceneNode::OnRegisterSceneNode();
5140 void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
5142 //m_dout<<DTIME<<"Rendering map..."<<std::endl;
5143 DSTACK(__FUNCTION_NAME);
5145 bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
5148 Get time for measuring timeout.
5150 Measuring time is very useful for long delays when the
5151 machine is swapping a lot.
5153 int time1 = time(0);
5155 u32 daynight_ratio = m_client->getDayNightRatio();
5157 m_camera_mutex.Lock();
5158 v3f camera_position = m_camera_position;
5159 v3f camera_direction = m_camera_direction;
5160 m_camera_mutex.Unlock();
5163 Get all blocks and draw all visible ones
5166 v3s16 cam_pos_nodes(
5167 camera_position.X / BS,
5168 camera_position.Y / BS,
5169 camera_position.Z / BS);
5171 v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
5173 v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
5174 v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
5176 // Take a fair amount as we will be dropping more out later
5178 p_nodes_min.X / MAP_BLOCKSIZE - 1,
5179 p_nodes_min.Y / MAP_BLOCKSIZE - 1,
5180 p_nodes_min.Z / MAP_BLOCKSIZE - 1);
5182 p_nodes_max.X / MAP_BLOCKSIZE + 1,
5183 p_nodes_max.Y / MAP_BLOCKSIZE + 1,
5184 p_nodes_max.Z / MAP_BLOCKSIZE + 1);
5186 u32 vertex_count = 0;
5188 // For limiting number of mesh updates per frame
5189 u32 mesh_update_count = 0;
5191 u32 blocks_would_have_drawn = 0;
5192 u32 blocks_drawn = 0;
5194 //NOTE: The sectors map should be locked but we're not doing it
5195 // because it'd cause too much delays
5197 int timecheck_counter = 0;
5198 core::map<v2s16, MapSector*>::Iterator si;
5199 si = m_sectors.getIterator();
5200 for(; si.atEnd() == false; si++)
5203 timecheck_counter++;
5204 if(timecheck_counter > 50)
5206 int time2 = time(0);
5207 if(time2 > time1 + 4)
5209 dstream<<"ClientMap::renderMap(): "
5210 "Rendering takes ages, returning."
5217 MapSector *sector = si.getNode()->getValue();
5218 v2s16 sp = sector->getPos();
5220 if(m_control.range_all == false)
5222 if(sp.X < p_blocks_min.X
5223 || sp.X > p_blocks_max.X
5224 || sp.Y < p_blocks_min.Z
5225 || sp.Y > p_blocks_max.Z)
5229 core::list< MapBlock * > sectorblocks;
5230 sector->getBlocks(sectorblocks);
5236 core::list< MapBlock * >::Iterator i;
5237 for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
5239 MapBlock *block = *i;
5242 Compare block position to camera position, skip
5243 if not seen on display
5246 float range = 100000 * BS;
5247 if(m_control.range_all == false)
5248 range = m_control.wanted_range * BS;
5250 if(isBlockInSight(block->getPos(), camera_position,
5251 camera_direction, range) == false)
5257 v3s16 blockpos_nodes = block->getPosRelative();
5259 // Block center position
5261 ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
5262 ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
5263 ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
5266 // Block position relative to camera
5267 v3f blockpos_relative = blockpos - camera_position;
5269 // Distance in camera direction (+=front, -=back)
5270 f32 dforward = blockpos_relative.dotProduct(camera_direction);
5273 f32 d = blockpos_relative.getLength();
5275 if(m_control.range_all == false)
5277 // If block is far away, don't draw it
5278 if(d > m_control.wanted_range * BS)
5282 // Maximum radius of a block
5283 f32 block_max_radius = 0.5*1.44*1.44*MAP_BLOCKSIZE*BS;
5285 // If block is (nearly) touching the camera, don't
5286 // bother validating further (that is, render it anyway)
5287 if(d > block_max_radius * 1.5)
5289 // Cosine of the angle between the camera direction
5290 // and the block direction (camera_direction is an unit vector)
5291 f32 cosangle = dforward / d;
5293 // Compensate for the size of the block
5294 // (as the block has to be shown even if it's a bit off FOV)
5295 // This is an estimate.
5296 cosangle += block_max_radius / dforward;
5298 // If block is not in the field of view, skip it
5299 //if(cosangle < cos(FOV_ANGLE/2))
5300 if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
5305 v3s16 blockpos_nodes = block->getPosRelative();
5307 // Block center position
5309 ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
5310 ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
5311 ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
5314 // Block position relative to camera
5315 v3f blockpos_relative = blockpos - camera_position;
5318 f32 d = blockpos_relative.getLength();
5325 bool mesh_expired = false;
5328 JMutexAutoLock lock(block->mesh_mutex);
5330 mesh_expired = block->getMeshExpired();
5332 // Mesh has not been expired and there is no mesh:
5333 // block has no content
5334 if(block->mesh == NULL && mesh_expired == false)
5338 f32 faraway = BS*50;
5339 //f32 faraway = m_control.wanted_range * BS;
5342 This has to be done with the mesh_mutex unlocked
5344 // Pretty random but this should work somewhat nicely
5345 if(mesh_expired && (
5346 (mesh_update_count < 3
5347 && (d < faraway || mesh_update_count < 2)
5350 (m_control.range_all && mesh_update_count < 20)
5353 /*if(mesh_expired && mesh_update_count < 6
5354 && (d < faraway || mesh_update_count < 3))*/
5356 mesh_update_count++;
5358 // Mesh has been expired: generate new mesh
5359 //block->updateMeshes(daynight_i);
5360 block->updateMesh(daynight_ratio);
5362 mesh_expired = false;
5366 Don't draw an expired mesh that is far away
5368 /*if(mesh_expired && d >= faraway)
5371 // Instead, delete it
5372 JMutexAutoLock lock(block->mesh_mutex);
5375 block->mesh->drop();
5378 // And continue to next block
5383 Draw the faces of the block
5386 JMutexAutoLock lock(block->mesh_mutex);
5388 scene::SMesh *mesh = block->mesh;
5393 blocks_would_have_drawn++;
5394 if(blocks_drawn >= m_control.wanted_max_blocks
5395 && m_control.range_all == false
5396 && d > m_control.wanted_min_range * BS)
5400 u32 c = mesh->getMeshBufferCount();
5402 for(u32 i=0; i<c; i++)
5404 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
5405 const video::SMaterial& material = buf->getMaterial();
5406 video::IMaterialRenderer* rnd =
5407 driver->getMaterialRenderer(material.MaterialType);
5408 bool transparent = (rnd && rnd->isTransparent());
5409 // Render transparent on transparent pass and likewise.
5410 if(transparent == is_transparent_pass)
5412 driver->setMaterial(buf->getMaterial());
5413 driver->drawMeshBuffer(buf);
5414 vertex_count += buf->getVertexCount();
5418 } // foreach sectorblocks
5421 m_control.blocks_drawn = blocks_drawn;
5422 m_control.blocks_would_have_drawn = blocks_would_have_drawn;
5424 /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
5425 <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
5428 bool ClientMap::setTempMod(v3s16 p, NodeMod mod,
5429 core::map<v3s16, MapBlock*> *affected_blocks)
5431 bool changed = false;
5433 Add it to all blocks touching it
5436 v3s16(0,0,0), // this
5437 v3s16(0,0,1), // back
5438 v3s16(0,1,0), // top
5439 v3s16(1,0,0), // right
5440 v3s16(0,0,-1), // front
5441 v3s16(0,-1,0), // bottom
5442 v3s16(-1,0,0), // left
5444 for(u16 i=0; i<7; i++)
5446 v3s16 p2 = p + dirs[i];
5447 // Block position of neighbor (or requested) node
5448 v3s16 blockpos = getNodeBlockPos(p2);
5449 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
5450 if(blockref == NULL)
5452 // Relative position of requested node
5453 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
5454 if(blockref->setTempMod(relpos, mod))
5459 if(changed && affected_blocks!=NULL)
5461 for(u16 i=0; i<7; i++)
5463 v3s16 p2 = p + dirs[i];
5464 // Block position of neighbor (or requested) node
5465 v3s16 blockpos = getNodeBlockPos(p2);
5466 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
5467 if(blockref == NULL)
5469 affected_blocks->insert(blockpos, blockref);
5475 bool ClientMap::clearTempMod(v3s16 p,
5476 core::map<v3s16, MapBlock*> *affected_blocks)
5478 bool changed = false;
5480 v3s16(0,0,0), // this
5481 v3s16(0,0,1), // back
5482 v3s16(0,1,0), // top
5483 v3s16(1,0,0), // right
5484 v3s16(0,0,-1), // front
5485 v3s16(0,-1,0), // bottom
5486 v3s16(-1,0,0), // left
5488 for(u16 i=0; i<7; i++)
5490 v3s16 p2 = p + dirs[i];
5491 // Block position of neighbor (or requested) node
5492 v3s16 blockpos = getNodeBlockPos(p2);
5493 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
5494 if(blockref == NULL)
5496 // Relative position of requested node
5497 v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
5498 if(blockref->clearTempMod(relpos))
5503 if(changed && affected_blocks!=NULL)
5505 for(u16 i=0; i<7; i++)
5507 v3s16 p2 = p + dirs[i];
5508 // Block position of neighbor (or requested) node
5509 v3s16 blockpos = getNodeBlockPos(p2);
5510 MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
5511 if(blockref == NULL)
5513 affected_blocks->insert(blockpos, blockref);
5519 void ClientMap::PrintInfo(std::ostream &out)
5530 MapVoxelManipulator::MapVoxelManipulator(Map *map)
5535 MapVoxelManipulator::~MapVoxelManipulator()
5537 /*dstream<<"MapVoxelManipulator: blocks: "<<m_loaded_blocks.size()
5541 void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id)
5543 TimeTaker timer1("emerge", &emerge_time);
5545 // Units of these are MapBlocks
5546 v3s16 p_min = getNodeBlockPos(a.MinEdge);
5547 v3s16 p_max = getNodeBlockPos(a.MaxEdge);
5549 VoxelArea block_area_nodes
5550 (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
5552 addArea(block_area_nodes);
5554 for(s32 z=p_min.Z; z<=p_max.Z; z++)
5555 for(s32 y=p_min.Y; y<=p_max.Y; y++)
5556 for(s32 x=p_min.X; x<=p_max.X; x++)
5559 core::map<v3s16, bool>::Node *n;
5560 n = m_loaded_blocks.find(p);
5564 bool block_data_inexistent = false;
5567 TimeTaker timer1("emerge load", &emerge_load_time);
5569 /*dstream<<"Loading block (caller_id="<<caller_id<<")"
5570 <<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
5573 dstream<<std::endl;*/
5575 MapBlock *block = m_map->getBlockNoCreate(p);
5576 if(block->isDummy())
5577 block_data_inexistent = true;
5579 block->copyTo(*this);
5581 catch(InvalidPositionException &e)
5583 block_data_inexistent = true;
5586 if(block_data_inexistent)
5588 VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
5589 // Fill with VOXELFLAG_INEXISTENT
5590 for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
5591 for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
5593 s32 i = m_area.index(a.MinEdge.X,y,z);
5594 memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE);
5598 m_loaded_blocks.insert(p, !block_data_inexistent);
5601 //dstream<<"emerge done"<<std::endl;
5605 SUGG: Add an option to only update eg. water and air nodes.
5606 This will make it interfere less with important stuff if
5609 void MapVoxelManipulator::blitBack
5610 (core::map<v3s16, MapBlock*> & modified_blocks)
5612 if(m_area.getExtent() == v3s16(0,0,0))
5615 //TimeTaker timer1("blitBack");
5617 /*dstream<<"blitBack(): m_loaded_blocks.size()="
5618 <<m_loaded_blocks.size()<<std::endl;*/
5621 Initialize block cache
5623 v3s16 blockpos_last;
5624 MapBlock *block = NULL;
5625 bool block_checked_in_modified = false;
5627 for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
5628 for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
5629 for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
5633 u8 f = m_flags[m_area.index(p)];
5634 if(f & (VOXELFLAG_NOT_LOADED|VOXELFLAG_INEXISTENT))
5637 MapNode &n = m_data[m_area.index(p)];
5639 v3s16 blockpos = getNodeBlockPos(p);
5644 if(block == NULL || blockpos != blockpos_last){
5645 block = m_map->getBlockNoCreate(blockpos);
5646 blockpos_last = blockpos;
5647 block_checked_in_modified = false;
5650 // Calculate relative position in block
5651 v3s16 relpos = p - blockpos * MAP_BLOCKSIZE;
5653 // Don't continue if nothing has changed here
5654 if(block->getNode(relpos) == n)
5657 //m_map->setNode(m_area.MinEdge + p, n);
5658 block->setNode(relpos, n);
5661 Make sure block is in modified_blocks
5663 if(block_checked_in_modified == false)
5665 modified_blocks[blockpos] = block;
5666 block_checked_in_modified = true;
5669 catch(InvalidPositionException &e)
5675 ManualMapVoxelManipulator::ManualMapVoxelManipulator(Map *map):
5676 MapVoxelManipulator(map)
5680 ManualMapVoxelManipulator::~ManualMapVoxelManipulator()
5684 void ManualMapVoxelManipulator::emerge(VoxelArea a, s32 caller_id)
5686 // Just create the area so that it can be pointed to
5687 VoxelManipulator::emerge(a, caller_id);
5690 void ManualMapVoxelManipulator::initialEmerge(
5691 v3s16 blockpos_min, v3s16 blockpos_max)
5693 TimeTaker timer1("emerge", &emerge_time);
5695 // Units of these are MapBlocks
5696 v3s16 p_min = blockpos_min;
5697 v3s16 p_max = blockpos_max;
5699 VoxelArea block_area_nodes
5700 (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
5702 addArea(block_area_nodes);
5704 for(s32 z=p_min.Z; z<=p_max.Z; z++)
5705 for(s32 y=p_min.Y; y<=p_max.Y; y++)
5706 for(s32 x=p_min.X; x<=p_max.X; x++)
5709 core::map<v3s16, bool>::Node *n;
5710 n = m_loaded_blocks.find(p);
5714 bool block_data_inexistent = false;
5717 TimeTaker timer1("emerge load", &emerge_load_time);
5719 MapBlock *block = m_map->getBlockNoCreate(p);
5720 if(block->isDummy())
5721 block_data_inexistent = true;
5723 block->copyTo(*this);
5725 catch(InvalidPositionException &e)
5727 block_data_inexistent = true;
5730 if(block_data_inexistent)
5733 Mark area inexistent
5735 VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
5736 // Fill with VOXELFLAG_INEXISTENT
5737 for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
5738 for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
5740 s32 i = m_area.index(a.MinEdge.X,y,z);
5741 memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE);
5745 m_loaded_blocks.insert(p, !block_data_inexistent);
5749 void ManualMapVoxelManipulator::blitBackAll(
5750 core::map<v3s16, MapBlock*> * modified_blocks)
5752 if(m_area.getExtent() == v3s16(0,0,0))
5756 Copy data of all blocks
5758 for(core::map<v3s16, bool>::Iterator
5759 i = m_loaded_blocks.getIterator();
5760 i.atEnd() == false; i++)
5762 bool existed = i.getNode()->getValue();
5763 if(existed == false)
5765 v3s16 p = i.getNode()->getKey();
5766 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
5769 dstream<<"WARNING: "<<__FUNCTION_NAME
5770 <<": got NULL block "
5771 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
5776 block->copyFrom(*this);
5779 modified_blocks->insert(p, block);