+ ContentFeatures f;
+ f.name = "air";
+ f.drawtype = NDT_AIRLIKE;
+ f.param_type = CPT_LIGHT;
+ f.light_propagates = true;
+ f.sunlight_propagates = true;
+ f.walkable = false;
+ f.pointable = false;
+ f.diggable = false;
+ f.buildable_to = true;
+ f.floodable = true;
+ f.is_ground_content = true;
+ // Insert directly into containers
+ content_t c = CONTENT_AIR;
+ m_content_features[c] = f;
+ addNameIdMapping(c, f.name);
+ }
+
+ // Set CONTENT_IGNORE
+ {
+ ContentFeatures f;
+ f.name = "ignore";
+ f.drawtype = NDT_AIRLIKE;
+ f.param_type = CPT_NONE;
+ f.light_propagates = false;
+ f.sunlight_propagates = false;
+ f.walkable = false;
+ f.pointable = false;
+ f.diggable = false;
+ f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs
+ f.is_ground_content = true;
+ // Insert directly into containers
+ content_t c = CONTENT_IGNORE;
+ m_content_features[c] = f;
+ addNameIdMapping(c, f.name);
+ }
+}
+
+
+IWritableNodeDefManager *CNodeDefManager::clone()
+{
+ CNodeDefManager *mgr = new CNodeDefManager();
+ *mgr = *this;
+ return mgr;
+}
+
+
+inline const ContentFeatures& CNodeDefManager::get(content_t c) const
+{
+ return c < m_content_features.size()
+ ? m_content_features[c] : m_content_features[CONTENT_UNKNOWN];
+}
+
+
+inline const ContentFeatures& CNodeDefManager::get(const MapNode &n) const
+{
+ return get(n.getContent());
+}
+
+
+bool CNodeDefManager::getId(const std::string &name, content_t &result) const
+{
+ UNORDERED_MAP<std::string, content_t>::const_iterator
+ i = m_name_id_mapping_with_aliases.find(name);
+ if(i == m_name_id_mapping_with_aliases.end())
+ return false;
+ result = i->second;
+ return true;
+}
+
+
+content_t CNodeDefManager::getId(const std::string &name) const
+{
+ content_t id = CONTENT_IGNORE;
+ getId(name, id);
+ return id;
+}
+
+
+bool CNodeDefManager::getIds(const std::string &name,
+ std::set<content_t> &result) const
+{
+ //TimeTaker t("getIds", NULL, PRECISION_MICRO);
+ if (name.substr(0,6) != "group:") {
+ content_t id = CONTENT_IGNORE;
+ bool exists = getId(name, id);
+ if (exists)
+ result.insert(id);
+ return exists;
+ }
+ std::string group = name.substr(6);
+
+ UNORDERED_MAP<std::string, GroupItems>::const_iterator
+ i = m_group_to_items.find(group);
+ if (i == m_group_to_items.end())
+ return true;
+
+ const GroupItems &items = i->second;
+ for (GroupItems::const_iterator j = items.begin();
+ j != items.end(); ++j) {
+ if ((*j).second != 0)
+ result.insert((*j).first);
+ }
+ //printf("getIds: %dus\n", t.stop());
+ return true;
+}
+
+
+const ContentFeatures& CNodeDefManager::get(const std::string &name) const
+{
+ content_t id = CONTENT_UNKNOWN;
+ getId(name, id);
+ return get(id);
+}
+
+
+// returns CONTENT_IGNORE if no free ID found
+content_t CNodeDefManager::allocateId()
+{
+ for (content_t id = m_next_id;
+ id >= m_next_id; // overflow?
+ ++id) {
+ while (id >= m_content_features.size()) {
+ m_content_features.push_back(ContentFeatures());
+ }
+ const ContentFeatures &f = m_content_features[id];
+ if (f.name == "") {
+ m_next_id = id + 1;
+ return id;
+ }
+ }
+ // If we arrive here, an overflow occurred in id.
+ // That means no ID was found
+ return CONTENT_IGNORE;
+}
+
+
+/*!
+ * Returns the smallest box that contains all boxes
+ * in the vector. Box_union is expanded.
+ * @param[in] boxes the vector containing the boxes
+ * @param[in, out] box_union the union of the arguments
+ */
+void boxVectorUnion(const std::vector<aabb3f> &boxes, aabb3f *box_union)
+{
+ for (std::vector<aabb3f>::const_iterator it = boxes.begin();
+ it != boxes.end(); ++it) {
+ box_union->addInternalBox(*it);
+ }
+}
+
+
+/*!
+ * Returns a box that contains the nodebox in every case.
+ * The argument node_union is expanded.
+ * @param[in] nodebox the nodebox to be measured
+ * @param[in] features used to decide whether the nodebox
+ * can be rotated
+ * @param[in, out] box_union the union of the arguments
+ */
+void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features,
+ aabb3f *box_union)
+{
+ switch(nodebox.type) {
+ case NODEBOX_FIXED:
+ case NODEBOX_LEVELED: {
+ // Raw union
+ aabb3f half_processed(0, 0, 0, 0, 0, 0);
+ boxVectorUnion(nodebox.fixed, &half_processed);
+ // Set leveled boxes to maximal
+ if (nodebox.type == NODEBOX_LEVELED) {
+ half_processed.MaxEdge.Y = +BS / 2;
+ }
+ if (features.param_type_2 == CPT2_FACEDIR ||
+ features.param_type_2 == CPT2_COLORED_FACEDIR) {
+ // Get maximal coordinate
+ f32 coords[] = {
+ fabsf(half_processed.MinEdge.X),
+ fabsf(half_processed.MinEdge.Y),
+ fabsf(half_processed.MinEdge.Z),
+ fabsf(half_processed.MaxEdge.X),
+ fabsf(half_processed.MaxEdge.Y),
+ fabsf(half_processed.MaxEdge.Z) };
+ f32 max = 0;
+ for (int i = 0; i < 6; i++) {
+ if (max < coords[i]) {
+ max = coords[i];
+ }
+ }
+ // Add the union of all possible rotated boxes
+ box_union->addInternalPoint(-max, -max, -max);
+ box_union->addInternalPoint(+max, +max, +max);
+ } else {
+ box_union->addInternalBox(half_processed);
+ }
+ break;
+ }
+ case NODEBOX_WALLMOUNTED: {
+ // Add fix boxes
+ box_union->addInternalBox(nodebox.wall_top);
+ box_union->addInternalBox(nodebox.wall_bottom);
+ // Find maximal coordinate in the X-Z plane
+ f32 coords[] = {
+ fabsf(nodebox.wall_side.MinEdge.X),
+ fabsf(nodebox.wall_side.MinEdge.Z),
+ fabsf(nodebox.wall_side.MaxEdge.X),
+ fabsf(nodebox.wall_side.MaxEdge.Z) };
+ f32 max = 0;
+ for (int i = 0; i < 4; i++) {
+ if (max < coords[i]) {
+ max = coords[i];
+ }
+ }
+ // Add the union of all possible rotated boxes
+ box_union->addInternalPoint(-max, nodebox.wall_side.MinEdge.Y, -max);
+ box_union->addInternalPoint(max, nodebox.wall_side.MaxEdge.Y, max);
+ break;
+ }
+ case NODEBOX_CONNECTED: {
+ // Add all possible connected boxes
+ boxVectorUnion(nodebox.fixed, box_union);
+ boxVectorUnion(nodebox.connect_top, box_union);
+ boxVectorUnion(nodebox.connect_bottom, box_union);
+ boxVectorUnion(nodebox.connect_front, box_union);
+ boxVectorUnion(nodebox.connect_left, box_union);
+ boxVectorUnion(nodebox.connect_back, box_union);
+ boxVectorUnion(nodebox.connect_right, box_union);
+ break;
+ }
+ default: {
+ // NODEBOX_REGULAR
+ box_union->addInternalPoint(-BS / 2, -BS / 2, -BS / 2);
+ box_union->addInternalPoint(+BS / 2, +BS / 2, +BS / 2);
+ }
+ }
+}
+
+
+inline void CNodeDefManager::fixSelectionBoxIntUnion()
+{
+ m_selection_box_int_union.MinEdge.X = floorf(
+ m_selection_box_union.MinEdge.X / BS + 0.5f);
+ m_selection_box_int_union.MinEdge.Y = floorf(
+ m_selection_box_union.MinEdge.Y / BS + 0.5f);
+ m_selection_box_int_union.MinEdge.Z = floorf(
+ m_selection_box_union.MinEdge.Z / BS + 0.5f);
+ m_selection_box_int_union.MaxEdge.X = ceilf(
+ m_selection_box_union.MaxEdge.X / BS - 0.5f);
+ m_selection_box_int_union.MaxEdge.Y = ceilf(
+ m_selection_box_union.MaxEdge.Y / BS - 0.5f);
+ m_selection_box_int_union.MaxEdge.Z = ceilf(
+ m_selection_box_union.MaxEdge.Z / BS - 0.5f);
+}
+
+
+// IWritableNodeDefManager
+content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &def)
+{
+ // Pre-conditions
+ assert(name != "");
+ assert(name == def.name);
+
+ // Don't allow redefining ignore (but allow air and unknown)
+ if (name == "ignore") {
+ warningstream << "NodeDefManager: Ignoring "
+ "CONTENT_IGNORE redefinition"<<std::endl;
+ return CONTENT_IGNORE;
+ }
+
+ content_t id = CONTENT_IGNORE;
+ if (!m_name_id_mapping.getId(name, id)) { // ignore aliases
+ // Get new id
+ id = allocateId();
+ if (id == CONTENT_IGNORE) {
+ warningstream << "NodeDefManager: Absolute "
+ "limit reached" << std::endl;
+ return CONTENT_IGNORE;
+ }
+ assert(id != CONTENT_IGNORE);
+ addNameIdMapping(id, name);
+ }
+ m_content_features[id] = def;
+ verbosestream << "NodeDefManager: registering content id \"" << id
+ << "\": name=\"" << def.name << "\""<<std::endl;
+
+ getNodeBoxUnion(def.selection_box, def, &m_selection_box_union);
+ fixSelectionBoxIntUnion();
+ // Add this content to the list of all groups it belongs to
+ // FIXME: This should remove a node from groups it no longer
+ // belongs to when a node is re-registered
+ for (ItemGroupList::const_iterator i = def.groups.begin();
+ i != def.groups.end(); ++i) {
+ std::string group_name = i->first;
+
+ UNORDERED_MAP<std::string, GroupItems>::iterator
+ j = m_group_to_items.find(group_name);
+ if (j == m_group_to_items.end()) {
+ m_group_to_items[group_name].push_back(
+ std::make_pair(id, i->second));
+ } else {
+ GroupItems &items = j->second;
+ items.push_back(std::make_pair(id, i->second));
+ }
+ }
+ return id;
+}
+
+
+content_t CNodeDefManager::allocateDummy(const std::string &name)
+{
+ assert(name != ""); // Pre-condition
+ ContentFeatures f;
+ f.name = name;
+ return set(name, f);
+}
+
+
+void CNodeDefManager::removeNode(const std::string &name)
+{
+ // Pre-condition
+ assert(name != "");
+
+ // Erase name from name ID mapping
+ content_t id = CONTENT_IGNORE;
+ if (m_name_id_mapping.getId(name, id)) {
+ m_name_id_mapping.eraseName(name);
+ m_name_id_mapping_with_aliases.erase(name);
+ }
+
+ // Erase node content from all groups it belongs to
+ for (UNORDERED_MAP<std::string, GroupItems>::iterator iter_groups =
+ m_group_to_items.begin(); iter_groups != m_group_to_items.end();) {
+ GroupItems &items = iter_groups->second;
+ for (GroupItems::iterator iter_groupitems = items.begin();
+ iter_groupitems != items.end();) {
+ if (iter_groupitems->first == id)
+ items.erase(iter_groupitems++);
+ else
+ ++iter_groupitems;
+ }
+
+ // Check if group is empty
+ if (items.size() == 0)
+ m_group_to_items.erase(iter_groups++);
+ else
+ ++iter_groups;
+ }
+}
+
+
+void CNodeDefManager::updateAliases(IItemDefManager *idef)
+{
+ std::set<std::string> all;
+ idef->getAll(all);
+ m_name_id_mapping_with_aliases.clear();
+ for (std::set<std::string>::const_iterator
+ i = all.begin(); i != all.end(); ++i) {
+ const std::string &name = *i;
+ const std::string &convert_to = idef->getAlias(name);
+ content_t id;
+ if (m_name_id_mapping.getId(convert_to, id)) {
+ m_name_id_mapping_with_aliases.insert(
+ std::make_pair(name, id));
+ }
+ }
+}
+
+void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath)
+{
+ infostream << "CNodeDefManager::applyTextureOverrides(): Applying "
+ "overrides to textures from " << override_filepath << std::endl;
+
+ std::ifstream infile(override_filepath.c_str());
+ std::string line;
+ int line_c = 0;
+ while (std::getline(infile, line)) {
+ line_c++;
+ if (trim(line) == "")
+ continue;
+ std::vector<std::string> splitted = str_split(line, ' ');
+ if (splitted.size() != 3) {
+ errorstream << override_filepath
+ << ":" << line_c << " Could not apply texture override \""
+ << line << "\": Syntax error" << std::endl;
+ continue;
+ }
+
+ content_t id;
+ if (!getId(splitted[0], id))
+ continue; // Ignore unknown node
+
+ ContentFeatures &nodedef = m_content_features[id];
+
+ if (splitted[1] == "top")
+ nodedef.tiledef[0].name = splitted[2];
+ else if (splitted[1] == "bottom")
+ nodedef.tiledef[1].name = splitted[2];
+ else if (splitted[1] == "right")
+ nodedef.tiledef[2].name = splitted[2];
+ else if (splitted[1] == "left")
+ nodedef.tiledef[3].name = splitted[2];
+ else if (splitted[1] == "back")
+ nodedef.tiledef[4].name = splitted[2];
+ else if (splitted[1] == "front")
+ nodedef.tiledef[5].name = splitted[2];
+ else if (splitted[1] == "all" || splitted[1] == "*")
+ for (int i = 0; i < 6; i++)
+ nodedef.tiledef[i].name = splitted[2];
+ else if (splitted[1] == "sides")
+ for (int i = 2; i < 6; i++)
+ nodedef.tiledef[i].name = splitted[2];
+ else {
+ errorstream << override_filepath
+ << ":" << line_c << " Could not apply texture override \""
+ << line << "\": Unknown node side \""
+ << splitted[1] << "\"" << std::endl;
+ continue;
+ }
+ }
+}
+
+void CNodeDefManager::updateTextures(IGameDef *gamedef,
+ void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress),
+ void *progress_callback_args)
+{
+#ifndef SERVER
+ infostream << "CNodeDefManager::updateTextures(): Updating "
+ "textures in node definitions" << std::endl;
+
+ Client *client = (Client *)gamedef;
+ ITextureSource *tsrc = client->tsrc();
+ IShaderSource *shdsrc = client->getShaderSource();
+ scene::ISceneManager* smgr = client->getSceneManager();
+ scene::IMeshManipulator* meshmanip = smgr->getMeshManipulator();
+ TextureSettings tsettings;
+ tsettings.readSettings();
+
+ u32 size = m_content_features.size();
+
+ for (u32 i = 0; i < size; i++) {
+ ContentFeatures *f = &(m_content_features[i]);
+ f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
+ progress_callback(progress_callback_args, i, size);
+ }
+#endif
+}
+
+void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) const
+{
+ writeU8(os, 1); // version
+ u16 count = 0;
+ std::ostringstream os2(std::ios::binary);
+ for (u32 i = 0; i < m_content_features.size(); i++) {
+ if (i == CONTENT_IGNORE || i == CONTENT_AIR
+ || i == CONTENT_UNKNOWN)
+ continue;
+ const ContentFeatures *f = &m_content_features[i];
+ if (f->name == "")
+ continue;
+ writeU16(os2, i);
+ // Wrap it in a string to allow different lengths without
+ // strict version incompatibilities
+ std::ostringstream wrapper_os(std::ios::binary);
+ f->serialize(wrapper_os, protocol_version);
+ os2<<serializeString(wrapper_os.str());
+
+ // must not overflow
+ u16 next = count + 1;
+ FATAL_ERROR_IF(next < count, "Overflow");
+ count++;
+ }
+ writeU16(os, count);
+ os << serializeLongString(os2.str());
+}
+
+
+void CNodeDefManager::deSerialize(std::istream &is)
+{
+ clear();
+ int version = readU8(is);
+ if (version != 1)
+ throw SerializationError("unsupported NodeDefinitionManager version");
+ u16 count = readU16(is);
+ std::istringstream is2(deSerializeLongString(is), std::ios::binary);
+ ContentFeatures f;
+ for (u16 n = 0; n < count; n++) {
+ u16 i = readU16(is2);
+
+ // Read it from the string wrapper
+ std::string wrapper = deSerializeString(is2);
+ std::istringstream wrapper_is(wrapper, std::ios::binary);
+ f.deSerialize(wrapper_is);
+
+ // Check error conditions
+ if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) {
+ warningstream << "NodeDefManager::deSerialize(): "
+ "not changing builtin node " << i << std::endl;
+ continue;
+ }
+ if (f.name == "") {
+ warningstream << "NodeDefManager::deSerialize(): "
+ "received empty name" << std::endl;
+ continue;
+ }
+
+ // Ignore aliases
+ u16 existing_id;
+ if (m_name_id_mapping.getId(f.name, existing_id) && i != existing_id) {
+ warningstream << "NodeDefManager::deSerialize(): "
+ "already defined with different ID: " << f.name << std::endl;
+ continue;
+ }
+
+ // All is ok, add node definition with the requested ID
+ if (i >= m_content_features.size())
+ m_content_features.resize((u32)(i) + 1);
+ m_content_features[i] = f;
+ addNameIdMapping(i, f.name);
+ verbosestream << "deserialized " << f.name << std::endl;
+
+ getNodeBoxUnion(f.selection_box, f, &m_selection_box_union);
+ fixSelectionBoxIntUnion();
+ }
+}
+
+
+void CNodeDefManager::addNameIdMapping(content_t i, std::string name)
+{
+ m_name_id_mapping.set(i, name);
+ m_name_id_mapping_with_aliases.insert(std::make_pair(name, i));
+}
+
+
+IWritableNodeDefManager *createNodeDefManager()
+{
+ return new CNodeDefManager();
+}
+
+
+//// Serialization of old ContentFeatures formats
+void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
+{
+ u8 compatible_param_type_2 = param_type_2;
+ if ((protocol_version < 28)
+ && (compatible_param_type_2 == CPT2_MESHOPTIONS))
+ compatible_param_type_2 = CPT2_NONE;
+ else if (protocol_version < 30) {
+ if (compatible_param_type_2 == CPT2_COLOR)
+ compatible_param_type_2 = CPT2_NONE;
+ else if (compatible_param_type_2 == CPT2_COLORED_FACEDIR)
+ compatible_param_type_2 = CPT2_FACEDIR;
+ else if (compatible_param_type_2 == CPT2_COLORED_WALLMOUNTED)
+ compatible_param_type_2 = CPT2_WALLMOUNTED;
+ }
+
+ float compatible_visual_scale = visual_scale;
+ if (protocol_version < 30 && drawtype == NDT_PLANTLIKE)
+ compatible_visual_scale = sqrt(visual_scale);
+
+ TileDef compatible_tiles[6];
+ for (u8 i = 0; i < 6; i++) {
+ compatible_tiles[i] = tiledef[i];
+ if (tiledef_overlay[i].name != "") {
+ std::stringstream s;
+ s << "(" << tiledef[i].name << ")^(" << tiledef_overlay[i].name
+ << ")";
+ compatible_tiles[i].name = s.str();
+ }
+ }
+
+ // Protocol >= 24
+ if (protocol_version < 31) {
+ writeU8(os, protocol_version < 27 ? 7 : 8);
+
+ os << serializeString(name);
+ writeU16(os, groups.size());
+ for (ItemGroupList::const_iterator i = groups.begin();
+ i != groups.end(); ++i) {
+ os << serializeString(i->first);
+ writeS16(os, i->second);
+ }
+ writeU8(os, drawtype);
+ writeF1000(os, compatible_visual_scale);
+ writeU8(os, 6);
+ for (u32 i = 0; i < 6; i++)
+ compatible_tiles[i].serialize(os, protocol_version);
+ writeU8(os, CF_SPECIAL_COUNT);
+ for (u32 i = 0; i < CF_SPECIAL_COUNT; i++)
+ tiledef_special[i].serialize(os, protocol_version);
+ writeU8(os, alpha);
+ writeU8(os, post_effect_color.getAlpha());
+ writeU8(os, post_effect_color.getRed());
+ writeU8(os, post_effect_color.getGreen());
+ writeU8(os, post_effect_color.getBlue());
+ writeU8(os, param_type);
+ writeU8(os, compatible_param_type_2);
+ writeU8(os, is_ground_content);
+ writeU8(os, light_propagates);
+ writeU8(os, sunlight_propagates);
+ writeU8(os, walkable);
+ writeU8(os, pointable);
+ writeU8(os, diggable);
+ writeU8(os, climbable);
+ writeU8(os, buildable_to);
+ os << serializeString(""); // legacy: used to be metadata_name
+ writeU8(os, liquid_type);
+ os << serializeString(liquid_alternative_flowing);
+ os << serializeString(liquid_alternative_source);
+ writeU8(os, liquid_viscosity);
+ writeU8(os, liquid_renewable);
+ writeU8(os, light_source);
+ writeU32(os, damage_per_second);
+ node_box.serialize(os, protocol_version);
+ selection_box.serialize(os, protocol_version);
+ writeU8(os, legacy_facedir_simple);
+ writeU8(os, legacy_wallmounted);
+ serializeSimpleSoundSpec(sound_footstep, os);
+ serializeSimpleSoundSpec(sound_dig, os);
+ serializeSimpleSoundSpec(sound_dug, os);
+ writeU8(os, rightclickable);
+ writeU8(os, drowning);
+ writeU8(os, leveled);
+ writeU8(os, liquid_range);
+ writeU8(os, waving);
+ os << serializeString(mesh);
+ collision_box.serialize(os, protocol_version);
+ writeU8(os, floodable);
+ writeU16(os, connects_to_ids.size());
+ for (std::set<content_t>::const_iterator i = connects_to_ids.begin();
+ i != connects_to_ids.end(); ++i)
+ writeU16(os, *i);
+ writeU8(os, connect_sides);
+ } else {
+ throw SerializationError("ContentFeatures::serialize(): "
+ "Unsupported version requested");