3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
24 #include "client/mesh.h"
25 #include "client/shader.h"
26 #include "client/client.h"
27 #include "client/renderingengine.h"
28 #include "client/tile.h"
29 #include <IMeshManipulator.h>
33 #include "nameidmapping.h"
34 #include "util/numeric.h"
35 #include "util/serialize.h"
36 #include "util/string.h"
37 #include "exceptions.h"
41 #include <fstream> // Used in applyTextureOverrides()
51 type = NODEBOX_REGULAR;
54 // default is sign/ladder-like
55 wall_top = aabb3f(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2);
56 wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2);
57 wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2);
58 // no default for other parts
62 void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
73 writeU16(os, fixed.size());
74 for (const aabb3f &nodebox : fixed) {
75 writeV3F32(os, nodebox.MinEdge);
76 writeV3F32(os, nodebox.MaxEdge);
79 case NODEBOX_WALLMOUNTED:
82 writeV3F32(os, wall_top.MinEdge);
83 writeV3F32(os, wall_top.MaxEdge);
84 writeV3F32(os, wall_bottom.MinEdge);
85 writeV3F32(os, wall_bottom.MaxEdge);
86 writeV3F32(os, wall_side.MinEdge);
87 writeV3F32(os, wall_side.MaxEdge);
89 case NODEBOX_CONNECTED: {
92 #define WRITEBOX(box) \
93 writeU16(os, (box).size()); \
94 for (const aabb3f &i: (box)) { \
95 writeV3F32(os, i.MinEdge); \
96 writeV3F32(os, i.MaxEdge); \
99 const auto &c = getConnected();
102 WRITEBOX(c.connect_top);
103 WRITEBOX(c.connect_bottom);
104 WRITEBOX(c.connect_front);
105 WRITEBOX(c.connect_left);
106 WRITEBOX(c.connect_back);
107 WRITEBOX(c.connect_right);
108 WRITEBOX(c.disconnected_top);
109 WRITEBOX(c.disconnected_bottom);
110 WRITEBOX(c.disconnected_front);
111 WRITEBOX(c.disconnected_left);
112 WRITEBOX(c.disconnected_back);
113 WRITEBOX(c.disconnected_right);
114 WRITEBOX(c.disconnected);
115 WRITEBOX(c.disconnected_sides);
124 void NodeBox::deSerialize(std::istream &is)
126 int version = readU8(is);
128 throw SerializationError("unsupported NodeBox version");
132 type = (enum NodeBoxType)readU8(is);
134 if(type == NODEBOX_FIXED || type == NODEBOX_LEVELED)
136 u16 fixed_count = readU16(is);
140 box.MinEdge = readV3F32(is);
141 box.MaxEdge = readV3F32(is);
142 fixed.push_back(box);
145 else if(type == NODEBOX_WALLMOUNTED)
147 wall_top.MinEdge = readV3F32(is);
148 wall_top.MaxEdge = readV3F32(is);
149 wall_bottom.MinEdge = readV3F32(is);
150 wall_bottom.MaxEdge = readV3F32(is);
151 wall_side.MinEdge = readV3F32(is);
152 wall_side.MaxEdge = readV3F32(is);
154 else if (type == NODEBOX_CONNECTED)
156 #define READBOXES(box) { \
157 count = readU16(is); \
158 (box).reserve(count); \
160 v3f min = readV3F32(is); \
161 v3f max = readV3F32(is); \
162 (box).emplace_back(min, max); }; }
166 auto &c = getConnected();
169 READBOXES(c.connect_top);
170 READBOXES(c.connect_bottom);
171 READBOXES(c.connect_front);
172 READBOXES(c.connect_left);
173 READBOXES(c.connect_back);
174 READBOXES(c.connect_right);
175 READBOXES(c.disconnected_top);
176 READBOXES(c.disconnected_bottom);
177 READBOXES(c.disconnected_front);
178 READBOXES(c.disconnected_left);
179 READBOXES(c.disconnected_back);
180 READBOXES(c.disconnected_right);
181 READBOXES(c.disconnected);
182 READBOXES(c.disconnected_sides);
190 #define TILE_FLAG_BACKFACE_CULLING (1 << 0)
191 #define TILE_FLAG_TILEABLE_HORIZONTAL (1 << 1)
192 #define TILE_FLAG_TILEABLE_VERTICAL (1 << 2)
193 #define TILE_FLAG_HAS_COLOR (1 << 3)
194 #define TILE_FLAG_HAS_SCALE (1 << 4)
195 #define TILE_FLAG_HAS_ALIGN_STYLE (1 << 5)
197 void TileDef::serialize(std::ostream &os, u16 protocol_version) const
199 // protocol_version >= 36
201 writeU8(os, version);
203 if (protocol_version > 39) {
204 os << serializeString16(name);
206 // Before f018737, TextureSource::getTextureAverageColor did not handle
207 // missing textures. "[png" can be used as base texture, but is not known
208 // on older clients. Hence use "blank.png" to avoid this problem.
209 // To be forward-compatible with future base textures/modifiers,
210 // we apply the same prefix to any texture beginning with [,
211 // except for the ones that are supported on older clients.
212 bool pass_through = true;
214 if (!name.empty() && name[0] == '[') {
215 pass_through = str_starts_with(name, "[combine:") ||
216 str_starts_with(name, "[inventorycube{") ||
217 str_starts_with(name, "[lowpart:");
221 os << serializeString16(name);
223 os << serializeString16("blank.png^" + name);
225 animation.serialize(os, version);
226 bool has_scale = scale > 0;
228 if (backface_culling)
229 flags |= TILE_FLAG_BACKFACE_CULLING;
230 if (tileable_horizontal)
231 flags |= TILE_FLAG_TILEABLE_HORIZONTAL;
232 if (tileable_vertical)
233 flags |= TILE_FLAG_TILEABLE_VERTICAL;
235 flags |= TILE_FLAG_HAS_COLOR;
237 flags |= TILE_FLAG_HAS_SCALE;
238 if (align_style != ALIGN_STYLE_NODE)
239 flags |= TILE_FLAG_HAS_ALIGN_STYLE;
242 writeU8(os, color.getRed());
243 writeU8(os, color.getGreen());
244 writeU8(os, color.getBlue());
248 if (align_style != ALIGN_STYLE_NODE)
249 writeU8(os, align_style);
252 void TileDef::deSerialize(std::istream &is, u8 contentfeatures_version,
253 NodeDrawType drawtype)
255 int version = readU8(is);
257 throw SerializationError("unsupported TileDef version");
258 name = deSerializeString16(is);
259 animation.deSerialize(is, version);
260 u16 flags = readU16(is);
261 backface_culling = flags & TILE_FLAG_BACKFACE_CULLING;
262 tileable_horizontal = flags & TILE_FLAG_TILEABLE_HORIZONTAL;
263 tileable_vertical = flags & TILE_FLAG_TILEABLE_VERTICAL;
264 has_color = flags & TILE_FLAG_HAS_COLOR;
265 bool has_scale = flags & TILE_FLAG_HAS_SCALE;
266 bool has_align_style = flags & TILE_FLAG_HAS_ALIGN_STYLE;
268 color.setRed(readU8(is));
269 color.setGreen(readU8(is));
270 color.setBlue(readU8(is));
272 scale = has_scale ? readU8(is) : 0;
274 align_style = static_cast<AlignStyle>(readU8(is));
276 align_style = ALIGN_STYLE_NODE;
279 void TextureSettings::readSettings()
281 connected_glass = g_settings->getBool("connected_glass");
282 opaque_water = g_settings->getBool("opaque_water");
283 bool smooth_lighting = g_settings->getBool("smooth_lighting");
284 enable_mesh_cache = g_settings->getBool("enable_mesh_cache");
285 enable_minimap = g_settings->getBool("enable_minimap");
286 node_texture_size = g_settings->getU16("texture_min_size");
287 std::string leaves_style_str = g_settings->get("leaves_style");
288 std::string world_aligned_mode_str = g_settings->get("world_aligned_mode");
289 std::string autoscale_mode_str = g_settings->get("autoscale_mode");
291 // Mesh cache is not supported in combination with smooth lighting
293 enable_mesh_cache = false;
295 if (leaves_style_str == "fancy") {
296 leaves_style = LEAVES_FANCY;
297 } else if (leaves_style_str == "simple") {
298 leaves_style = LEAVES_SIMPLE;
300 leaves_style = LEAVES_OPAQUE;
303 if (world_aligned_mode_str == "enable")
304 world_aligned_mode = WORLDALIGN_ENABLE;
305 else if (world_aligned_mode_str == "force_solid")
306 world_aligned_mode = WORLDALIGN_FORCE;
307 else if (world_aligned_mode_str == "force_nodebox")
308 world_aligned_mode = WORLDALIGN_FORCE_NODEBOX;
310 world_aligned_mode = WORLDALIGN_DISABLE;
312 if (autoscale_mode_str == "enable")
313 autoscale_mode = AUTOSCALE_ENABLE;
314 else if (autoscale_mode_str == "force")
315 autoscale_mode = AUTOSCALE_FORCE;
317 autoscale_mode = AUTOSCALE_DISABLE;
324 ContentFeatures::ContentFeatures()
329 ContentFeatures::~ContentFeatures()
332 for (u16 j = 0; j < 6; j++) {
333 delete tiles[j].layers[0].frames;
334 delete tiles[j].layers[1].frames;
336 for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
337 delete special_tiles[j].layers[0].frames;
341 void ContentFeatures::reset()
348 visual_solidness = 0;
349 backface_culling = true;
352 has_on_construct = false;
353 has_on_destruct = false;
354 has_after_destruct = false;
358 NOTE: Most of this is always overridden by the default values given
363 // Unknown nodes can be dug
364 groups["dig_immediate"] = 2;
365 drawtype = NDT_NORMAL;
368 for (auto &i : mesh_ptr)
370 minimap_color = video::SColor(0, 0, 0, 0);
373 for (auto &i : tiledef)
375 for (auto &j : tiledef_special)
377 alpha = ALPHAMODE_OPAQUE;
378 post_effect_color = video::SColor(0, 0, 0, 0);
379 param_type = CPT_NONE;
380 param_type_2 = CPT2_NONE;
381 is_ground_content = false;
382 light_propagates = false;
383 sunlight_propagates = false;
388 buildable_to = false;
390 rightclickable = true;
392 leveled_max = LEVELED_MAX;
393 liquid_type = LIQUID_NONE;
394 liquid_alternative_flowing = "";
395 liquid_alternative_flowing_id = CONTENT_IGNORE;
396 liquid_alternative_source = "";
397 liquid_alternative_source_id = CONTENT_IGNORE;
398 liquid_viscosity = 0;
399 liquid_renewable = true;
400 liquid_range = LIQUID_LEVEL_MAX+1;
403 damage_per_second = 0;
405 selection_box.reset();
406 collision_box.reset();
408 legacy_facedir_simple = false;
409 legacy_wallmounted = false;
410 sound_footstep = SimpleSoundSpec();
411 sound_dig = SimpleSoundSpec("__group");
412 sound_dug = SimpleSoundSpec();
414 connects_to_ids.clear();
416 color = video::SColor(0xFFFFFFFF);
419 node_dig_prediction = "air";
421 liquid_move_physics = false;
424 void ContentFeatures::setAlphaFromLegacy(u8 legacy_alpha)
426 // No special handling for nodebox/mesh here as it doesn't make sense to
427 // throw warnings when the server is too old to support the "correct" way
430 alpha = legacy_alpha == 255 ? ALPHAMODE_OPAQUE : ALPHAMODE_CLIP;
433 case NDT_FLOWINGLIQUID:
434 alpha = legacy_alpha == 255 ? ALPHAMODE_OPAQUE : ALPHAMODE_BLEND;
437 alpha = legacy_alpha == 255 ? ALPHAMODE_CLIP : ALPHAMODE_BLEND;
442 u8 ContentFeatures::getAlphaForLegacy() const
444 // This is so simple only because 255 and 0 mean wildly different things
445 // depending on drawtype...
446 return alpha == ALPHAMODE_OPAQUE ? 255 : 0;
449 void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
451 const u8 version = CONTENTFEATURES_VERSION;
452 writeU8(os, version);
455 os << serializeString16(name);
456 writeU16(os, groups.size());
457 for (const auto &group : groups) {
458 os << serializeString16(group.first);
459 if (protocol_version < 41 && group.first.compare("bouncy") == 0) {
460 // Old clients may choke on negative bouncy value
461 writeS16(os, abs(group.second));
463 writeS16(os, group.second);
466 writeU8(os, param_type);
467 writeU8(os, param_type_2);
470 writeU8(os, drawtype);
471 os << serializeString16(mesh);
472 writeF32(os, visual_scale);
474 for (const TileDef &td : tiledef)
475 td.serialize(os, protocol_version);
476 for (const TileDef &td : tiledef_overlay)
477 td.serialize(os, protocol_version);
478 writeU8(os, CF_SPECIAL_COUNT);
479 for (const TileDef &td : tiledef_special) {
480 td.serialize(os, protocol_version);
482 writeU8(os, getAlphaForLegacy());
483 writeU8(os, color.getRed());
484 writeU8(os, color.getGreen());
485 writeU8(os, color.getBlue());
486 os << serializeString16(palette_name);
488 writeU8(os, connect_sides);
489 writeU16(os, connects_to_ids.size());
490 for (u16 connects_to_id : connects_to_ids)
491 writeU16(os, connects_to_id);
492 writeARGB8(os, post_effect_color);
493 writeU8(os, leveled);
496 writeU8(os, light_propagates);
497 writeU8(os, sunlight_propagates);
498 writeU8(os, light_source);
501 writeU8(os, is_ground_content);
504 writeU8(os, walkable);
505 writeU8(os, pointable);
506 writeU8(os, diggable);
507 writeU8(os, climbable);
508 writeU8(os, buildable_to);
509 writeU8(os, rightclickable);
510 writeU32(os, damage_per_second);
513 LiquidType liquid_type_bc = liquid_type;
514 if (protocol_version <= 39) {
515 // Since commit 7f25823, liquid drawtypes can be used even with LIQUID_NONE
516 // solution: force liquid type accordingly to accepted values
517 if (drawtype == NDT_LIQUID)
518 liquid_type_bc = LIQUID_SOURCE;
519 else if (drawtype == NDT_FLOWINGLIQUID)
520 liquid_type_bc = LIQUID_FLOWING;
522 writeU8(os, liquid_type_bc);
523 os << serializeString16(liquid_alternative_flowing);
524 os << serializeString16(liquid_alternative_source);
525 writeU8(os, liquid_viscosity);
526 writeU8(os, liquid_renewable);
527 writeU8(os, liquid_range);
528 writeU8(os, drowning);
529 writeU8(os, floodable);
532 node_box.serialize(os, protocol_version);
533 selection_box.serialize(os, protocol_version);
534 collision_box.serialize(os, protocol_version);
537 sound_footstep.serialize(os, version);
538 sound_dig.serialize(os, version);
539 sound_dug.serialize(os, version);
542 writeU8(os, legacy_facedir_simple);
543 writeU8(os, legacy_wallmounted);
546 os << serializeString16(node_dig_prediction);
547 writeU8(os, leveled_max);
549 writeU8(os, move_resistance);
550 writeU8(os, liquid_move_physics);
553 void ContentFeatures::deSerialize(std::istream &is)
556 const u8 version = readU8(is);
557 if (version < CONTENTFEATURES_VERSION)
558 throw SerializationError("unsupported ContentFeatures version");
561 name = deSerializeString16(is);
563 u32 groups_size = readU16(is);
564 for (u32 i = 0; i < groups_size; i++) {
565 std::string name = deSerializeString16(is);
566 int value = readS16(is);
567 groups[name] = value;
569 param_type = (enum ContentParamType) readU8(is);
570 param_type_2 = (enum ContentParamType2) readU8(is);
573 drawtype = (enum NodeDrawType) readU8(is);
574 mesh = deSerializeString16(is);
575 visual_scale = readF32(is);
577 throw SerializationError("unsupported tile count");
578 for (TileDef &td : tiledef)
579 td.deSerialize(is, version, drawtype);
580 for (TileDef &td : tiledef_overlay)
581 td.deSerialize(is, version, drawtype);
582 if (readU8(is) != CF_SPECIAL_COUNT)
583 throw SerializationError("unsupported CF_SPECIAL_COUNT");
584 for (TileDef &td : tiledef_special)
585 td.deSerialize(is, version, drawtype);
586 setAlphaFromLegacy(readU8(is));
587 color.setRed(readU8(is));
588 color.setGreen(readU8(is));
589 color.setBlue(readU8(is));
590 palette_name = deSerializeString16(is);
592 connect_sides = readU8(is);
593 u16 connects_to_size = readU16(is);
594 connects_to_ids.clear();
595 for (u16 i = 0; i < connects_to_size; i++)
596 connects_to_ids.push_back(readU16(is));
597 post_effect_color = readARGB8(is);
598 leveled = readU8(is);
601 light_propagates = readU8(is);
602 sunlight_propagates = readU8(is);
603 light_source = readU8(is);
604 light_source = MYMIN(light_source, LIGHT_MAX);
607 is_ground_content = readU8(is);
610 walkable = readU8(is);
611 pointable = readU8(is);
612 diggable = readU8(is);
613 climbable = readU8(is);
614 buildable_to = readU8(is);
615 rightclickable = readU8(is);
616 damage_per_second = readU32(is);
619 liquid_type = (enum LiquidType) readU8(is);
620 liquid_move_physics = liquid_type != LIQUID_NONE;
621 liquid_alternative_flowing = deSerializeString16(is);
622 liquid_alternative_source = deSerializeString16(is);
623 liquid_viscosity = readU8(is);
624 move_resistance = liquid_viscosity; // set default move_resistance
625 liquid_renewable = readU8(is);
626 liquid_range = readU8(is);
627 drowning = readU8(is);
628 floodable = readU8(is);
631 node_box.deSerialize(is);
632 selection_box.deSerialize(is);
633 collision_box.deSerialize(is);
636 sound_footstep.deSerialize(is, version);
637 sound_dig.deSerialize(is, version);
638 sound_dug.deSerialize(is, version);
640 // read legacy properties
641 legacy_facedir_simple = readU8(is);
642 legacy_wallmounted = readU8(is);
645 node_dig_prediction = deSerializeString16(is);
648 if (is.eof()) /* readU8 doesn't throw exceptions so we have to do this */
649 throw SerializationError("");
654 throw SerializationError("");
655 alpha = static_cast<enum AlphaMode>(tmp);
659 throw SerializationError("");
660 move_resistance = tmp;
664 throw SerializationError("");
665 liquid_move_physics = tmp;
666 } catch(SerializationError &e) {};
670 static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
671 const TileSpec &tile, const TileDef &tiledef, video::SColor color,
672 u8 material_type, u32 shader_id, bool backface_culling,
673 const TextureSettings &tsettings)
675 layer->shader_id = shader_id;
676 layer->texture = tsrc->getTextureForMesh(tiledef.name, &layer->texture_id);
677 layer->material_type = material_type;
679 bool has_scale = tiledef.scale > 0;
680 bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE ||
681 (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale);
682 if (use_autoscale && layer->texture) {
683 auto texture_size = layer->texture->getOriginalSize();
684 float base_size = tsettings.node_texture_size;
685 float size = std::fmin(texture_size.Width, texture_size.Height);
686 layer->scale = std::fmax(base_size, size) / base_size;
687 } else if (has_scale) {
688 layer->scale = tiledef.scale;
692 if (!tile.world_aligned)
695 layer->flags_texture = tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false);
698 layer->material_flags = 0;
699 if (backface_culling)
700 layer->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
701 if (tiledef.animation.type != TAT_NONE)
702 layer->material_flags |= MATERIAL_FLAG_ANIMATION;
703 if (tiledef.tileable_horizontal)
704 layer->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL;
705 if (tiledef.tileable_vertical)
706 layer->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL;
709 layer->has_color = tiledef.has_color;
710 if (tiledef.has_color)
711 layer->color = tiledef.color;
713 layer->color = color;
715 // Animation parameters
717 if (layer->material_flags & MATERIAL_FLAG_ANIMATION) {
718 assert(layer->texture);
720 tiledef.animation.determineParams(layer->texture->getOriginalSize(),
721 &frame_count, &frame_length_ms, NULL);
722 layer->animation_frame_count = frame_count;
723 layer->animation_frame_length_ms = frame_length_ms;
726 if (frame_count == 1) {
727 layer->material_flags &= ~MATERIAL_FLAG_ANIMATION;
729 assert(layer->texture);
731 layer->frames = new std::vector<FrameSpec>();
732 layer->frames->resize(frame_count);
734 std::ostringstream os(std::ios::binary);
735 for (int i = 0; i < frame_count; i++) {
740 tiledef.animation.getTextureModifer(os,
741 layer->texture->getOriginalSize(), i);
743 frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id);
744 if (layer->normal_texture)
745 frame.normal_texture = tsrc->getNormalTexture(os.str());
746 frame.flags_texture = layer->flags_texture;
747 (*layer->frames)[i] = frame;
752 bool ContentFeatures::textureAlphaCheck(ITextureSource *tsrc, const TileDef *tiles, int length)
754 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
755 static thread_local bool long_warning_printed = false;
756 std::set<std::string> seen;
758 for (int i = 0; i < length; i++) {
759 if (seen.find(tiles[i].name) != seen.end())
761 seen.insert(tiles[i].name);
763 // Load the texture and see if there's any transparent pixels
764 video::ITexture *texture = tsrc->getTexture(tiles[i].name);
765 video::IImage *image = driver->createImage(texture,
766 core::position2d<s32>(0, 0), texture->getOriginalSize());
769 core::dimension2d<u32> dim = image->getDimension();
771 for (u16 x = 0; x < dim.Width; x++) {
772 for (u16 y = 0; y < dim.Height; y++) {
773 if (image->getPixel(x, y).getAlpha() < 255) {
784 warningstream << "Texture \"" << tiles[i].name << "\" of "
785 << name << " has transparency, assuming "
786 "use_texture_alpha = \"clip\"." << std::endl;
787 if (!long_warning_printed) {
788 warningstream << " This warning can be a false-positive if "
789 "unused pixels in the texture are transparent. However if "
790 "it is meant to be transparent, you *MUST* update the "
791 "nodedef and set use_texture_alpha = \"clip\"! This "
792 "compatibility code will be removed in a few releases."
794 long_warning_printed = true;
801 bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype)
803 if (style == ALIGN_STYLE_WORLD)
805 if (mode == WORLDALIGN_DISABLE)
807 if (style == ALIGN_STYLE_USER_DEFINED)
809 if (drawtype == NDT_NORMAL)
810 return mode >= WORLDALIGN_FORCE;
811 if (drawtype == NDT_NODEBOX)
812 return mode >= WORLDALIGN_FORCE_NODEBOX;
816 void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
817 scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
819 // minimap pixel color - the average color of a texture
820 if (tsettings.enable_minimap && !tiledef[0].name.empty())
821 minimap_color = tsrc->getTextureAverageColor(tiledef[0].name);
823 // Figure out the actual tiles to use
825 for (u32 j = 0; j < 6; j++) {
826 tdef[j] = tiledef[j];
827 if (tdef[j].name.empty()) {
828 tdef[j].name = "no_texture.png";
829 tdef[j].backface_culling = false;
832 // also the overlay tiles
833 TileDef tdef_overlay[6];
834 for (u32 j = 0; j < 6; j++)
835 tdef_overlay[j] = tiledef_overlay[j];
836 // also the special tiles
837 TileDef tdef_spec[6];
838 for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) {
839 tdef_spec[j] = tiledef_special[j];
842 bool is_liquid = false;
844 if (alpha == ALPHAMODE_LEGACY_COMPAT) {
845 // Before working with the alpha mode, resolve any legacy kludges
846 alpha = textureAlphaCheck(tsrc, tdef, 6) ? ALPHAMODE_CLIP : ALPHAMODE_OPAQUE;
849 MaterialType material_type = alpha == ALPHAMODE_OPAQUE ?
850 TILE_MATERIAL_OPAQUE : (alpha == ALPHAMODE_CLIP ? TILE_MATERIAL_BASIC :
851 TILE_MATERIAL_ALPHA);
862 if (tsettings.opaque_water)
863 alpha = ALPHAMODE_OPAQUE;
867 case NDT_FLOWINGLIQUID:
869 if (tsettings.opaque_water)
870 alpha = ALPHAMODE_OPAQUE;
875 visual_solidness = 1;
877 case NDT_GLASSLIKE_FRAMED:
879 visual_solidness = 1;
881 case NDT_GLASSLIKE_FRAMED_OPTIONAL:
883 visual_solidness = 1;
884 drawtype = tsettings.connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE;
888 visual_solidness = 1;
890 case NDT_ALLFACES_OPTIONAL:
891 if (tsettings.leaves_style == LEAVES_FANCY) {
892 drawtype = NDT_ALLFACES;
894 visual_solidness = 1;
895 } else if (tsettings.leaves_style == LEAVES_SIMPLE) {
896 for (u32 j = 0; j < 6; j++) {
897 if (!tdef_spec[j].name.empty())
898 tdef[j].name = tdef_spec[j].name;
900 drawtype = NDT_GLASSLIKE;
902 visual_solidness = 1;
904 drawtype = NDT_NORMAL;
906 for (TileDef &td : tdef)
907 td.name += std::string("^[noalpha");
910 material_type = TILE_MATERIAL_WAVING_LEAVES;
915 material_type = TILE_MATERIAL_WAVING_PLANTS;
924 material_type = TILE_MATERIAL_WAVING_PLANTS;
925 } else if (waving == 2) {
926 material_type = TILE_MATERIAL_WAVING_LEAVES;
927 } else if (waving == 3) {
928 material_type = alpha == ALPHAMODE_OPAQUE ?
929 TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ?
930 TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
939 case NDT_PLANTLIKE_ROOTED:
946 material_type = alpha == ALPHAMODE_OPAQUE ?
947 TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ?
948 TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
950 material_type = alpha == ALPHAMODE_OPAQUE ? TILE_MATERIAL_LIQUID_OPAQUE :
951 TILE_MATERIAL_LIQUID_TRANSPARENT;
955 u32 tile_shader = shdsrc->getShader("nodes_shader", material_type, drawtype);
957 MaterialType overlay_material = material_type;
958 if (overlay_material == TILE_MATERIAL_OPAQUE)
959 overlay_material = TILE_MATERIAL_BASIC;
960 else if (overlay_material == TILE_MATERIAL_LIQUID_OPAQUE)
961 overlay_material = TILE_MATERIAL_LIQUID_TRANSPARENT;
963 u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype);
965 // Tiles (fill in f->tiles[])
966 for (u16 j = 0; j < 6; j++) {
967 tiles[j].world_aligned = isWorldAligned(tdef[j].align_style,
968 tsettings.world_aligned_mode, drawtype);
969 fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j],
970 color, material_type, tile_shader,
971 tdef[j].backface_culling, tsettings);
972 if (!tdef_overlay[j].name.empty())
973 fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j],
974 color, overlay_material, overlay_shader,
975 tdef[j].backface_culling, tsettings);
978 MaterialType special_material = material_type;
979 if (drawtype == NDT_PLANTLIKE_ROOTED) {
981 special_material = TILE_MATERIAL_WAVING_PLANTS;
982 else if (waving == 2)
983 special_material = TILE_MATERIAL_WAVING_LEAVES;
985 u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype);
987 // Special tiles (fill in f->special_tiles[])
988 for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
989 fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j],
990 color, special_material, special_shader,
991 tdef_spec[j].backface_culling, tsettings);
993 if (param_type_2 == CPT2_COLOR ||
994 param_type_2 == CPT2_COLORED_FACEDIR ||
995 param_type_2 == CPT2_COLORED_WALLMOUNTED ||
996 param_type_2 == CPT2_COLORED_DEGROTATE)
997 palette = tsrc->getPalette(palette_name);
999 if (drawtype == NDT_MESH && !mesh.empty()) {
1000 // Meshnode drawtype
1001 // Read the mesh and apply scale
1002 mesh_ptr[0] = client->getMesh(mesh);
1004 v3f scale = v3f(1.0, 1.0, 1.0) * BS * visual_scale;
1005 scaleMesh(mesh_ptr[0], scale);
1006 recalculateBoundingBox(mesh_ptr[0]);
1007 meshmanip->recalculateNormals(mesh_ptr[0], true, false);
1011 //Cache 6dfacedir and wallmounted rotated clones of meshes
1012 if (tsettings.enable_mesh_cache && mesh_ptr[0] &&
1013 (param_type_2 == CPT2_FACEDIR
1014 || param_type_2 == CPT2_COLORED_FACEDIR)) {
1015 for (u16 j = 1; j < 24; j++) {
1016 mesh_ptr[j] = cloneMesh(mesh_ptr[0]);
1017 rotateMeshBy6dFacedir(mesh_ptr[j], j);
1018 recalculateBoundingBox(mesh_ptr[j]);
1019 meshmanip->recalculateNormals(mesh_ptr[j], true, false);
1021 } else if (tsettings.enable_mesh_cache && mesh_ptr[0]
1022 && (param_type_2 == CPT2_WALLMOUNTED ||
1023 param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
1024 static const u8 wm_to_6d[6] = { 20, 0, 16 + 1, 12 + 3, 8, 4 + 2 };
1025 for (u16 j = 1; j < 6; j++) {
1026 mesh_ptr[j] = cloneMesh(mesh_ptr[0]);
1027 rotateMeshBy6dFacedir(mesh_ptr[j], wm_to_6d[j]);
1028 recalculateBoundingBox(mesh_ptr[j]);
1029 meshmanip->recalculateNormals(mesh_ptr[j], true, false);
1031 rotateMeshBy6dFacedir(mesh_ptr[0], wm_to_6d[0]);
1032 recalculateBoundingBox(mesh_ptr[0]);
1033 meshmanip->recalculateNormals(mesh_ptr[0], true, false);
1045 NodeDefManager::NodeDefManager()
1051 NodeDefManager::~NodeDefManager()
1054 for (ContentFeatures &f : m_content_features) {
1055 for (auto &j : f.mesh_ptr) {
1064 void NodeDefManager::clear()
1066 m_content_features.clear();
1067 m_name_id_mapping.clear();
1068 m_name_id_mapping_with_aliases.clear();
1069 m_group_to_items.clear();
1071 m_selection_box_union.reset(0,0,0);
1072 m_selection_box_int_union.reset(0,0,0);
1074 resetNodeResolveState();
1076 u32 initial_length = 0;
1077 initial_length = MYMAX(initial_length, CONTENT_UNKNOWN + 1);
1078 initial_length = MYMAX(initial_length, CONTENT_AIR + 1);
1079 initial_length = MYMAX(initial_length, CONTENT_IGNORE + 1);
1080 m_content_features.resize(initial_length);
1082 // Set CONTENT_UNKNOWN
1086 for (int t = 0; t < 6; t++)
1087 f.tiledef[t].name = "unknown_node.png";
1088 // Insert directly into containers
1089 content_t c = CONTENT_UNKNOWN;
1090 m_content_features[c] = f;
1091 addNameIdMapping(c, f.name);
1098 f.drawtype = NDT_AIRLIKE;
1099 f.param_type = CPT_LIGHT;
1100 f.light_propagates = true;
1101 f.sunlight_propagates = true;
1103 f.pointable = false;
1105 f.buildable_to = true;
1107 f.is_ground_content = true;
1108 // Insert directly into containers
1109 content_t c = CONTENT_AIR;
1110 m_content_features[c] = f;
1111 addNameIdMapping(c, f.name);
1114 // Set CONTENT_IGNORE
1118 f.drawtype = NDT_AIRLIKE;
1119 f.param_type = CPT_NONE;
1120 f.light_propagates = false;
1121 f.sunlight_propagates = false;
1123 f.pointable = false;
1125 f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs
1126 f.is_ground_content = true;
1127 // Insert directly into containers
1128 content_t c = CONTENT_IGNORE;
1129 m_content_features[c] = f;
1130 addNameIdMapping(c, f.name);
1135 bool NodeDefManager::getId(const std::string &name, content_t &result) const
1137 std::unordered_map<std::string, content_t>::const_iterator
1138 i = m_name_id_mapping_with_aliases.find(name);
1139 if(i == m_name_id_mapping_with_aliases.end())
1146 content_t NodeDefManager::getId(const std::string &name) const
1148 content_t id = CONTENT_IGNORE;
1154 bool NodeDefManager::getIds(const std::string &name,
1155 std::vector<content_t> &result) const
1157 //TimeTaker t("getIds", NULL, PRECISION_MICRO);
1158 if (name.substr(0,6) != "group:") {
1159 content_t id = CONTENT_IGNORE;
1160 bool exists = getId(name, id);
1162 result.push_back(id);
1165 std::string group = name.substr(6);
1167 std::unordered_map<std::string, std::vector<content_t>>::const_iterator
1168 i = m_group_to_items.find(group);
1169 if (i == m_group_to_items.end())
1172 const std::vector<content_t> &items = i->second;
1173 result.insert(result.end(), items.begin(), items.end());
1174 //printf("getIds: %dus\n", t.stop());
1179 const ContentFeatures& NodeDefManager::get(const std::string &name) const
1181 content_t id = CONTENT_UNKNOWN;
1187 // returns CONTENT_IGNORE if no free ID found
1188 content_t NodeDefManager::allocateId()
1190 for (content_t id = m_next_id;
1191 id >= m_next_id; // overflow?
1193 while (id >= m_content_features.size()) {
1194 m_content_features.emplace_back();
1196 const ContentFeatures &f = m_content_features[id];
1197 if (f.name.empty()) {
1202 // If we arrive here, an overflow occurred in id.
1203 // That means no ID was found
1204 return CONTENT_IGNORE;
1209 * Returns the smallest box that contains all boxes
1210 * in the vector. Box_union is expanded.
1211 * @param[in] boxes the vector containing the boxes
1212 * @param[in, out] box_union the union of the arguments
1214 void boxVectorUnion(const std::vector<aabb3f> &boxes, aabb3f *box_union)
1216 for (const aabb3f &box : boxes) {
1217 box_union->addInternalBox(box);
1223 * Returns a box that contains the nodebox in every case.
1224 * The argument node_union is expanded.
1225 * @param[in] nodebox the nodebox to be measured
1226 * @param[in] features used to decide whether the nodebox
1228 * @param[in, out] box_union the union of the arguments
1230 void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features,
1233 switch(nodebox.type) {
1235 case NODEBOX_LEVELED: {
1237 aabb3f half_processed(0, 0, 0, 0, 0, 0);
1238 boxVectorUnion(nodebox.fixed, &half_processed);
1239 // Set leveled boxes to maximal
1240 if (nodebox.type == NODEBOX_LEVELED) {
1241 half_processed.MaxEdge.Y = +BS / 2;
1243 if (features.param_type_2 == CPT2_FACEDIR ||
1244 features.param_type_2 == CPT2_COLORED_FACEDIR) {
1245 // Get maximal coordinate
1247 fabsf(half_processed.MinEdge.X),
1248 fabsf(half_processed.MinEdge.Y),
1249 fabsf(half_processed.MinEdge.Z),
1250 fabsf(half_processed.MaxEdge.X),
1251 fabsf(half_processed.MaxEdge.Y),
1252 fabsf(half_processed.MaxEdge.Z) };
1254 for (float coord : coords) {
1259 // Add the union of all possible rotated boxes
1260 box_union->addInternalPoint(-max, -max, -max);
1261 box_union->addInternalPoint(+max, +max, +max);
1263 box_union->addInternalBox(half_processed);
1267 case NODEBOX_WALLMOUNTED: {
1269 box_union->addInternalBox(nodebox.wall_top);
1270 box_union->addInternalBox(nodebox.wall_bottom);
1271 // Find maximal coordinate in the X-Z plane
1273 fabsf(nodebox.wall_side.MinEdge.X),
1274 fabsf(nodebox.wall_side.MinEdge.Z),
1275 fabsf(nodebox.wall_side.MaxEdge.X),
1276 fabsf(nodebox.wall_side.MaxEdge.Z) };
1278 for (float coord : coords) {
1283 // Add the union of all possible rotated boxes
1284 box_union->addInternalPoint(-max, nodebox.wall_side.MinEdge.Y, -max);
1285 box_union->addInternalPoint(max, nodebox.wall_side.MaxEdge.Y, max);
1288 case NODEBOX_CONNECTED: {
1289 const auto &c = nodebox.getConnected();
1290 // Add all possible connected boxes
1291 boxVectorUnion(nodebox.fixed, box_union);
1292 boxVectorUnion(c.connect_top, box_union);
1293 boxVectorUnion(c.connect_bottom, box_union);
1294 boxVectorUnion(c.connect_front, box_union);
1295 boxVectorUnion(c.connect_left, box_union);
1296 boxVectorUnion(c.connect_back, box_union);
1297 boxVectorUnion(c.connect_right, box_union);
1298 boxVectorUnion(c.disconnected_top, box_union);
1299 boxVectorUnion(c.disconnected_bottom, box_union);
1300 boxVectorUnion(c.disconnected_front, box_union);
1301 boxVectorUnion(c.disconnected_left, box_union);
1302 boxVectorUnion(c.disconnected_back, box_union);
1303 boxVectorUnion(c.disconnected_right, box_union);
1304 boxVectorUnion(c.disconnected, box_union);
1305 boxVectorUnion(c.disconnected_sides, box_union);
1310 box_union->addInternalPoint(-BS / 2, -BS / 2, -BS / 2);
1311 box_union->addInternalPoint(+BS / 2, +BS / 2, +BS / 2);
1317 inline void NodeDefManager::fixSelectionBoxIntUnion()
1319 m_selection_box_int_union.MinEdge.X = floorf(
1320 m_selection_box_union.MinEdge.X / BS + 0.5f);
1321 m_selection_box_int_union.MinEdge.Y = floorf(
1322 m_selection_box_union.MinEdge.Y / BS + 0.5f);
1323 m_selection_box_int_union.MinEdge.Z = floorf(
1324 m_selection_box_union.MinEdge.Z / BS + 0.5f);
1325 m_selection_box_int_union.MaxEdge.X = ceilf(
1326 m_selection_box_union.MaxEdge.X / BS - 0.5f);
1327 m_selection_box_int_union.MaxEdge.Y = ceilf(
1328 m_selection_box_union.MaxEdge.Y / BS - 0.5f);
1329 m_selection_box_int_union.MaxEdge.Z = ceilf(
1330 m_selection_box_union.MaxEdge.Z / BS - 0.5f);
1334 void NodeDefManager::eraseIdFromGroups(content_t id)
1336 // For all groups in m_group_to_items...
1337 for (auto iter_groups = m_group_to_items.begin();
1338 iter_groups != m_group_to_items.end();) {
1339 // Get the group items vector.
1340 std::vector<content_t> &items = iter_groups->second;
1342 // Remove any occurence of the id in the group items vector.
1343 items.erase(std::remove(items.begin(), items.end(), id), items.end());
1345 // If group is empty, erase its vector from the map.
1347 iter_groups = m_group_to_items.erase(iter_groups);
1354 // IWritableNodeDefManager
1355 content_t NodeDefManager::set(const std::string &name, const ContentFeatures &def)
1359 assert(name != "ignore");
1360 assert(name == def.name);
1362 content_t id = CONTENT_IGNORE;
1363 if (!m_name_id_mapping.getId(name, id)) { // ignore aliases
1366 if (id == CONTENT_IGNORE) {
1367 warningstream << "NodeDefManager: Absolute "
1368 "limit reached" << std::endl;
1369 return CONTENT_IGNORE;
1371 assert(id != CONTENT_IGNORE);
1372 addNameIdMapping(id, name);
1375 // If there is already ContentFeatures registered for this id, clear old groups
1376 if (id < m_content_features.size())
1377 eraseIdFromGroups(id);
1379 m_content_features[id] = def;
1380 verbosestream << "NodeDefManager: registering content id \"" << id
1381 << "\": name=\"" << def.name << "\""<<std::endl;
1383 getNodeBoxUnion(def.selection_box, def, &m_selection_box_union);
1384 fixSelectionBoxIntUnion();
1386 // Add this content to the list of all groups it belongs to
1387 for (const auto &group : def.groups) {
1388 const std::string &group_name = group.first;
1389 m_group_to_items[group_name].push_back(id);
1396 content_t NodeDefManager::allocateDummy(const std::string &name)
1398 assert(name != ""); // Pre-condition
1401 return set(name, f);
1405 void NodeDefManager::removeNode(const std::string &name)
1410 // Erase name from name ID mapping
1411 content_t id = CONTENT_IGNORE;
1412 if (m_name_id_mapping.getId(name, id)) {
1413 m_name_id_mapping.eraseName(name);
1414 m_name_id_mapping_with_aliases.erase(name);
1417 eraseIdFromGroups(id);
1421 void NodeDefManager::updateAliases(IItemDefManager *idef)
1423 std::set<std::string> all;
1425 m_name_id_mapping_with_aliases.clear();
1426 for (const std::string &name : all) {
1427 const std::string &convert_to = idef->getAlias(name);
1429 if (m_name_id_mapping.getId(convert_to, id)) {
1430 m_name_id_mapping_with_aliases.insert(
1431 std::make_pair(name, id));
1436 void NodeDefManager::applyTextureOverrides(const std::vector<TextureOverride> &overrides)
1438 infostream << "NodeDefManager::applyTextureOverrides(): Applying "
1439 "overrides to textures" << std::endl;
1441 for (const TextureOverride& texture_override : overrides) {
1443 if (!getId(texture_override.id, id))
1444 continue; // Ignore unknown node
1446 ContentFeatures &nodedef = m_content_features[id];
1449 if (texture_override.hasTarget(OverrideTarget::TOP))
1450 nodedef.tiledef[0].name = texture_override.texture;
1452 if (texture_override.hasTarget(OverrideTarget::BOTTOM))
1453 nodedef.tiledef[1].name = texture_override.texture;
1455 if (texture_override.hasTarget(OverrideTarget::RIGHT))
1456 nodedef.tiledef[2].name = texture_override.texture;
1458 if (texture_override.hasTarget(OverrideTarget::LEFT))
1459 nodedef.tiledef[3].name = texture_override.texture;
1461 if (texture_override.hasTarget(OverrideTarget::BACK))
1462 nodedef.tiledef[4].name = texture_override.texture;
1464 if (texture_override.hasTarget(OverrideTarget::FRONT))
1465 nodedef.tiledef[5].name = texture_override.texture;
1468 // Override special tiles, if applicable
1469 if (texture_override.hasTarget(OverrideTarget::SPECIAL_1))
1470 nodedef.tiledef_special[0].name = texture_override.texture;
1472 if (texture_override.hasTarget(OverrideTarget::SPECIAL_2))
1473 nodedef.tiledef_special[1].name = texture_override.texture;
1475 if (texture_override.hasTarget(OverrideTarget::SPECIAL_3))
1476 nodedef.tiledef_special[2].name = texture_override.texture;
1478 if (texture_override.hasTarget(OverrideTarget::SPECIAL_4))
1479 nodedef.tiledef_special[3].name = texture_override.texture;
1481 if (texture_override.hasTarget(OverrideTarget::SPECIAL_5))
1482 nodedef.tiledef_special[4].name = texture_override.texture;
1484 if (texture_override.hasTarget(OverrideTarget::SPECIAL_6))
1485 nodedef.tiledef_special[5].name = texture_override.texture;
1489 void NodeDefManager::updateTextures(IGameDef *gamedef, void *progress_callback_args)
1492 infostream << "NodeDefManager::updateTextures(): Updating "
1493 "textures in node definitions" << std::endl;
1495 Client *client = (Client *)gamedef;
1496 ITextureSource *tsrc = client->tsrc();
1497 IShaderSource *shdsrc = client->getShaderSource();
1498 auto smgr = client->getSceneManager();
1499 scene::IMeshManipulator *meshmanip = smgr->getMeshManipulator();
1500 TextureSettings tsettings;
1501 tsettings.readSettings();
1503 u32 size = m_content_features.size();
1505 for (u32 i = 0; i < size; i++) {
1506 ContentFeatures *f = &(m_content_features[i]);
1507 f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
1508 client->showUpdateProgressTexture(progress_callback_args, i, size);
1513 void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const
1515 writeU8(os, 1); // version
1517 std::ostringstream os2(std::ios::binary);
1518 for (u32 i = 0; i < m_content_features.size(); i++) {
1519 if (i == CONTENT_IGNORE || i == CONTENT_AIR
1520 || i == CONTENT_UNKNOWN)
1522 const ContentFeatures *f = &m_content_features[i];
1523 if (f->name.empty())
1526 // Wrap it in a string to allow different lengths without
1527 // strict version incompatibilities
1528 std::ostringstream wrapper_os(std::ios::binary);
1529 f->serialize(wrapper_os, protocol_version);
1530 os2<<serializeString16(wrapper_os.str());
1532 // must not overflow
1533 u16 next = count + 1;
1534 FATAL_ERROR_IF(next < count, "Overflow");
1537 writeU16(os, count);
1538 os << serializeString32(os2.str());
1542 void NodeDefManager::deSerialize(std::istream &is)
1545 int version = readU8(is);
1547 throw SerializationError("unsupported NodeDefinitionManager version");
1548 u16 count = readU16(is);
1549 std::istringstream is2(deSerializeString32(is), std::ios::binary);
1551 for (u16 n = 0; n < count; n++) {
1552 u16 i = readU16(is2);
1554 // Read it from the string wrapper
1555 std::string wrapper = deSerializeString16(is2);
1556 std::istringstream wrapper_is(wrapper, std::ios::binary);
1557 f.deSerialize(wrapper_is);
1559 // Check error conditions
1560 if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) {
1561 warningstream << "NodeDefManager::deSerialize(): "
1562 "not changing builtin node " << i << std::endl;
1565 if (f.name.empty()) {
1566 warningstream << "NodeDefManager::deSerialize(): "
1567 "received empty name" << std::endl;
1573 if (m_name_id_mapping.getId(f.name, existing_id) && i != existing_id) {
1574 warningstream << "NodeDefManager::deSerialize(): "
1575 "already defined with different ID: " << f.name << std::endl;
1579 // All is ok, add node definition with the requested ID
1580 if (i >= m_content_features.size())
1581 m_content_features.resize((u32)(i) + 1);
1582 m_content_features[i] = f;
1583 addNameIdMapping(i, f.name);
1584 TRACESTREAM(<< "NodeDef: deserialized " << f.name << std::endl);
1586 getNodeBoxUnion(f.selection_box, f, &m_selection_box_union);
1587 fixSelectionBoxIntUnion();
1590 // Since liquid_alternative_flowing_id and liquid_alternative_source_id
1591 // are not sent, resolve them client-side too.
1596 void NodeDefManager::addNameIdMapping(content_t i, const std::string &name)
1598 m_name_id_mapping.set(i, name);
1599 m_name_id_mapping_with_aliases.emplace(name, i);
1603 NodeDefManager *createNodeDefManager()
1605 return new NodeDefManager();
1609 void NodeDefManager::pendNodeResolve(NodeResolver *nr) const
1612 if (m_node_registration_complete)
1613 nr->nodeResolveInternal();
1615 m_pending_resolve_callbacks.push_back(nr);
1619 bool NodeDefManager::cancelNodeResolveCallback(NodeResolver *nr) const
1621 size_t len = m_pending_resolve_callbacks.size();
1622 for (size_t i = 0; i != len; i++) {
1623 if (nr != m_pending_resolve_callbacks[i])
1627 m_pending_resolve_callbacks[i] = m_pending_resolve_callbacks[len];
1628 m_pending_resolve_callbacks.resize(len);
1636 void NodeDefManager::runNodeResolveCallbacks()
1638 for (size_t i = 0; i != m_pending_resolve_callbacks.size(); i++) {
1639 NodeResolver *nr = m_pending_resolve_callbacks[i];
1640 nr->nodeResolveInternal();
1643 m_pending_resolve_callbacks.clear();
1647 void NodeDefManager::resetNodeResolveState()
1649 m_node_registration_complete = false;
1650 m_pending_resolve_callbacks.clear();
1653 static void removeDupes(std::vector<content_t> &list)
1655 std::sort(list.begin(), list.end());
1656 auto new_end = std::unique(list.begin(), list.end());
1657 list.erase(new_end, list.end());
1660 void NodeDefManager::resolveCrossrefs()
1662 for (ContentFeatures &f : m_content_features) {
1663 if (f.liquid_type != LIQUID_NONE || f.drawtype == NDT_LIQUID || f.drawtype == NDT_FLOWINGLIQUID) {
1664 f.liquid_alternative_flowing_id = getId(f.liquid_alternative_flowing);
1665 f.liquid_alternative_source_id = getId(f.liquid_alternative_source);
1668 if (f.drawtype != NDT_NODEBOX || f.node_box.type != NODEBOX_CONNECTED)
1671 for (const std::string &name : f.connects_to) {
1672 getIds(name, f.connects_to_ids);
1674 removeDupes(f.connects_to_ids);
1678 bool NodeDefManager::nodeboxConnects(MapNode from, MapNode to,
1679 u8 connect_face) const
1681 const ContentFeatures &f1 = get(from);
1683 if ((f1.drawtype != NDT_NODEBOX) || (f1.node_box.type != NODEBOX_CONNECTED))
1686 // lookup target in connected set
1687 if (!CONTAINS(f1.connects_to_ids, to.param0))
1690 const ContentFeatures &f2 = get(to);
1692 if ((f2.drawtype == NDT_NODEBOX) && (f2.node_box.type == NODEBOX_CONNECTED))
1693 // ignores actually looking if back connection exists
1694 return CONTAINS(f2.connects_to_ids, from.param0);
1696 // does to node declare usable faces?
1697 if (f2.connect_sides > 0) {
1698 if ((f2.param_type_2 == CPT2_FACEDIR ||
1699 f2.param_type_2 == CPT2_COLORED_FACEDIR)
1700 && (connect_face >= 4)) {
1701 static const u8 rot[33 * 4] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1702 0, 0, 0, 0, 4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1704 8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1706 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 4, 32, 0,
1707 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1709 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1710 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1711 0, 0, 0, 0, 0, 0, 32, 16, 8, 4 // 32 - left
1713 return (f2.connect_sides
1714 & rot[(connect_face * 4) + (to.param2 & 0x1F)]);
1716 return (f2.connect_sides & connect_face);
1718 // the target is just a regular node, so connect no matter back connection
1726 NodeResolver::NodeResolver()
1732 NodeResolver::~NodeResolver()
1734 if (!m_resolve_done && m_ndef)
1735 m_ndef->cancelNodeResolveCallback(this);
1739 void NodeResolver::cloneTo(NodeResolver *res) const
1741 FATAL_ERROR_IF(!m_resolve_done, "NodeResolver can only be cloned"
1742 " after resolving has completed");
1743 /* We don't actually do anything significant. Since the node resolving has
1744 * already completed, the class that called us will already have the
1745 * resolved IDs in its data structures (which it copies on its own) */
1746 res->m_ndef = m_ndef;
1747 res->m_resolve_done = true;
1751 void NodeResolver::nodeResolveInternal()
1753 m_nodenames_idx = 0;
1754 m_nnlistsizes_idx = 0;
1757 m_resolve_done = true;
1759 m_nodenames.clear();
1760 m_nnlistsizes.clear();
1764 bool NodeResolver::getIdFromNrBacklog(content_t *result_out,
1765 const std::string &node_alt, content_t c_fallback, bool error_on_fallback)
1767 if (m_nodenames_idx == m_nodenames.size()) {
1768 *result_out = c_fallback;
1769 errorstream << "NodeResolver: no more nodes in list" << std::endl;
1774 std::string name = m_nodenames[m_nodenames_idx++];
1776 bool success = m_ndef->getId(name, c);
1777 if (!success && !node_alt.empty()) {
1779 success = m_ndef->getId(name, c);
1783 if (error_on_fallback)
1784 errorstream << "NodeResolver: failed to resolve node name '" << name
1785 << "'." << std::endl;
1794 bool NodeResolver::getIdsFromNrBacklog(std::vector<content_t> *result_out,
1795 bool all_required, content_t c_fallback)
1797 bool success = true;
1799 if (m_nnlistsizes_idx == m_nnlistsizes.size()) {
1800 errorstream << "NodeResolver: no more node lists" << std::endl;
1804 size_t length = m_nnlistsizes[m_nnlistsizes_idx++];
1807 if (m_nodenames_idx == m_nodenames.size()) {
1808 errorstream << "NodeResolver: no more nodes in list" << std::endl;
1813 std::string &name = m_nodenames[m_nodenames_idx++];
1815 if (name.substr(0,6) != "group:") {
1816 if (m_ndef->getId(name, c)) {
1817 result_out->push_back(c);
1818 } else if (all_required) {
1819 errorstream << "NodeResolver: failed to resolve node name '"
1820 << name << "'." << std::endl;
1821 result_out->push_back(c_fallback);
1825 m_ndef->getIds(name, *result_out);
1832 void NodeResolver::reset(bool resolve_done)
1834 m_nodenames.clear();
1835 m_nodenames_idx = 0;
1836 m_nnlistsizes.clear();
1837 m_nnlistsizes_idx = 0;
1839 m_resolve_done = resolve_done;
1841 m_nodenames.reserve(16);
1842 m_nnlistsizes.reserve(4);