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