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