diff --git a/doc/lua_api.md b/doc/lua_api.md index 438769085..f6f867991 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -9529,6 +9529,16 @@ Player properties need to be saved manually. -- `false` will cause the background to be set automatically based on user settings. -- Default: false + nametag_fontsize = 1, + -- Sets the font size of the nametag in pixels. + -- `false` will cause the size to be set automatically based on user settings. + -- Default: false + + nametag_scale_z = false, + -- If enabled, the nametag will be scaled by Z in screen space, meaning it becomes + -- smaller the further away the object is. + -- Default: false + infotext = "", -- Same as infotext for nodes. Empty by default diff --git a/src/client/camera.cpp b/src/client/camera.cpp index dfa2649ed..376806373 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -631,55 +631,81 @@ void Camera::drawNametags() core::matrix4 trans = m_cameranode->getProjectionMatrix(); trans *= m_cameranode->getViewMatrix(); - gui::IGUIFont *font = g_fontengine->getFont(); + const u32 default_font_size = g_fontengine->getFontSize(FM_Unspecified); video::IVideoDriver *driver = RenderingEngine::get_video_driver(); v2u32 screensize = driver->getScreenSize(); + // Note: hidden nametags (e.g. GenericCAO) are removed from the array for (const Nametag *nametag : m_nametags) { - // Nametags are hidden in GenericCAO::updateNametag() - v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->pos * BS; f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f }; trans.multiplyWith1x4Matrix(transformed_pos); - if (transformed_pos[3] > 0) { - std::wstring nametag_colorless = - unescape_translate(utf8_to_wide(nametag->text)); - core::dimension2d textsize = font->getDimension( - nametag_colorless.c_str()); - f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : - core::reciprocal(transformed_pos[3]); - v2s32 screen_pos; - screen_pos.X = screensize.X * - (0.5 * transformed_pos[0] * zDiv + 0.5) - textsize.Width / 2; - screen_pos.Y = screensize.Y * - (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2; - core::rect size(0, 0, textsize.Width, textsize.Height); + if (transformed_pos[3] <= 0) // negative Z means behind camera + continue; + f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : (1.0f / transformed_pos[3]); - auto bgcolor = nametag->getBgColor(m_show_nametag_backgrounds); - if (bgcolor.getAlpha() != 0) { - core::rect bg_size(-2, 0, textsize.Width + 2, textsize.Height); - driver->draw2DRectangle(bgcolor, bg_size + screen_pos); - } - - font->draw( - translate_string(utf8_to_wide(nametag->text)).c_str(), - size + screen_pos, nametag->textcolor); + u32 font_size = 0; + if (nametag->scale_z) { + // Higher default since nametag should be reasonably visible + // even at distance. + u32 base_size = nametag->textsize.value_or(default_font_size * 4); + font_size = rangelim(base_size * BS * zDiv, 0, base_size); + } else { + font_size = nametag->textsize.value_or(default_font_size); } + if (font_size <= 1) + continue; + // TODO: This is quite primitive. It would be better to let the GPU handle + // scaling (draw to RTT first?). + { + // Because the current approach puts a high load on the font engine + // we quantize the font size and set an arbitrary maximum... + font_size = MYMIN(font_size, 256); + if (font_size > 128) + font_size &= ~(1|2|4); + else if (font_size > 64) + font_size &= ~(1|2); + else if (font_size > 32) + font_size &= ~1; + } + auto *font = g_fontengine->getFont(font_size); + assert(font); + + const auto wtext = utf8_to_wide(nametag->text); + // Measure dimensions with escapes removed + core::dimension2du textsize = font->getDimension(unescape_translate(wtext).c_str()); + v2s32 screen_pos; + screen_pos.X = screensize.X * + (0.5f + transformed_pos[0] * zDiv * 0.5f) - textsize.Width / 2; + screen_pos.Y = screensize.Y * + (0.5f - transformed_pos[1] * zDiv * 0.5f) - textsize.Height / 2; + core::rect size(0, 0, textsize.Width, textsize.Height); + + auto bgcolor = nametag->getBgColor(m_show_nametag_backgrounds); + if (bgcolor.getAlpha() != 0) { + core::rect bg_size(-2, 0, textsize.Width + 2, textsize.Height); + driver->draw2DRectangle(bgcolor, bg_size + screen_pos); + } + + // but draw text with escapes + font->draw(translate_string(wtext).c_str(), + size + screen_pos, nametag->textcolor); } } -Nametag *Camera::addNametag(scene::ISceneNode *parent_node, - const std::string &text, video::SColor textcolor, - std::optional bgcolor, const v3f &pos) +Nametag *Camera::addNametag(const Nametag ¶ms) { - Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos); + assert(params.parent_node); + auto *nametag = new Nametag(params); m_nametags.push_back(nametag); return nametag; } void Camera::removeNametag(Nametag *nametag) { - m_nametags.remove(nametag); + auto it = std::find(m_nametags.begin(), m_nametags.end(), nametag); + assert(it != m_nametags.end()); + m_nametags.erase(it); delete nametag; } diff --git a/src/client/camera.h b/src/client/camera.h index 49e07d4ee..c15cb373b 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -23,28 +23,17 @@ class WieldMeshSceneNode; struct Nametag { - scene::ISceneNode *parent_node; + scene::ISceneNode *parent_node = nullptr; std::string text; video::SColor textcolor; std::optional bgcolor; - v3f pos; - - Nametag(scene::ISceneNode *a_parent_node, - const std::string &text, - const video::SColor &textcolor, - const std::optional &bgcolor, - const v3f &pos): - parent_node(a_parent_node), - text(text), - textcolor(textcolor), - bgcolor(bgcolor), - pos(pos) - { - } + std::optional textsize; + v3f pos; // offset from parent node + bool scale_z; video::SColor getBgColor(bool use_fallback) const { - if (bgcolor) + if (bgcolor.has_value()) return bgcolor.value(); else if (!use_fallback) return video::SColor(0, 0, 0, 0); @@ -189,9 +178,7 @@ public: return m_camera_mode; } - Nametag *addNametag(scene::ISceneNode *parent_node, - const std::string &text, video::SColor textcolor, - std::optional bgcolor, const v3f &pos); + Nametag *addNametag(const Nametag ¶ms); void removeNametag(Nametag *nametag); diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index ecfe0f2de..f42cd4028 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -930,17 +930,15 @@ void GenericCAO::updateNametag() v3f pos; pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f; + // Add or update nametag + Nametag tmp{node, m_prop.nametag, m_prop.nametag_color, + m_prop.nametag_bgcolor, m_prop.nametag_fontsize, pos, + m_prop.nametag_scale_z}; if (!m_nametag) { - // Add nametag - m_nametag = m_client->getCamera()->addNametag(node, - m_prop.nametag, m_prop.nametag_color, - m_prop.nametag_bgcolor, pos); + m_nametag = m_client->getCamera()->addNametag(tmp); + assert(m_nametag); } else { - // Update nametag - m_nametag->text = m_prop.nametag; - m_nametag->textcolor = m_prop.nametag_color; - m_nametag->bgcolor = m_prop.nametag_bgcolor; - m_nametag->pos = pos; + *m_nametag = tmp; } } diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 35bf2e63e..e1538242e 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -34,6 +34,11 @@ ObjectProperties::ObjectProperties() std::string ObjectProperties::dump() const { + const auto &put_color = [] (std::ostream &os, video::SColor color) { + os << "\"" << color.getAlpha() << "," << color.getRed() << "," + << color.getGreen() << "," << color.getBlue() << "\" "; + }; + std::ostringstream os(std::ios::binary); os << "hp_max=" << hp_max; os << ", breath_max=" << breath_max; @@ -49,10 +54,8 @@ std::string ObjectProperties::dump() const } os << "]"; os << ", colors=["; - for (const video::SColor &color : colors) { - os << "\"" << color.getAlpha() << "," << color.getRed() << "," - << color.getGreen() << "," << color.getBlue() << "\" "; - } + for (const video::SColor &color : colors) + put_color(os, color); os << "]"; os << ", spritediv=" << spritediv; os << ", initial_sprite_basepos=" << initial_sprite_basepos; @@ -62,15 +65,18 @@ std::string ObjectProperties::dump() const os << ", backface_culling="<< backface_culling; os << ", glow=" << glow; os << ", nametag=" << nametag; - os << ", nametag_color=" << "\"" << nametag_color.getAlpha() << "," << nametag_color.getRed() - << "," << nametag_color.getGreen() << "," << nametag_color.getBlue() << "\" "; - + os << ", nametag_color="; + put_color(os, nametag_color); + os << ", nametag_bgcolor="; if (nametag_bgcolor) - os << ", nametag_bgcolor=" << "\"" << nametag_color.getAlpha() << "," << nametag_color.getRed() - << "," << nametag_color.getGreen() << "," << nametag_color.getBlue() << "\" "; + put_color(os, nametag_bgcolor.value()); else - os << ", nametag_bgcolor=null "; - + os << "=null "; + os << ", nametag_fontsize="; + if (nametag_fontsize) + os << "=" << nametag_fontsize.value() << " "; + else + os << "=null "; os << ", selectionbox=" << selectionbox.MinEdge << "," << selectionbox.MaxEdge; os << ", rotate_selectionbox=" << rotate_selectionbox; os << ", pointable=" << Pointabilities::toStringPointabilityType(pointable); @@ -83,22 +89,24 @@ std::string ObjectProperties::dump() const os << ", damage_texture_modifier=" << damage_texture_modifier; os << ", shaded=" << shaded; os << ", show_on_minimap=" << show_on_minimap; + os << ", nametag_scale_z=" << nametag_scale_z; return os.str(); } -static auto tie(const ObjectProperties &o) +static inline auto tie(const ObjectProperties &o) { // Make sure to add new members to this list! return std::tie( o.textures, o.colors, o.collisionbox, o.selectionbox, o.visual, o.mesh, o.damage_texture_modifier, o.nametag, o.infotext, o.wield_item, o.visual_size, - o.nametag_color, o.nametag_bgcolor, o.spritediv, o.initial_sprite_basepos, + o.nametag_color, o.nametag_bgcolor, o.nametag_fontsize, o.spritediv, + o.initial_sprite_basepos, o.stepheight, o.automatic_rotate, o.automatic_face_movement_dir_offset, o.automatic_face_movement_max_rotation_per_sec, o.eye_height, o.zoom_fov, o.node, o.hp_max, o.breath_max, o.glow, o.pointable, o.physical, o.collideWithObjects, o.rotate_selectionbox, o.is_visible, o.makes_footstep_sound, o.automatic_face_movement_dir, o.backface_culling, o.static_save, o.use_texture_alpha, - o.shaded, o.show_on_minimap + o.shaded, o.show_on_minimap, o.nametag_scale_z ); } @@ -202,6 +210,13 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, node.getParam1()); writeU8(os, node.getParam2()); + if (!nametag_fontsize) + writeU32(os, U32_MAX); // nil placeholder + else + writeU32(os, nametag_fontsize.value()); + + writeU8(os, nametag_scale_z); + // Add stuff only at the bottom. // Never remove anything, because we don't want new versions of this! } @@ -306,5 +321,14 @@ void ObjectProperties::deSerialize(std::istream &is) node.param1 = readU8(is); node.param2 = readU8(is); + u32 fontsize; + if (!tryRead(fontsize, is)) + return; + if (fontsize != U32_MAX) + nametag_fontsize = fontsize; + else + nametag_fontsize = std::nullopt; + nametag_scale_z = readU8(is); + // Add new properties down here and remember to use either tryRead<> or a try-catch. } diff --git a/src/object_properties.h b/src/object_properties.h index b898c982d..8cfa8cec7 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -56,6 +56,7 @@ struct ObjectProperties f32 automatic_face_movement_max_rotation_per_sec = -1.0f; float eye_height = 1.625f; float zoom_fov = 0.0f; + std::optional nametag_fontsize; MapNode node = MapNode(CONTENT_IGNORE); u16 hp_max = 1; u16 breath_max = 0; @@ -73,6 +74,7 @@ struct ObjectProperties bool use_texture_alpha = false; bool shaded = true; bool show_on_minimap = false; + bool nametag_scale_z = false; ObjectProperties(); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 91aa5b405..078a17a1d 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -267,7 +267,7 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) } /******************************************************************************/ -const std::array object_property_keys = { +const std::array object_property_keys = { "hp_max", "breath_max", "physical", @@ -302,6 +302,8 @@ const std::array object_property_keys = { "damage_texture_modifier", "show_on_minimap", // "node" is intentionally not here as it's gated behind `fallback` below! + "nametag_fontsize", + "nametag_scale_z", }; /******************************************************************************/ @@ -467,7 +469,7 @@ void read_object_properties(lua_State *L, int index, lua_pop(L, 1); lua_getfield(L, -1, "nametag_bgcolor"); if (!lua_isnil(L, -1)) { - if (lua_toboolean(L, -1)) { + if (lua_toboolean(L, -1)) { // truthy video::SColor color; if (read_color(L, -1, &color)) prop->nametag_bgcolor = color; @@ -476,6 +478,16 @@ void read_object_properties(lua_State *L, int index, } } lua_pop(L, 1); + lua_getfield(L, -1, "nametag_fontsize"); + if (!lua_isnil(L, -1)) { + if (lua_toboolean(L, -1)) { // truthy + prop->nametag_fontsize = lua_tointeger(L, -1); + } else { + prop->nametag_fontsize = std::nullopt; + } + } + lua_pop(L, 1); + getboolfield(L, -1, "nametag_scale_z", prop->nametag_scale_z); getstringfield(L, -1, "infotext", prop->infotext); getboolfield(L, -1, "static_save", prop->static_save); @@ -577,6 +589,15 @@ void push_object_properties(lua_State *L, const ObjectProperties *prop) lua_pushboolean(L, false); lua_setfield(L, -2, "nametag_bgcolor"); } + if (prop->nametag_fontsize) { + lua_pushinteger(L, prop->nametag_fontsize.value()); + lua_setfield(L, -2, "nametag_fontsize"); + } else { + lua_pushboolean(L, false); + lua_setfield(L, -2, "nametag_fontsize"); + } + lua_pushboolean(L, prop->nametag_scale_z); + lua_setfield(L, -2, "nametag_scale_z"); lua_pushlstring(L, prop->infotext.c_str(), prop->infotext.size()); lua_setfield(L, -2, "infotext"); lua_pushboolean(L, prop->static_save); diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index f57c15797..99c243705 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -60,8 +60,7 @@ extern struct EnumString es_TileAnimationType[]; extern struct EnumString es_ItemType[]; extern struct EnumString es_TouchInteractionMode[]; - -extern const std::array object_property_keys; +extern const std::array object_property_keys; void read_content_features(lua_State *L, ContentFeatures &f, int index); void push_content_features(lua_State *L, const ContentFeatures &c);