comment update
[oweals/minetest.git] / src / mapblock.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "mapblock.h"
21 #include "map.h"
22 // For g_settings
23 #include "main.h"
24 #include "light.h"
25 #include <sstream>
26
27 /*
28         MapBlock
29 */
30
31 MapBlock::MapBlock(NodeContainer *parent, v3s16 pos, bool dummy):
32                 m_parent(parent),
33                 m_pos(pos),
34                 changed(true),
35                 is_underground(false),
36                 m_lighting_expired(true),
37                 m_day_night_differs(false),
38                 //m_not_fully_generated(false),
39                 m_objects(this),
40                 m_timestamp(BLOCK_TIMESTAMP_UNDEFINED)
41 {
42         data = NULL;
43         if(dummy == false)
44                 reallocate();
45         
46         //m_spawn_timer = -10000;
47
48 #ifndef SERVER
49         m_mesh_expired = false;
50         mesh_mutex.Init();
51         mesh = NULL;
52         m_temp_mods_mutex.Init();
53 #endif
54 }
55
56 MapBlock::~MapBlock()
57 {
58 #ifndef SERVER
59         {
60                 JMutexAutoLock lock(mesh_mutex);
61                 
62                 if(mesh)
63                 {
64                         mesh->drop();
65                         mesh = NULL;
66                 }
67         }
68 #endif
69
70         if(data)
71                 delete[] data;
72 }
73
74 bool MapBlock::isValidPositionParent(v3s16 p)
75 {
76         if(isValidPosition(p))
77         {
78                 return true;
79         }
80         else{
81                 return m_parent->isValidPosition(getPosRelative() + p);
82         }
83 }
84
85 MapNode MapBlock::getNodeParent(v3s16 p)
86 {
87         if(isValidPosition(p) == false)
88         {
89                 return m_parent->getNode(getPosRelative() + p);
90         }
91         else
92         {
93                 if(data == NULL)
94                         throw InvalidPositionException();
95                 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
96         }
97 }
98
99 void MapBlock::setNodeParent(v3s16 p, MapNode & n)
100 {
101         if(isValidPosition(p) == false)
102         {
103                 m_parent->setNode(getPosRelative() + p, n);
104         }
105         else
106         {
107                 if(data == NULL)
108                         throw InvalidPositionException();
109                 data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
110         }
111 }
112
113 MapNode MapBlock::getNodeParentNoEx(v3s16 p)
114 {
115         if(isValidPosition(p) == false)
116         {
117                 try{
118                         return m_parent->getNode(getPosRelative() + p);
119                 }
120                 catch(InvalidPositionException &e)
121                 {
122                         return MapNode(CONTENT_IGNORE);
123                 }
124         }
125         else
126         {
127                 if(data == NULL)
128                 {
129                         return MapNode(CONTENT_IGNORE);
130                 }
131                 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
132         }
133 }
134
135 #ifndef SERVER
136
137 #if 1
138 void MapBlock::updateMesh(u32 daynight_ratio)
139 {
140 #if 0
141         /*
142                 DEBUG: If mesh has been generated, don't generate it again
143         */
144         {
145                 JMutexAutoLock meshlock(mesh_mutex);
146                 if(mesh != NULL)
147                         return;
148         }
149 #endif
150
151         MeshMakeData data;
152         data.fill(daynight_ratio, this);
153         
154         scene::SMesh *mesh_new = makeMapBlockMesh(&data);
155         
156         /*
157                 Replace the mesh
158         */
159
160         replaceMesh(mesh_new);
161
162 }
163 #endif
164
165 void MapBlock::replaceMesh(scene::SMesh *mesh_new)
166 {
167         mesh_mutex.Lock();
168
169         //scene::SMesh *mesh_old = mesh[daynight_i];
170         //mesh[daynight_i] = mesh_new;
171
172         scene::SMesh *mesh_old = mesh;
173         mesh = mesh_new;
174         setMeshExpired(false);
175         
176         if(mesh_old != NULL)
177         {
178                 // Remove hardware buffers of meshbuffers of mesh
179                 // NOTE: No way, this runs in a different thread and everything
180                 /*u32 c = mesh_old->getMeshBufferCount();
181                 for(u32 i=0; i<c; i++)
182                 {
183                         IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
184                 }*/
185                 
186                 /*dstream<<"mesh_old->getReferenceCount()="
187                                 <<mesh_old->getReferenceCount()<<std::endl;
188                 u32 c = mesh_old->getMeshBufferCount();
189                 for(u32 i=0; i<c; i++)
190                 {
191                         scene::IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
192                         dstream<<"buf->getReferenceCount()="
193                                         <<buf->getReferenceCount()<<std::endl;
194                 }*/
195
196                 // Drop the mesh
197                 mesh_old->drop();
198
199                 //delete mesh_old;
200         }
201
202         mesh_mutex.Unlock();
203 }
204         
205 #endif // !SERVER
206
207 /*
208         Propagates sunlight down through the block.
209         Doesn't modify nodes that are not affected by sunlight.
210         
211         Returns false if sunlight at bottom block is invalid.
212         Returns true if sunlight at bottom block is valid.
213         Returns true if bottom block doesn't exist.
214
215         If there is a block above, continues from it.
216         If there is no block above, assumes there is sunlight, unless
217         is_underground is set or highest node is water.
218
219         All sunlighted nodes are added to light_sources.
220
221         if remove_light==true, sets non-sunlighted nodes black.
222
223         if black_air_left!=NULL, it is set to true if non-sunlighted
224         air is left in block.
225 */
226 bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources,
227                 bool remove_light, bool *black_air_left)
228 {
229         // Whether the sunlight at the top of the bottom block is valid
230         bool block_below_is_valid = true;
231         
232         v3s16 pos_relative = getPosRelative();
233         
234         for(s16 x=0; x<MAP_BLOCKSIZE; x++)
235         {
236                 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
237                 {
238 #if 1
239                         bool no_sunlight = false;
240                         bool no_top_block = false;
241                         // Check if node above block has sunlight
242                         try{
243                                 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
244                                 if(n.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
245                                 {
246                                         no_sunlight = true;
247                                 }
248                         }
249                         catch(InvalidPositionException &e)
250                         {
251                                 no_top_block = true;
252                                 
253                                 // NOTE: This makes over-ground roofed places sunlighted
254                                 // Assume sunlight, unless is_underground==true
255                                 if(is_underground)
256                                 {
257                                         no_sunlight = true;
258                                 }
259                                 else
260                                 {
261                                         MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
262                                         //if(n.d == CONTENT_WATER || n.d == CONTENT_WATERSOURCE)
263                                         if(content_features(n.d).sunlight_propagates == false)
264                                         {
265                                                 no_sunlight = true;
266                                         }
267                                 }
268                                 // NOTE: As of now, this just would make everything dark.
269                                 // No sunlight here
270                                 //no_sunlight = true;
271                         }
272 #endif
273 #if 0 // Doesn't work; nothing gets light.
274                         bool no_sunlight = true;
275                         bool no_top_block = false;
276                         // Check if node above block has sunlight
277                         try{
278                                 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
279                                 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
280                                 {
281                                         no_sunlight = false;
282                                 }
283                         }
284                         catch(InvalidPositionException &e)
285                         {
286                                 no_top_block = true;
287                         }
288 #endif
289
290                         /*std::cout<<"("<<x<<","<<z<<"): "
291                                         <<"no_top_block="<<no_top_block
292                                         <<", is_underground="<<is_underground
293                                         <<", no_sunlight="<<no_sunlight
294                                         <<std::endl;*/
295                 
296                         s16 y = MAP_BLOCKSIZE-1;
297                         
298                         // This makes difference to diminishing in water.
299                         bool stopped_to_solid_object = false;
300                         
301                         u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
302
303                         for(; y >= 0; y--)
304                         {
305                                 v3s16 pos(x, y, z);
306                                 MapNode &n = getNodeRef(pos);
307                                 
308                                 if(current_light == 0)
309                                 {
310                                         // Do nothing
311                                 }
312                                 else if(current_light == LIGHT_SUN && n.sunlight_propagates())
313                                 {
314                                         // Do nothing: Sunlight is continued
315                                 }
316                                 else if(n.light_propagates() == false)
317                                 {
318                                         /*// DEPRECATED TODO: REMOVE
319                                         if(grow_grass)
320                                         {
321                                                 bool upper_is_air = false;
322                                                 try
323                                                 {
324                                                         if(getNodeParent(pos+v3s16(0,1,0)).d == CONTENT_AIR)
325                                                                 upper_is_air = true;
326                                                 }
327                                                 catch(InvalidPositionException &e)
328                                                 {
329                                                 }
330                                                 // Turn mud into grass
331                                                 if(upper_is_air && n.d == CONTENT_MUD
332                                                                 && current_light == LIGHT_SUN)
333                                                 {
334                                                         n.d = CONTENT_GRASS;
335                                                 }
336                                         }*/
337
338                                         // A solid object is on the way.
339                                         stopped_to_solid_object = true;
340                                         
341                                         // Light stops.
342                                         current_light = 0;
343                                 }
344                                 else
345                                 {
346                                         // Diminish light
347                                         current_light = diminish_light(current_light);
348                                 }
349
350                                 u8 old_light = n.getLight(LIGHTBANK_DAY);
351
352                                 if(current_light > old_light || remove_light)
353                                 {
354                                         n.setLight(LIGHTBANK_DAY, current_light);
355                                 }
356                                 
357                                 if(diminish_light(current_light) != 0)
358                                 {
359                                         light_sources.insert(pos_relative + pos, true);
360                                 }
361
362                                 if(current_light == 0 && stopped_to_solid_object)
363                                 {
364                                         if(black_air_left)
365                                         {
366                                                 *black_air_left = true;
367                                         }
368                                 }
369                         }
370
371                         // Whether or not the block below should see LIGHT_SUN
372                         bool sunlight_should_go_down = (current_light == LIGHT_SUN);
373
374                         /*
375                                 If the block below hasn't already been marked invalid:
376
377                                 Check if the node below the block has proper sunlight at top.
378                                 If not, the block below is invalid.
379                                 
380                                 Ignore non-transparent nodes as they always have no light
381                         */
382                         try
383                         {
384                         if(block_below_is_valid)
385                         {
386                                 MapNode n = getNodeParent(v3s16(x, -1, z));
387                                 if(n.light_propagates())
388                                 {
389                                         if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN
390                                                         && sunlight_should_go_down == false)
391                                                 block_below_is_valid = false;
392                                         else if(n.getLight(LIGHTBANK_DAY) != LIGHT_SUN
393                                                         && sunlight_should_go_down == true)
394                                                 block_below_is_valid = false;
395                                 }
396                         }//if
397                         }//try
398                         catch(InvalidPositionException &e)
399                         {
400                                 /*std::cout<<"InvalidBlockException for bottom block node"
401                                                 <<std::endl;*/
402                                 // Just no block below, no need to panic.
403                         }
404                 }
405         }
406
407         return block_below_is_valid;
408 }
409
410
411 void MapBlock::copyTo(VoxelManipulator &dst)
412 {
413         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
414         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
415         
416         // Copy from data to VoxelManipulator
417         dst.copyFrom(data, data_area, v3s16(0,0,0),
418                         getPosRelative(), data_size);
419 }
420
421 void MapBlock::copyFrom(VoxelManipulator &dst)
422 {
423         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
424         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
425         
426         // Copy from VoxelManipulator to data
427         dst.copyTo(data, data_area, v3s16(0,0,0),
428                         getPosRelative(), data_size);
429 }
430
431 void MapBlock::stepObjects(float dtime, bool server, u32 daynight_ratio)
432 {
433         /*
434                 Step objects
435         */
436         m_objects.step(dtime, server, daynight_ratio);
437
438         setChangedFlag();
439 }
440
441
442 void MapBlock::updateDayNightDiff()
443 {
444         if(data == NULL)
445         {
446                 m_day_night_differs = false;
447                 return;
448         }
449
450         bool differs = false;
451
452         /*
453                 Check if any lighting value differs
454         */
455         for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
456         {
457                 MapNode &n = data[i];
458                 if(n.getLight(LIGHTBANK_DAY) != n.getLight(LIGHTBANK_NIGHT))
459                 {
460                         differs = true;
461                         break;
462                 }
463         }
464
465         /*
466                 If some lighting values differ, check if the whole thing is
467                 just air. If it is, differ = false
468         */
469         if(differs)
470         {
471                 bool only_air = true;
472                 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
473                 {
474                         MapNode &n = data[i];
475                         if(n.d != CONTENT_AIR)
476                         {
477                                 only_air = false;
478                                 break;
479                         }
480                 }
481                 if(only_air)
482                         differs = false;
483         }
484
485         // Set member variable
486         m_day_night_differs = differs;
487 }
488
489 s16 MapBlock::getGroundLevel(v2s16 p2d)
490 {
491         if(isDummy())
492                 return -3;
493         try
494         {
495                 s16 y = MAP_BLOCKSIZE-1;
496                 for(; y>=0; y--)
497                 {
498                         //if(is_ground_content(getNodeRef(p2d.X, y, p2d.Y).d))
499                         if(content_features(getNodeRef(p2d.X, y, p2d.Y).d).walkable)
500                         {
501                                 if(y == MAP_BLOCKSIZE-1)
502                                         return -2;
503                                 else
504                                         return y;
505                         }
506                 }
507                 return -1;
508         }
509         catch(InvalidPositionException &e)
510         {
511                 return -3;
512         }
513 }
514
515 /*
516         Serialization
517 */
518
519 void MapBlock::serialize(std::ostream &os, u8 version)
520 {
521         if(!ser_ver_supported(version))
522                 throw VersionMismatchException("ERROR: MapBlock format not supported");
523         
524         if(data == NULL)
525         {
526                 throw SerializationError("ERROR: Not writing dummy block.");
527         }
528         
529         // These have no compression
530         if(version <= 3 || version == 5 || version == 6)
531         {
532                 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
533                 
534                 u32 buflen = 1 + nodecount * MapNode::serializedLength(version);
535                 SharedBuffer<u8> dest(buflen);
536
537                 dest[0] = is_underground;
538                 for(u32 i=0; i<nodecount; i++)
539                 {
540                         u32 s = 1 + i * MapNode::serializedLength(version);
541                         data[i].serialize(&dest[s], version);
542                 }
543                 
544                 os.write((char*)*dest, dest.getSize());
545         }
546         else if(version <= 10)
547         {
548                 /*
549                         With compression.
550                         Compress the materials and the params separately.
551                 */
552                 
553                 // First byte
554                 os.write((char*)&is_underground, 1);
555
556                 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
557
558                 // Get and compress materials
559                 SharedBuffer<u8> materialdata(nodecount);
560                 for(u32 i=0; i<nodecount; i++)
561                 {
562                         materialdata[i] = data[i].d;
563                 }
564                 compress(materialdata, os, version);
565
566                 // Get and compress lights
567                 SharedBuffer<u8> lightdata(nodecount);
568                 for(u32 i=0; i<nodecount; i++)
569                 {
570                         lightdata[i] = data[i].param;
571                 }
572                 compress(lightdata, os, version);
573                 
574                 if(version >= 10)
575                 {
576                         // Get and compress param2
577                         SharedBuffer<u8> param2data(nodecount);
578                         for(u32 i=0; i<nodecount; i++)
579                         {
580                                 param2data[i] = data[i].param2;
581                         }
582                         compress(param2data, os, version);
583                 }
584         }
585         // All other versions (newest)
586         else
587         {
588                 // First byte
589                 u8 flags = 0;
590                 if(is_underground)
591                         flags |= 0x01;
592                 if(m_day_night_differs)
593                         flags |= 0x02;
594                 if(m_lighting_expired)
595                         flags |= 0x04;
596                 os.write((char*)&flags, 1);
597
598                 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
599
600                 /*
601                         Get data
602                 */
603
604                 SharedBuffer<u8> databuf(nodecount*3);
605
606                 // Get contents
607                 for(u32 i=0; i<nodecount; i++)
608                 {
609                         databuf[i] = data[i].d;
610                 }
611
612                 // Get params
613                 for(u32 i=0; i<nodecount; i++)
614                 {
615                         databuf[i+nodecount] = data[i].param;
616                 }
617
618                 // Get param2
619                 for(u32 i=0; i<nodecount; i++)
620                 {
621                         databuf[i+nodecount*2] = data[i].param2;
622                 }
623
624                 /*
625                         Compress data to output stream
626                 */
627
628                 compress(databuf, os, version);
629                 
630                 /*
631                         NodeMetadata
632                 */
633                 if(version >= 14)
634                 {
635                         if(version <= 15)
636                         {
637                                 try{
638                                         std::ostringstream oss(std::ios_base::binary);
639                                         m_node_metadata.serialize(oss);
640                                         os<<serializeString(oss.str());
641                                 }
642                                 // This will happen if the string is longer than 65535
643                                 catch(SerializationError &e)
644                                 {
645                                         // Use an empty string
646                                         os<<serializeString("");
647                                 }
648                         }
649                         else
650                         {
651                                 std::ostringstream oss(std::ios_base::binary);
652                                 m_node_metadata.serialize(oss);
653                                 compressZlib(oss.str(), os);
654                                 //os<<serializeLongString(oss.str());
655                         }
656                 }
657         }
658 }
659
660 void MapBlock::deSerialize(std::istream &is, u8 version)
661 {
662         if(!ser_ver_supported(version))
663                 throw VersionMismatchException("ERROR: MapBlock format not supported");
664
665         // These have no lighting info
666         if(version <= 1)
667         {
668                 setLightingExpired(true);
669         }
670
671         // These have no compression
672         if(version <= 3 || version == 5 || version == 6)
673         {
674                 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
675                 char tmp;
676                 is.read(&tmp, 1);
677                 if(is.gcount() != 1)
678                         throw SerializationError
679                                         ("MapBlock::deSerialize: no enough input data");
680                 is_underground = tmp;
681                 for(u32 i=0; i<nodecount; i++)
682                 {
683                         s32 len = MapNode::serializedLength(version);
684                         SharedBuffer<u8> d(len);
685                         is.read((char*)*d, len);
686                         if(is.gcount() != len)
687                                 throw SerializationError
688                                                 ("MapBlock::deSerialize: no enough input data");
689                         data[i].deSerialize(*d, version);
690                 }
691         }
692         else if(version <= 10)
693         {
694                 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
695
696                 u8 t8;
697                 is.read((char*)&t8, 1);
698                 is_underground = t8;
699
700                 {
701                         // Uncompress and set material data
702                         std::ostringstream os(std::ios_base::binary);
703                         decompress(is, os, version);
704                         std::string s = os.str();
705                         if(s.size() != nodecount)
706                                 throw SerializationError
707                                                 ("MapBlock::deSerialize: invalid format");
708                         for(u32 i=0; i<s.size(); i++)
709                         {
710                                 data[i].d = s[i];
711                         }
712                 }
713                 {
714                         // Uncompress and set param data
715                         std::ostringstream os(std::ios_base::binary);
716                         decompress(is, os, version);
717                         std::string s = os.str();
718                         if(s.size() != nodecount)
719                                 throw SerializationError
720                                                 ("MapBlock::deSerialize: invalid format");
721                         for(u32 i=0; i<s.size(); i++)
722                         {
723                                 data[i].param = s[i];
724                         }
725                 }
726         
727                 if(version >= 10)
728                 {
729                         // Uncompress and set param2 data
730                         std::ostringstream os(std::ios_base::binary);
731                         decompress(is, os, version);
732                         std::string s = os.str();
733                         if(s.size() != nodecount)
734                                 throw SerializationError
735                                                 ("MapBlock::deSerialize: invalid format");
736                         for(u32 i=0; i<s.size(); i++)
737                         {
738                                 data[i].param2 = s[i];
739                         }
740                 }
741         }
742         // All other versions (newest)
743         else
744         {
745                 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
746
747                 u8 flags;
748                 is.read((char*)&flags, 1);
749                 is_underground = (flags & 0x01) ? true : false;
750                 m_day_night_differs = (flags & 0x02) ? true : false;
751                 m_lighting_expired = (flags & 0x04) ? true : false;
752
753                 // Uncompress data
754                 std::ostringstream os(std::ios_base::binary);
755                 decompress(is, os, version);
756                 std::string s = os.str();
757                 if(s.size() != nodecount*3)
758                         throw SerializationError
759                                         ("MapBlock::deSerialize: decompress resulted in size"
760                                         " other than nodecount*3");
761
762                 // Set contents
763                 for(u32 i=0; i<nodecount; i++)
764                 {
765                         data[i].d = s[i];
766                 }
767                 // Set params
768                 for(u32 i=0; i<nodecount; i++)
769                 {
770                         data[i].param = s[i+nodecount];
771                 }
772                 // Set param2
773                 for(u32 i=0; i<nodecount; i++)
774                 {
775                         data[i].param2 = s[i+nodecount*2];
776                 }
777                 
778                 /*
779                         NodeMetadata
780                 */
781                 if(version >= 14)
782                 {
783                         // Ignore errors
784                         try{
785                                 if(version <= 15)
786                                 {
787                                         std::string data = deSerializeString(is);
788                                         std::istringstream iss(data, std::ios_base::binary);
789                                         m_node_metadata.deSerialize(iss);
790                                 }
791                                 else
792                                 {
793                                         //std::string data = deSerializeLongString(is);
794                                         std::ostringstream oss(std::ios_base::binary);
795                                         decompressZlib(is, oss);
796                                         std::istringstream iss(oss.str(), std::ios_base::binary);
797                                         m_node_metadata.deSerialize(iss);
798                                 }
799                         }
800                         catch(SerializationError &e)
801                         {
802                                 dstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
803                                                 <<" while deserializing node metadata"<<std::endl;
804                         }
805                 }
806         }
807         
808         /*
809                 Translate nodes as specified in the translate_to fields of
810                 node features
811
812                 NOTE: This isn't really used. Should it be removed?
813         */
814         for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
815         {
816                 MapNode &n = data[i];
817
818                 MapNode *translate_to = content_features(n.d).translate_to;
819                 if(translate_to)
820                 {
821                         dstream<<"MapBlock: WARNING: Translating node "<<n.d<<" to "
822                                         <<translate_to->d<<std::endl;
823                         n = *translate_to;
824                 }
825         }
826 }
827
828 void MapBlock::serializeDiskExtra(std::ostream &os, u8 version)
829 {
830         // Versions up from 9 have block objects.
831         if(version >= 9)
832         {
833                 serializeObjects(os, version);
834         }
835         
836         // Versions up from 15 have static objects.
837         if(version >= 15)
838         {
839                 m_static_objects.serialize(os);
840         }
841
842         // Timestamp
843         if(version >= 17)
844         {
845                 writeU32(os, getTimestamp());
846         }
847 }
848
849 void MapBlock::deSerializeDiskExtra(std::istream &is, u8 version)
850 {
851         /*
852                 Versions up from 9 have block objects.
853         */
854         if(version >= 9)
855         {
856                 updateObjects(is, version, NULL, 0);
857         }
858
859         /*
860                 Versions up from 15 have static objects.
861         */
862         if(version >= 15)
863         {
864                 m_static_objects.deSerialize(is);
865         }
866                 
867         // Timestamp
868         if(version >= 17)
869         {
870                 setTimestamp(readU32(is));
871         }
872         else
873         {
874                 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
875         }
876 }
877
878
879 //END