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