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