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