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