Massive performance improvement on correctBlockNodeIds
[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 = false;
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.empty())
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::unordered_set<content_t> unnamed_contents;
496         std::unordered_set<std::string> unallocatable_contents;
497
498         bool previous_was_found = false;
499         content_t previous_local_id = CONTENT_IGNORE;
500         content_t previous_global_id = CONTENT_IGNORE;
501
502         for (u32 i = 0; i < MapBlock::nodecount; i++) {
503                 content_t local_id = nodes[i].getContent();
504                 // If previous node local_id was found and same than before, don't lookup maps
505                 // apply directly previous resolved id
506                 // This permits to massively improve loading performance when nodes are similar
507                 // example: default:air, default:stone are massively present
508                 if (previous_was_found && local_id == previous_local_id) {
509                         nodes[i].setContent(previous_global_id);
510                         continue;
511                 }
512
513                 std::string name;
514                 if (!nimap->getName(local_id, name)) {
515                         unnamed_contents.insert(local_id);
516                         previous_was_found = false;
517                         continue;
518                 }
519
520                 content_t global_id;
521                 if (!nodedef->getId(name, global_id)) {
522                         global_id = gamedef->allocateUnknownNodeId(name);
523                         if (global_id == CONTENT_IGNORE) {
524                                 unallocatable_contents.insert(name);
525                                 previous_was_found = false;
526                                 continue;
527                         }
528                 }
529                 nodes[i].setContent(global_id);
530
531                 // Save previous node local_id & global_id result
532                 previous_local_id = local_id;
533                 previous_global_id = global_id;
534                 previous_was_found = true;
535         }
536
537         for (const content_t c: unnamed_contents) {
538                 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
539                                 << "Block contains id " << c
540                                 << " with no name mapping" << std::endl;
541         }
542         for (const std::string &node_name: unallocatable_contents) {
543                 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
544                                 << "Could not allocate global id for node name \""
545                                 << node_name << "\"" << std::endl;
546         }
547 }
548
549 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
550 {
551         if(!ser_ver_supported(version))
552                 throw VersionMismatchException("ERROR: MapBlock format not supported");
553
554         if (!data)
555                 throw SerializationError("ERROR: Not writing dummy block.");
556
557         FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
558
559         // First byte
560         u8 flags = 0;
561         if(is_underground)
562                 flags |= 0x01;
563         if(getDayNightDiff())
564                 flags |= 0x02;
565         if(m_generated == false)
566                 flags |= 0x08;
567         writeU8(os, flags);
568         if (version >= 27) {
569                 writeU16(os, m_lighting_complete);
570         }
571
572         /*
573                 Bulk node data
574         */
575         NameIdMapping nimap;
576         if(disk)
577         {
578                 MapNode *tmp_nodes = new MapNode[nodecount];
579                 for(u32 i=0; i<nodecount; i++)
580                         tmp_nodes[i] = data[i];
581                 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
582
583                 u8 content_width = 2;
584                 u8 params_width = 2;
585                 writeU8(os, content_width);
586                 writeU8(os, params_width);
587                 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
588                                 content_width, params_width, true);
589                 delete[] tmp_nodes;
590         }
591         else
592         {
593                 u8 content_width = 2;
594                 u8 params_width = 2;
595                 writeU8(os, content_width);
596                 writeU8(os, params_width);
597                 MapNode::serializeBulk(os, version, data, nodecount,
598                                 content_width, params_width, true);
599         }
600
601         /*
602                 Node metadata
603         */
604         std::ostringstream oss(std::ios_base::binary);
605         m_node_metadata.serialize(oss, version, disk);
606         compressZlib(oss.str(), os);
607
608         /*
609                 Data that goes to disk, but not the network
610         */
611         if(disk)
612         {
613                 if(version <= 24){
614                         // Node timers
615                         m_node_timers.serialize(os, version);
616                 }
617
618                 // Static objects
619                 m_static_objects.serialize(os);
620
621                 // Timestamp
622                 writeU32(os, getTimestamp());
623
624                 // Write block-specific node definition id mapping
625                 nimap.serialize(os);
626
627                 if(version >= 25){
628                         // Node timers
629                         m_node_timers.serialize(os, version);
630                 }
631         }
632 }
633
634 void MapBlock::serializeNetworkSpecific(std::ostream &os)
635 {
636         if (!data) {
637                 throw SerializationError("ERROR: Not writing dummy block.");
638         }
639
640         writeU8(os, 1); // version
641         writeF1000(os, 0); // deprecated heat
642         writeF1000(os, 0); // deprecated humidity
643 }
644
645 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
646 {
647         if(!ser_ver_supported(version))
648                 throw VersionMismatchException("ERROR: MapBlock format not supported");
649
650         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
651
652         m_day_night_differs_expired = false;
653
654         if(version <= 21)
655         {
656                 deSerialize_pre22(is, version, disk);
657                 return;
658         }
659
660         u8 flags = readU8(is);
661         is_underground = (flags & 0x01) ? true : false;
662         m_day_night_differs = (flags & 0x02) ? true : false;
663         if (version < 27)
664                 m_lighting_complete = 0xFFFF;
665         else
666                 m_lighting_complete = readU16(is);
667         m_generated = (flags & 0x08) ? false : true;
668
669         /*
670                 Bulk node data
671         */
672         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
673                         <<": Bulk node data"<<std::endl);
674         u8 content_width = readU8(is);
675         u8 params_width = readU8(is);
676         if(content_width != 1 && content_width != 2)
677                 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
678         if(params_width != 2)
679                 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
680         MapNode::deSerializeBulk(is, version, data, nodecount,
681                         content_width, params_width, true);
682
683         /*
684                 NodeMetadata
685         */
686         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
687                         <<": Node metadata"<<std::endl);
688         // Ignore errors
689         try {
690                 std::ostringstream oss(std::ios_base::binary);
691                 decompressZlib(is, oss);
692                 std::istringstream iss(oss.str(), std::ios_base::binary);
693                 if (version >= 23)
694                         m_node_metadata.deSerialize(iss, m_gamedef->idef());
695                 else
696                         content_nodemeta_deserialize_legacy(iss,
697                                 &m_node_metadata, &m_node_timers,
698                                 m_gamedef->idef());
699         } catch(SerializationError &e) {
700                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
701                                 <<" while deserializing node metadata at ("
702                                 <<PP(getPos())<<": "<<e.what()<<std::endl;
703         }
704
705         /*
706                 Data that is only on disk
707         */
708         if(disk)
709         {
710                 // Node timers
711                 if(version == 23){
712                         // Read unused zero
713                         readU8(is);
714                 }
715                 if(version == 24){
716                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
717                                         <<": Node timers (ver==24)"<<std::endl);
718                         m_node_timers.deSerialize(is, version);
719                 }
720
721                 // Static objects
722                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
723                                 <<": Static objects"<<std::endl);
724                 m_static_objects.deSerialize(is);
725
726                 // Timestamp
727                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
728                                 <<": Timestamp"<<std::endl);
729                 setTimestamp(readU32(is));
730                 m_disk_timestamp = m_timestamp;
731
732                 // Dynamically re-set ids based on node names
733                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
734                                 <<": NameIdMapping"<<std::endl);
735                 NameIdMapping nimap;
736                 nimap.deSerialize(is);
737                 correctBlockNodeIds(&nimap, data, m_gamedef);
738
739                 if(version >= 25){
740                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
741                                         <<": Node timers (ver>=25)"<<std::endl);
742                         m_node_timers.deSerialize(is, version);
743                 }
744         }
745
746         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
747                         <<": Done."<<std::endl);
748 }
749
750 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
751 {
752         try {
753                 int version = readU8(is);
754                 //if(version != 1)
755                 //      throw SerializationError("unsupported MapBlock version");
756                 if(version >= 1) {
757                         readF1000(is); // deprecated heat
758                         readF1000(is); // deprecated humidity
759                 }
760         }
761         catch(SerializationError &e)
762         {
763                 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
764                                 <<": "<<e.what()<<std::endl;
765         }
766 }
767
768 /*
769         Legacy serialization
770 */
771
772 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
773 {
774         // Initialize default flags
775         is_underground = false;
776         m_day_night_differs = false;
777         m_lighting_complete = 0xFFFF;
778         m_generated = true;
779
780         // Make a temporary buffer
781         u32 ser_length = MapNode::serializedLength(version);
782         SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
783
784         // These have no compression
785         if (version <= 3 || version == 5 || version == 6) {
786                 char tmp;
787                 is.read(&tmp, 1);
788                 if (is.gcount() != 1)
789                         throw SerializationError(std::string(FUNCTION_NAME)
790                                 + ": not enough input data");
791                 is_underground = tmp;
792                 is.read((char *)*databuf_nodelist, nodecount * ser_length);
793                 if ((u32)is.gcount() != nodecount * ser_length)
794                         throw SerializationError(std::string(FUNCTION_NAME)
795                                 + ": not enough input data");
796         } else if (version <= 10) {
797                 u8 t8;
798                 is.read((char *)&t8, 1);
799                 is_underground = t8;
800
801                 {
802                         // Uncompress and set material data
803                         std::ostringstream os(std::ios_base::binary);
804                         decompress(is, os, version);
805                         std::string s = os.str();
806                         if (s.size() != nodecount)
807                                 throw SerializationError(std::string(FUNCTION_NAME)
808                                         + ": not enough input data");
809                         for (u32 i = 0; i < s.size(); i++) {
810                                 databuf_nodelist[i*ser_length] = s[i];
811                         }
812                 }
813                 {
814                         // Uncompress and set param data
815                         std::ostringstream os(std::ios_base::binary);
816                         decompress(is, os, version);
817                         std::string s = os.str();
818                         if (s.size() != nodecount)
819                                 throw SerializationError(std::string(FUNCTION_NAME)
820                                         + ": not enough input data");
821                         for (u32 i = 0; i < s.size(); i++) {
822                                 databuf_nodelist[i*ser_length + 1] = s[i];
823                         }
824                 }
825
826                 if (version >= 10) {
827                         // Uncompress and set param2 data
828                         std::ostringstream os(std::ios_base::binary);
829                         decompress(is, os, version);
830                         std::string s = os.str();
831                         if (s.size() != nodecount)
832                                 throw SerializationError(std::string(FUNCTION_NAME)
833                                         + ": not enough input data");
834                         for (u32 i = 0; i < s.size(); i++) {
835                                 databuf_nodelist[i*ser_length + 2] = s[i];
836                         }
837                 }
838         } else { // All other versions (10 to 21)
839                 u8 flags;
840                 is.read((char*)&flags, 1);
841                 is_underground = (flags & 0x01) ? true : false;
842                 m_day_night_differs = (flags & 0x02) ? true : false;
843                 if(version >= 18)
844                         m_generated = (flags & 0x08) ? false : true;
845
846                 // Uncompress data
847                 std::ostringstream os(std::ios_base::binary);
848                 decompress(is, os, version);
849                 std::string s = os.str();
850                 if (s.size() != nodecount * 3)
851                         throw SerializationError(std::string(FUNCTION_NAME)
852                                 + ": decompress resulted in size other than nodecount*3");
853
854                 // deserialize nodes from buffer
855                 for (u32 i = 0; i < nodecount; i++) {
856                         databuf_nodelist[i*ser_length] = s[i];
857                         databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
858                         databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
859                 }
860
861                 /*
862                         NodeMetadata
863                 */
864                 if (version >= 14) {
865                         // Ignore errors
866                         try {
867                                 if (version <= 15) {
868                                         std::string data = deSerializeString(is);
869                                         std::istringstream iss(data, std::ios_base::binary);
870                                         content_nodemeta_deserialize_legacy(iss,
871                                                 &m_node_metadata, &m_node_timers,
872                                                 m_gamedef->idef());
873                                 } else {
874                                         //std::string data = deSerializeLongString(is);
875                                         std::ostringstream oss(std::ios_base::binary);
876                                         decompressZlib(is, oss);
877                                         std::istringstream iss(oss.str(), std::ios_base::binary);
878                                         content_nodemeta_deserialize_legacy(iss,
879                                                 &m_node_metadata, &m_node_timers,
880                                                 m_gamedef->idef());
881                                 }
882                         } catch(SerializationError &e) {
883                                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
884                                                 <<" while deserializing node metadata"<<std::endl;
885                         }
886                 }
887         }
888
889         // Deserialize node data
890         for (u32 i = 0; i < nodecount; i++) {
891                 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
892         }
893
894         if (disk) {
895                 /*
896                         Versions up from 9 have block objects. (DEPRECATED)
897                 */
898                 if (version >= 9) {
899                         u16 count = readU16(is);
900                         // Not supported and length not known if count is not 0
901                         if(count != 0){
902                                 warningstream<<"MapBlock::deSerialize_pre22(): "
903                                                 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
904                                 return;
905                         }
906                 }
907
908                 /*
909                         Versions up from 15 have static objects.
910                 */
911                 if (version >= 15)
912                         m_static_objects.deSerialize(is);
913
914                 // Timestamp
915                 if (version >= 17) {
916                         setTimestamp(readU32(is));
917                         m_disk_timestamp = m_timestamp;
918                 } else {
919                         setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
920                 }
921
922                 // Dynamically re-set ids based on node names
923                 NameIdMapping nimap;
924                 // If supported, read node definition id mapping
925                 if (version >= 21) {
926                         nimap.deSerialize(is);
927                 // Else set the legacy mapping
928                 } else {
929                         content_mapnode_get_name_id_mapping(&nimap);
930                 }
931                 correctBlockNodeIds(&nimap, data, m_gamedef);
932         }
933
934
935         // Legacy data changes
936         // This code has to convert from pre-22 to post-22 format.
937         INodeDefManager *nodedef = m_gamedef->ndef();
938         for(u32 i=0; i<nodecount; i++)
939         {
940                 const ContentFeatures &f = nodedef->get(data[i].getContent());
941                 // Mineral
942                 if(nodedef->getId("default:stone") == data[i].getContent()
943                                 && data[i].getParam1() == 1)
944                 {
945                         data[i].setContent(nodedef->getId("default:stone_with_coal"));
946                         data[i].setParam1(0);
947                 }
948                 else if(nodedef->getId("default:stone") == data[i].getContent()
949                                 && data[i].getParam1() == 2)
950                 {
951                         data[i].setContent(nodedef->getId("default:stone_with_iron"));
952                         data[i].setParam1(0);
953                 }
954                 // facedir_simple
955                 if(f.legacy_facedir_simple)
956                 {
957                         data[i].setParam2(data[i].getParam1());
958                         data[i].setParam1(0);
959                 }
960                 // wall_mounted
961                 if(f.legacy_wallmounted)
962                 {
963                         u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
964                         u8 dir_old_format = data[i].getParam2();
965                         u8 dir_new_format = 0;
966                         for(u8 j=0; j<8; j++)
967                         {
968                                 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
969                                 {
970                                         dir_new_format = j;
971                                         break;
972                                 }
973                         }
974                         data[i].setParam2(dir_new_format);
975                 }
976         }
977
978 }
979
980 /*
981         Get a quick string to describe what a block actually contains
982 */
983 std::string analyze_block(MapBlock *block)
984 {
985         if(block == NULL)
986                 return "NULL";
987
988         std::ostringstream desc;
989
990         v3s16 p = block->getPos();
991         char spos[25];
992         snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
993         desc<<spos;
994
995         switch(block->getModified())
996         {
997         case MOD_STATE_CLEAN:
998                 desc<<"CLEAN,           ";
999                 break;
1000         case MOD_STATE_WRITE_AT_UNLOAD:
1001                 desc<<"WRITE_AT_UNLOAD, ";
1002                 break;
1003         case MOD_STATE_WRITE_NEEDED:
1004                 desc<<"WRITE_NEEDED,    ";
1005                 break;
1006         default:
1007                 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1008         }
1009
1010         if(block->isGenerated())
1011                 desc<<"is_gen [X], ";
1012         else
1013                 desc<<"is_gen [ ], ";
1014
1015         if(block->getIsUnderground())
1016                 desc<<"is_ug [X], ";
1017         else
1018                 desc<<"is_ug [ ], ";
1019
1020         desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1021
1022         if(block->isDummy())
1023         {
1024                 desc<<"Dummy, ";
1025         }
1026         else
1027         {
1028                 bool full_ignore = true;
1029                 bool some_ignore = false;
1030                 bool full_air = true;
1031                 bool some_air = false;
1032                 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1033                 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1034                 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1035                 {
1036                         v3s16 p(x0,y0,z0);
1037                         MapNode n = block->getNodeNoEx(p);
1038                         content_t c = n.getContent();
1039                         if(c == CONTENT_IGNORE)
1040                                 some_ignore = true;
1041                         else
1042                                 full_ignore = false;
1043                         if(c == CONTENT_AIR)
1044                                 some_air = true;
1045                         else
1046                                 full_air = false;
1047                 }
1048
1049                 desc<<"content {";
1050
1051                 std::ostringstream ss;
1052
1053                 if(full_ignore)
1054                         ss<<"IGNORE (full), ";
1055                 else if(some_ignore)
1056                         ss<<"IGNORE, ";
1057
1058                 if(full_air)
1059                         ss<<"AIR (full), ";
1060                 else if(some_air)
1061                         ss<<"AIR, ";
1062
1063                 if(ss.str().size()>=2)
1064                         desc<<ss.str().substr(0, ss.str().size()-2);
1065
1066                 desc<<"}, ";
1067         }
1068
1069         return desc.str().substr(0, desc.str().size()-2);
1070 }
1071
1072
1073 //END