Fog effect when camera is inside cloud
[oweals/minetest.git] / src / mapblock.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 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 Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser 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
22 #include <sstream>
23 #include "map.h"
24 #include "light.h"
25 #include "nodedef.h"
26 #include "nodemetadata.h"
27 #include "gamedef.h"
28 #include "log.h"
29 #include "nameidmapping.h"
30 #include "content_mapnode.h" // For legacy name-id mapping
31 #include "content_nodemeta.h" // For legacy deserialization
32 #include "serialization.h"
33 #ifndef SERVER
34 #include "mapblock_mesh.h"
35 #endif
36 #include "util/string.h"
37 #include "util/serialize.h"
38 #include "util/basic_macros.h"
39
40 static const char *modified_reason_strings[] = {
41         "initial",
42         "reallocate",
43         "setIsUnderground",
44         "setLightingExpired",
45         "setGenerated",
46         "setNode",
47         "setNodeNoCheck",
48         "setTimestamp",
49         "NodeMetaRef::reportMetadataChange",
50         "clearAllObjects",
51         "Timestamp expired (step)",
52         "addActiveObjectRaw",
53         "removeRemovedObjects/remove",
54         "removeRemovedObjects/deactivate",
55         "Stored list cleared in activateObjects due to overflow",
56         "deactivateFarObjects: Static data moved in",
57         "deactivateFarObjects: Static data moved out",
58         "deactivateFarObjects: Static data changed considerably",
59         "finishBlockMake: expireDayNightDiff",
60         "unknown",
61 };
62
63
64 /*
65         MapBlock
66 */
67
68 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
69                 m_parent(parent),
70                 m_pos(pos),
71                 m_pos_relative(pos * MAP_BLOCKSIZE),
72                 m_gamedef(gamedef)
73 {
74         if(dummy == false)
75                 reallocate();
76 }
77
78 MapBlock::~MapBlock()
79 {
80 #ifndef SERVER
81         {
82                 delete mesh;
83                 mesh = nullptr;
84         }
85 #endif
86
87         delete[] data;
88 }
89
90 bool MapBlock::isValidPositionParent(v3s16 p)
91 {
92         if(isValidPosition(p))
93         {
94                 return true;
95         }
96         else{
97                 return m_parent->isValidPosition(getPosRelative() + p);
98         }
99 }
100
101 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
102 {
103         if (isValidPosition(p) == false)
104                 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
105
106         if (!data) {
107                 if (is_valid_position)
108                         *is_valid_position = false;
109                 return MapNode(CONTENT_IGNORE);
110         }
111         if (is_valid_position)
112                 *is_valid_position = true;
113         return data[p.Z * zstride + p.Y * ystride + p.X];
114 }
115
116 std::string MapBlock::getModifiedReasonString()
117 {
118         std::string reason;
119
120         const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
121                 ARRLEN(modified_reason_strings));
122
123         for (u32 i = 0; i != ubound; i++) {
124                 if ((m_modified_reason & (1 << i)) == 0)
125                         continue;
126
127                 reason += modified_reason_strings[i];
128                 reason += ", ";
129         }
130
131         if (reason.length() > 2)
132                 reason.resize(reason.length() - 2);
133
134         return reason;
135 }
136
137 /*
138         Propagates sunlight down through the block.
139         Doesn't modify nodes that are not affected by sunlight.
140
141         Returns false if sunlight at bottom block is invalid.
142         Returns true if sunlight at bottom block is valid.
143         Returns true if bottom block doesn't exist.
144
145         If there is a block above, continues from it.
146         If there is no block above, assumes there is sunlight, unless
147         is_underground is set or highest node is water.
148
149         All sunlighted nodes are added to light_sources.
150
151         if remove_light==true, sets non-sunlighted nodes black.
152
153         if black_air_left!=NULL, it is set to true if non-sunlighted
154         air is left in block.
155 */
156 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
157                 bool remove_light, bool *black_air_left)
158 {
159         INodeDefManager *nodemgr = m_gamedef->ndef();
160
161         // Whether the sunlight at the top of the bottom block is valid
162         bool block_below_is_valid = true;
163
164         v3s16 pos_relative = getPosRelative();
165
166         for(s16 x=0; x<MAP_BLOCKSIZE; x++)
167         {
168                 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
169                 {
170 #if 1
171                         bool no_sunlight = false;
172                         //bool no_top_block = false;
173
174                         // Check if node above block has sunlight
175
176                         bool is_valid_position;
177                         MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
178                                 &is_valid_position);
179                         if (is_valid_position)
180                         {
181                                 if(n.getContent() == CONTENT_IGNORE)
182                                 {
183                                         // Trust heuristics
184                                         no_sunlight = is_underground;
185                                 }
186                                 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
187                                 {
188                                         no_sunlight = true;
189                                 }
190                         }
191                         else
192                         {
193                                 //no_top_block = true;
194
195                                 // NOTE: This makes over-ground roofed places sunlighted
196                                 // Assume sunlight, unless is_underground==true
197                                 if(is_underground)
198                                 {
199                                         no_sunlight = true;
200                                 }
201                                 else
202                                 {
203                                         MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
204                                         if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
205                                         {
206                                                 no_sunlight = true;
207                                         }
208                                 }
209                                 // NOTE: As of now, this just would make everything dark.
210                                 // No sunlight here
211                                 //no_sunlight = true;
212                         }
213 #endif
214 #if 0 // Doesn't work; nothing gets light.
215                         bool no_sunlight = true;
216                         bool no_top_block = false;
217                         // Check if node above block has sunlight
218                         try{
219                                 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
220                                 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
221                                 {
222                                         no_sunlight = false;
223                                 }
224                         }
225                         catch(InvalidPositionException &e)
226                         {
227                                 no_top_block = true;
228                         }
229 #endif
230
231                         /*std::cout<<"("<<x<<","<<z<<"): "
232                                         <<"no_top_block="<<no_top_block
233                                         <<", is_underground="<<is_underground
234                                         <<", no_sunlight="<<no_sunlight
235                                         <<std::endl;*/
236
237                         s16 y = MAP_BLOCKSIZE-1;
238
239                         // This makes difference to diminishing in water.
240                         bool stopped_to_solid_object = false;
241
242                         u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
243
244                         for(; y >= 0; y--)
245                         {
246                                 v3s16 pos(x, y, z);
247                                 MapNode &n = getNodeRef(pos);
248
249                                 if(current_light == 0)
250                                 {
251                                         // Do nothing
252                                 }
253                                 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
254                                 {
255                                         // Do nothing: Sunlight is continued
256                                 }
257                                 else if(nodemgr->get(n).light_propagates == false)
258                                 {
259                                         // A solid object is on the way.
260                                         stopped_to_solid_object = true;
261
262                                         // Light stops.
263                                         current_light = 0;
264                                 }
265                                 else
266                                 {
267                                         // Diminish light
268                                         current_light = diminish_light(current_light);
269                                 }
270
271                                 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
272
273                                 if(current_light > old_light || remove_light)
274                                 {
275                                         n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
276                                 }
277
278                                 if(diminish_light(current_light) != 0)
279                                 {
280                                         light_sources.insert(pos_relative + pos);
281                                 }
282
283                                 if(current_light == 0 && stopped_to_solid_object)
284                                 {
285                                         if(black_air_left)
286                                         {
287                                                 *black_air_left = true;
288                                         }
289                                 }
290                         }
291
292                         // Whether or not the block below should see LIGHT_SUN
293                         bool sunlight_should_go_down = (current_light == LIGHT_SUN);
294
295                         /*
296                                 If the block below hasn't already been marked invalid:
297
298                                 Check if the node below the block has proper sunlight at top.
299                                 If not, the block below is invalid.
300
301                                 Ignore non-transparent nodes as they always have no light
302                         */
303
304                         if(block_below_is_valid)
305                         {
306                                 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
307                                 if (is_valid_position) {
308                                         if(nodemgr->get(n).light_propagates)
309                                         {
310                                                 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
311                                                                 && sunlight_should_go_down == false)
312                                                         block_below_is_valid = false;
313                                                 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
314                                                                 && sunlight_should_go_down == true)
315                                                         block_below_is_valid = false;
316                                         }
317                                 }
318                                 else
319                                 {
320                                         /*std::cout<<"InvalidBlockException for bottom block node"
321                                                         <<std::endl;*/
322                                         // Just no block below, no need to panic.
323                                 }
324                         }
325                 }
326         }
327
328         return block_below_is_valid;
329 }
330
331
332 void MapBlock::copyTo(VoxelManipulator &dst)
333 {
334         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
335         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
336
337         // Copy from data to VoxelManipulator
338         dst.copyFrom(data, data_area, v3s16(0,0,0),
339                         getPosRelative(), data_size);
340 }
341
342 void MapBlock::copyFrom(VoxelManipulator &dst)
343 {
344         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
345         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
346
347         // Copy from VoxelManipulator to data
348         dst.copyTo(data, data_area, v3s16(0,0,0),
349                         getPosRelative(), data_size);
350 }
351
352 void MapBlock::actuallyUpdateDayNightDiff()
353 {
354         INodeDefManager *nodemgr = m_gamedef->ndef();
355
356         // Running this function un-expires m_day_night_differs
357         m_day_night_differs_expired = false;
358
359         if (!data) {
360                 m_day_night_differs = false;
361                 return;
362         }
363
364         bool differs;
365
366         /*
367                 Check if any lighting value differs
368         */
369         for (u32 i = 0; i < nodecount; i++) {
370                 MapNode &n = data[i];
371
372                 differs = !n.isLightDayNightEq(nodemgr);
373                 if (differs)
374                         break;
375         }
376
377         /*
378                 If some lighting values differ, check if the whole thing is
379                 just air. If it is just air, differs = false
380         */
381         if (differs) {
382                 bool only_air = true;
383                 for (u32 i = 0; i < nodecount; i++) {
384                         MapNode &n = data[i];
385                         if (n.getContent() != CONTENT_AIR) {
386                                 only_air = false;
387                                 break;
388                         }
389                 }
390                 if (only_air)
391                         differs = false;
392         }
393
394         // Set member variable
395         m_day_night_differs = differs;
396 }
397
398 void MapBlock::expireDayNightDiff()
399 {
400         if (!data) {
401                 m_day_night_differs = false;
402                 m_day_night_differs_expired = false;
403                 return;
404         }
405
406         m_day_night_differs_expired = true;
407 }
408
409 s16 MapBlock::getGroundLevel(v2s16 p2d)
410 {
411         if(isDummy())
412                 return -3;
413         try
414         {
415                 s16 y = MAP_BLOCKSIZE-1;
416                 for(; y>=0; y--)
417                 {
418                         MapNode n = getNodeRef(p2d.X, y, p2d.Y);
419                         if(m_gamedef->ndef()->get(n).walkable)
420                         {
421                                 if(y == MAP_BLOCKSIZE-1)
422                                         return -2;
423                                 else
424                                         return y;
425                         }
426                 }
427                 return -1;
428         }
429         catch(InvalidPositionException &e)
430         {
431                 return -3;
432         }
433 }
434
435 /*
436         Serialization
437 */
438 // List relevant id-name pairs for ids in the block using nodedef
439 // Renumbers the content IDs (starting at 0 and incrementing
440 // use static memory requires about 65535 * sizeof(int) ram in order to be
441 // sure we can handle all content ids. But it's absolutely worth it as it's
442 // a speedup of 4 for one of the major time consuming functions on storing
443 // mapblocks.
444 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
445 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
446                 INodeDefManager *nodedef)
447 {
448         memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
449
450         std::set<content_t> unknown_contents;
451         content_t id_counter = 0;
452         for (u32 i = 0; i < MapBlock::nodecount; i++) {
453                 content_t global_id = nodes[i].getContent();
454                 content_t id = CONTENT_IGNORE;
455
456                 // Try to find an existing mapping
457                 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
458                         id = getBlockNodeIdMapping_mapping[global_id];
459                 }
460                 else
461                 {
462                         // We have to assign a new mapping
463                         id = id_counter++;
464                         getBlockNodeIdMapping_mapping[global_id] = id;
465
466                         const ContentFeatures &f = nodedef->get(global_id);
467                         const std::string &name = f.name;
468                         if(name == "")
469                                 unknown_contents.insert(global_id);
470                         else
471                                 nimap->set(id, name);
472                 }
473
474                 // Update the MapNode
475                 nodes[i].setContent(id);
476         }
477         for(std::set<content_t>::const_iterator
478                         i = unknown_contents.begin();
479                         i != unknown_contents.end(); ++i){
480                 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
481                                 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
482         }
483 }
484 // Correct ids in the block to match nodedef based on names.
485 // Unknown ones are added to nodedef.
486 // Will not update itself to match id-name pairs in nodedef.
487 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
488                 IGameDef *gamedef)
489 {
490         INodeDefManager *nodedef = gamedef->ndef();
491         // This means the block contains incorrect ids, and we contain
492         // the information to convert those to names.
493         // nodedef contains information to convert our names to globally
494         // correct ids.
495         std::set<content_t> unnamed_contents;
496         std::set<std::string> unallocatable_contents;
497         for (u32 i = 0; i < MapBlock::nodecount; i++) {
498                 content_t local_id = nodes[i].getContent();
499                 std::string name;
500                 bool found = nimap->getName(local_id, name);
501                 if(!found){
502                         unnamed_contents.insert(local_id);
503                         continue;
504                 }
505                 content_t global_id;
506                 found = nodedef->getId(name, global_id);
507                 if(!found){
508                         global_id = gamedef->allocateUnknownNodeId(name);
509                         if(global_id == CONTENT_IGNORE){
510                                 unallocatable_contents.insert(name);
511                                 continue;
512                         }
513                 }
514                 nodes[i].setContent(global_id);
515         }
516         for(std::set<content_t>::const_iterator
517                         i = unnamed_contents.begin();
518                         i != unnamed_contents.end(); ++i){
519                 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
520                                 <<"Block contains id "<<(*i)
521                                 <<" with no name mapping"<<std::endl;
522         }
523         for(std::set<std::string>::const_iterator
524                         i = unallocatable_contents.begin();
525                         i != unallocatable_contents.end(); ++i){
526                 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
527                                 <<"Could not allocate global id for node name \""
528                                 <<(*i)<<"\""<<std::endl;
529         }
530 }
531
532 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
533 {
534         if(!ser_ver_supported(version))
535                 throw VersionMismatchException("ERROR: MapBlock format not supported");
536
537         if (!data)
538                 throw SerializationError("ERROR: Not writing dummy block.");
539
540         FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
541
542         // First byte
543         u8 flags = 0;
544         if(is_underground)
545                 flags |= 0x01;
546         if(getDayNightDiff())
547                 flags |= 0x02;
548         if(m_generated == false)
549                 flags |= 0x08;
550         writeU8(os, flags);
551         if (version >= 27) {
552                 writeU16(os, m_lighting_complete);
553         }
554
555         /*
556                 Bulk node data
557         */
558         NameIdMapping nimap;
559         if(disk)
560         {
561                 MapNode *tmp_nodes = new MapNode[nodecount];
562                 for(u32 i=0; i<nodecount; i++)
563                         tmp_nodes[i] = data[i];
564                 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
565
566                 u8 content_width = 2;
567                 u8 params_width = 2;
568                 writeU8(os, content_width);
569                 writeU8(os, params_width);
570                 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
571                                 content_width, params_width, true);
572                 delete[] tmp_nodes;
573         }
574         else
575         {
576                 u8 content_width = 2;
577                 u8 params_width = 2;
578                 writeU8(os, content_width);
579                 writeU8(os, params_width);
580                 MapNode::serializeBulk(os, version, data, nodecount,
581                                 content_width, params_width, true);
582         }
583
584         /*
585                 Node metadata
586         */
587         std::ostringstream oss(std::ios_base::binary);
588         m_node_metadata.serialize(oss, version, disk);
589         compressZlib(oss.str(), os);
590
591         /*
592                 Data that goes to disk, but not the network
593         */
594         if(disk)
595         {
596                 if(version <= 24){
597                         // Node timers
598                         m_node_timers.serialize(os, version);
599                 }
600
601                 // Static objects
602                 m_static_objects.serialize(os);
603
604                 // Timestamp
605                 writeU32(os, getTimestamp());
606
607                 // Write block-specific node definition id mapping
608                 nimap.serialize(os);
609
610                 if(version >= 25){
611                         // Node timers
612                         m_node_timers.serialize(os, version);
613                 }
614         }
615 }
616
617 void MapBlock::serializeNetworkSpecific(std::ostream &os)
618 {
619         if (!data) {
620                 throw SerializationError("ERROR: Not writing dummy block.");
621         }
622
623         writeU8(os, 1); // version
624         writeF1000(os, 0); // deprecated heat
625         writeF1000(os, 0); // deprecated humidity
626 }
627
628 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
629 {
630         if(!ser_ver_supported(version))
631                 throw VersionMismatchException("ERROR: MapBlock format not supported");
632
633         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
634
635         m_day_night_differs_expired = false;
636
637         if(version <= 21)
638         {
639                 deSerialize_pre22(is, version, disk);
640                 return;
641         }
642
643         u8 flags = readU8(is);
644         is_underground = (flags & 0x01) ? true : false;
645         m_day_night_differs = (flags & 0x02) ? true : false;
646         if (version < 27)
647                 m_lighting_complete = 0xFFFF;
648         else
649                 m_lighting_complete = readU16(is);
650         m_generated = (flags & 0x08) ? false : true;
651
652         /*
653                 Bulk node data
654         */
655         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
656                         <<": Bulk node data"<<std::endl);
657         u8 content_width = readU8(is);
658         u8 params_width = readU8(is);
659         if(content_width != 1 && content_width != 2)
660                 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
661         if(params_width != 2)
662                 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
663         MapNode::deSerializeBulk(is, version, data, nodecount,
664                         content_width, params_width, true);
665
666         /*
667                 NodeMetadata
668         */
669         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
670                         <<": Node metadata"<<std::endl);
671         // Ignore errors
672         try {
673                 std::ostringstream oss(std::ios_base::binary);
674                 decompressZlib(is, oss);
675                 std::istringstream iss(oss.str(), std::ios_base::binary);
676                 if (version >= 23)
677                         m_node_metadata.deSerialize(iss, m_gamedef->idef());
678                 else
679                         content_nodemeta_deserialize_legacy(iss,
680                                 &m_node_metadata, &m_node_timers,
681                                 m_gamedef->idef());
682         } catch(SerializationError &e) {
683                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
684                                 <<" while deserializing node metadata at ("
685                                 <<PP(getPos())<<": "<<e.what()<<std::endl;
686         }
687
688         /*
689                 Data that is only on disk
690         */
691         if(disk)
692         {
693                 // Node timers
694                 if(version == 23){
695                         // Read unused zero
696                         readU8(is);
697                 }
698                 if(version == 24){
699                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
700                                         <<": Node timers (ver==24)"<<std::endl);
701                         m_node_timers.deSerialize(is, version);
702                 }
703
704                 // Static objects
705                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
706                                 <<": Static objects"<<std::endl);
707                 m_static_objects.deSerialize(is);
708
709                 // Timestamp
710                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
711                                 <<": Timestamp"<<std::endl);
712                 setTimestamp(readU32(is));
713                 m_disk_timestamp = m_timestamp;
714
715                 // Dynamically re-set ids based on node names
716                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
717                                 <<": NameIdMapping"<<std::endl);
718                 NameIdMapping nimap;
719                 nimap.deSerialize(is);
720                 correctBlockNodeIds(&nimap, data, m_gamedef);
721
722                 if(version >= 25){
723                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
724                                         <<": Node timers (ver>=25)"<<std::endl);
725                         m_node_timers.deSerialize(is, version);
726                 }
727         }
728
729         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
730                         <<": Done."<<std::endl);
731 }
732
733 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
734 {
735         try {
736                 int version = readU8(is);
737                 //if(version != 1)
738                 //      throw SerializationError("unsupported MapBlock version");
739                 if(version >= 1) {
740                         readF1000(is); // deprecated heat
741                         readF1000(is); // deprecated humidity
742                 }
743         }
744         catch(SerializationError &e)
745         {
746                 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
747                                 <<": "<<e.what()<<std::endl;
748         }
749 }
750
751 /*
752         Legacy serialization
753 */
754
755 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
756 {
757         // Initialize default flags
758         is_underground = false;
759         m_day_night_differs = false;
760         m_lighting_complete = 0xFFFF;
761         m_generated = true;
762
763         // Make a temporary buffer
764         u32 ser_length = MapNode::serializedLength(version);
765         SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
766
767         // These have no compression
768         if (version <= 3 || version == 5 || version == 6) {
769                 char tmp;
770                 is.read(&tmp, 1);
771                 if (is.gcount() != 1)
772                         throw SerializationError(std::string(FUNCTION_NAME)
773                                 + ": not enough input data");
774                 is_underground = tmp;
775                 is.read((char *)*databuf_nodelist, nodecount * ser_length);
776                 if ((u32)is.gcount() != nodecount * ser_length)
777                         throw SerializationError(std::string(FUNCTION_NAME)
778                                 + ": not enough input data");
779         } else if (version <= 10) {
780                 u8 t8;
781                 is.read((char *)&t8, 1);
782                 is_underground = t8;
783
784                 {
785                         // Uncompress and set material data
786                         std::ostringstream os(std::ios_base::binary);
787                         decompress(is, os, version);
788                         std::string s = os.str();
789                         if (s.size() != nodecount)
790                                 throw SerializationError(std::string(FUNCTION_NAME)
791                                         + ": not enough input data");
792                         for (u32 i = 0; i < s.size(); i++) {
793                                 databuf_nodelist[i*ser_length] = s[i];
794                         }
795                 }
796                 {
797                         // Uncompress and set param data
798                         std::ostringstream os(std::ios_base::binary);
799                         decompress(is, os, version);
800                         std::string s = os.str();
801                         if (s.size() != nodecount)
802                                 throw SerializationError(std::string(FUNCTION_NAME)
803                                         + ": not enough input data");
804                         for (u32 i = 0; i < s.size(); i++) {
805                                 databuf_nodelist[i*ser_length + 1] = s[i];
806                         }
807                 }
808
809                 if (version >= 10) {
810                         // Uncompress and set param2 data
811                         std::ostringstream os(std::ios_base::binary);
812                         decompress(is, os, version);
813                         std::string s = os.str();
814                         if (s.size() != nodecount)
815                                 throw SerializationError(std::string(FUNCTION_NAME)
816                                         + ": not enough input data");
817                         for (u32 i = 0; i < s.size(); i++) {
818                                 databuf_nodelist[i*ser_length + 2] = s[i];
819                         }
820                 }
821         } else { // All other versions (10 to 21)
822                 u8 flags;
823                 is.read((char*)&flags, 1);
824                 is_underground = (flags & 0x01) ? true : false;
825                 m_day_night_differs = (flags & 0x02) ? true : false;
826                 if(version >= 18)
827                         m_generated = (flags & 0x08) ? false : true;
828
829                 // Uncompress data
830                 std::ostringstream os(std::ios_base::binary);
831                 decompress(is, os, version);
832                 std::string s = os.str();
833                 if (s.size() != nodecount * 3)
834                         throw SerializationError(std::string(FUNCTION_NAME)
835                                 + ": decompress resulted in size other than nodecount*3");
836
837                 // deserialize nodes from buffer
838                 for (u32 i = 0; i < nodecount; i++) {
839                         databuf_nodelist[i*ser_length] = s[i];
840                         databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
841                         databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
842                 }
843
844                 /*
845                         NodeMetadata
846                 */
847                 if (version >= 14) {
848                         // Ignore errors
849                         try {
850                                 if (version <= 15) {
851                                         std::string data = deSerializeString(is);
852                                         std::istringstream iss(data, std::ios_base::binary);
853                                         content_nodemeta_deserialize_legacy(iss,
854                                                 &m_node_metadata, &m_node_timers,
855                                                 m_gamedef->idef());
856                                 } else {
857                                         //std::string data = deSerializeLongString(is);
858                                         std::ostringstream oss(std::ios_base::binary);
859                                         decompressZlib(is, oss);
860                                         std::istringstream iss(oss.str(), std::ios_base::binary);
861                                         content_nodemeta_deserialize_legacy(iss,
862                                                 &m_node_metadata, &m_node_timers,
863                                                 m_gamedef->idef());
864                                 }
865                         } catch(SerializationError &e) {
866                                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
867                                                 <<" while deserializing node metadata"<<std::endl;
868                         }
869                 }
870         }
871
872         // Deserialize node data
873         for (u32 i = 0; i < nodecount; i++) {
874                 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
875         }
876
877         if (disk) {
878                 /*
879                         Versions up from 9 have block objects. (DEPRECATED)
880                 */
881                 if (version >= 9) {
882                         u16 count = readU16(is);
883                         // Not supported and length not known if count is not 0
884                         if(count != 0){
885                                 warningstream<<"MapBlock::deSerialize_pre22(): "
886                                                 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
887                                 return;
888                         }
889                 }
890
891                 /*
892                         Versions up from 15 have static objects.
893                 */
894                 if (version >= 15)
895                         m_static_objects.deSerialize(is);
896
897                 // Timestamp
898                 if (version >= 17) {
899                         setTimestamp(readU32(is));
900                         m_disk_timestamp = m_timestamp;
901                 } else {
902                         setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
903                 }
904
905                 // Dynamically re-set ids based on node names
906                 NameIdMapping nimap;
907                 // If supported, read node definition id mapping
908                 if (version >= 21) {
909                         nimap.deSerialize(is);
910                 // Else set the legacy mapping
911                 } else {
912                         content_mapnode_get_name_id_mapping(&nimap);
913                 }
914                 correctBlockNodeIds(&nimap, data, m_gamedef);
915         }
916
917
918         // Legacy data changes
919         // This code has to convert from pre-22 to post-22 format.
920         INodeDefManager *nodedef = m_gamedef->ndef();
921         for(u32 i=0; i<nodecount; i++)
922         {
923                 const ContentFeatures &f = nodedef->get(data[i].getContent());
924                 // Mineral
925                 if(nodedef->getId("default:stone") == data[i].getContent()
926                                 && data[i].getParam1() == 1)
927                 {
928                         data[i].setContent(nodedef->getId("default:stone_with_coal"));
929                         data[i].setParam1(0);
930                 }
931                 else if(nodedef->getId("default:stone") == data[i].getContent()
932                                 && data[i].getParam1() == 2)
933                 {
934                         data[i].setContent(nodedef->getId("default:stone_with_iron"));
935                         data[i].setParam1(0);
936                 }
937                 // facedir_simple
938                 if(f.legacy_facedir_simple)
939                 {
940                         data[i].setParam2(data[i].getParam1());
941                         data[i].setParam1(0);
942                 }
943                 // wall_mounted
944                 if(f.legacy_wallmounted)
945                 {
946                         u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
947                         u8 dir_old_format = data[i].getParam2();
948                         u8 dir_new_format = 0;
949                         for(u8 j=0; j<8; j++)
950                         {
951                                 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
952                                 {
953                                         dir_new_format = j;
954                                         break;
955                                 }
956                         }
957                         data[i].setParam2(dir_new_format);
958                 }
959         }
960
961 }
962
963 /*
964         Get a quick string to describe what a block actually contains
965 */
966 std::string analyze_block(MapBlock *block)
967 {
968         if(block == NULL)
969                 return "NULL";
970
971         std::ostringstream desc;
972
973         v3s16 p = block->getPos();
974         char spos[25];
975         snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
976         desc<<spos;
977
978         switch(block->getModified())
979         {
980         case MOD_STATE_CLEAN:
981                 desc<<"CLEAN,           ";
982                 break;
983         case MOD_STATE_WRITE_AT_UNLOAD:
984                 desc<<"WRITE_AT_UNLOAD, ";
985                 break;
986         case MOD_STATE_WRITE_NEEDED:
987                 desc<<"WRITE_NEEDED,    ";
988                 break;
989         default:
990                 desc<<"unknown getModified()="+itos(block->getModified())+", ";
991         }
992
993         if(block->isGenerated())
994                 desc<<"is_gen [X], ";
995         else
996                 desc<<"is_gen [ ], ";
997
998         if(block->getIsUnderground())
999                 desc<<"is_ug [X], ";
1000         else
1001                 desc<<"is_ug [ ], ";
1002
1003         desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1004
1005         if(block->isDummy())
1006         {
1007                 desc<<"Dummy, ";
1008         }
1009         else
1010         {
1011                 bool full_ignore = true;
1012                 bool some_ignore = false;
1013                 bool full_air = true;
1014                 bool some_air = false;
1015                 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1016                 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1017                 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1018                 {
1019                         v3s16 p(x0,y0,z0);
1020                         MapNode n = block->getNodeNoEx(p);
1021                         content_t c = n.getContent();
1022                         if(c == CONTENT_IGNORE)
1023                                 some_ignore = true;
1024                         else
1025                                 full_ignore = false;
1026                         if(c == CONTENT_AIR)
1027                                 some_air = true;
1028                         else
1029                                 full_air = false;
1030                 }
1031
1032                 desc<<"content {";
1033
1034                 std::ostringstream ss;
1035
1036                 if(full_ignore)
1037                         ss<<"IGNORE (full), ";
1038                 else if(some_ignore)
1039                         ss<<"IGNORE, ";
1040
1041                 if(full_air)
1042                         ss<<"AIR (full), ";
1043                 else if(some_air)
1044                         ss<<"AIR, ";
1045
1046                 if(ss.str().size()>=2)
1047                         desc<<ss.str().substr(0, ss.str().size()-2);
1048
1049                 desc<<"}, ";
1050         }
1051
1052         return desc.str().substr(0, desc.str().size()-2);
1053 }
1054
1055
1056 //END