From 93ccb4b355d45bbdbbcbadcb0d9c1a1dffef5325 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sun, 26 Oct 2025 18:48:53 +0100 Subject: [PATCH] Add inventory image animation API (#16538) --- builtin/game/features.lua | 1 + builtin/game/misc_s.lua | 1 + doc/lua_api.md | 33 ++-- games/devtest/mods/testitems/init.lua | 56 ++++++ .../textures/testitems_animation_overlay.png | Bin 0 -> 115 bytes src/client/item_visuals_manager.cpp | 138 ++++++++++++--- src/client/item_visuals_manager.h | 42 ++--- src/client/tile.cpp | 25 ++- src/client/tile.h | 24 ++- src/client/wieldmesh.cpp | 164 ++++++++++++------ src/client/wieldmesh.h | 38 ++-- src/gui/drawItemStack.cpp | 30 ++-- src/inventory.cpp | 44 ++--- src/inventory.h | 8 +- src/itemdef.cpp | 45 +++-- src/itemdef.h | 31 +++- src/network/networkprotocol.cpp | 5 +- src/nodedef.cpp | 36 ++-- src/script/common/c_content.cpp | 64 +++++-- src/script/common/c_content.h | 3 + src/tileanimation.cpp | 30 ++++ src/tileanimation.h | 5 + src/util/string.h | 10 ++ 23 files changed, 606 insertions(+), 227 deletions(-) create mode 100644 games/devtest/mods/testitems/textures/testitems_animation_overlay.png diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 747b3aee7..99cb1681e 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -52,6 +52,7 @@ core.features = { particlespawner_exclude_player = true, generate_decorations_biomes = true, chunksize_vector = true, + item_inventory_image_animation = true, } function core.has_feature(arg) diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua index 739add904..360321884 100644 --- a/builtin/game/misc_s.lua +++ b/builtin/game/misc_s.lua @@ -132,6 +132,7 @@ core.protocol_versions = { ["5.12.0"] = 48, ["5.13.0"] = 49, ["5.14.0"] = 50, + ["5.15.0"] = 51, } setmetatable(core.protocol_versions, {__newindex = function() diff --git a/doc/lua_api.md b/doc/lua_api.md index 4265bb4c3..29c1a88bd 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2708,10 +2708,10 @@ Some of the values in the key-value store are handled specially: See also: `get_description` in [`ItemStack`](#itemstack) * `short_description`: Set the item stack's short description. See also: `get_short_description` in [`ItemStack`](#itemstack) -* `inventory_image`: Override inventory_image -* `inventory_overlay`: Override inventory_overlay -* `wield_image`: Override wield_image -* `wield_overlay`: Override wield_overlay +* `inventory_image`: Override inventory_image.name +* `inventory_overlay`: Override inventory_overlay.name +* `wield_image`: Override wield_image.name +* `wield_overlay`: Override wield_overlay.name * `wield_scale`: Override wield_scale, use vector.to_string * `color`: A `ColorString`, which sets the stack's color. * `palette_index`: If the item has a palette, this is used to get the @@ -5854,6 +5854,9 @@ Utilities generate_decorations_biomes = true, -- 'chunksize' mapgen setting can be a vector, instead of a single number (5.15.0) chunksize_vector = true, + -- Item definition fields `inventory_image`, `inventory_overlay`, `wield_image` + -- and `wield_overlay` accept a table containing animation definitions. (5.16.0) + item_image_animation = true, } ``` @@ -9518,6 +9521,7 @@ Player properties need to be saved manually. -- `core.itemstring_with_palette()`), the entity will inherit the color. -- Wielditems are scaled a bit. If you want a wielditem to appear -- to be as large as a node, use `0.667` in `visual_size` + -- Currently, item image animations are not played. This may change in the future. -- "item" is similar to "wielditem" but ignores the 'wield_image' parameter. -- "node" looks exactly like a node in-world (supported since 5.12.0) -- Note that visual effects like waving or liquid reflections will not work. @@ -9858,6 +9862,13 @@ Tile animation definition } ``` +Item image definition +--------------------- + +* `"image.png"` +* `{name="image.png", animation={Tile Animation definition}}` + * Basically a tile definition but for items + Item definition --------------- @@ -9884,18 +9895,18 @@ Used by `core.register_node`, `core.register_craftitem`, and -- {bendy = 2, snappy = 1}, -- {hard = 1, metal = 1, spikes = 1} - inventory_image = "", - -- Texture shown in the inventory GUI + inventory_image = , + -- Image shown in the inventory GUI -- Defaults to a 3D rendering of the node if left empty. - inventory_overlay = "", - -- An overlay texture which is not affected by colorization + inventory_overlay = , + -- An overlay image which is not affected by colorization - wield_image = "", - -- Texture shown when item is held in hand + wield_image = , + -- Image shown when item is held in hand -- Defaults to a 3D rendering of the node if left empty. - wield_overlay = "", + wield_overlay = , -- Like inventory_overlay but only used in the same situation as wield_image wield_scale = {x = 1, y = 1, z = 1}, diff --git a/games/devtest/mods/testitems/init.lua b/games/devtest/mods/testitems/init.lua index a5535c346..2450f28f2 100644 --- a/games/devtest/mods/testitems/init.lua +++ b/games/devtest/mods/testitems/init.lua @@ -177,3 +177,59 @@ core.register_craftitem("testitems:tree_spawner_vmanip", { core.register_on_leaveplayer(function(player, timed_out) vmanip_for_trees[player:get_player_name()] = nil end) + +-- Animation test items + +local animated_image = { + name = "testnodes_anim.png^[invert:rgb", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 4.0, + } +} + +local animated_overlay = { + name = "testitems_animation_overlay.png", + animation = { + type = "sheet_2d", + frames_w = 1, + frames_h = 4, + frame_length = 1.0, + } +} + +core.register_craftitem("testitems:inventory_image_animation", { + description = S("Animated Test Item").."\n".. + S("Image animate from A to D in 4s cycle"), + inventory_image = animated_image +}) + +core.register_craftitem("testitems:inventory_image_animation_overlay", { + description = S("Animated Test Item With Overlay").."\n".. + S("Should be colored red and have green stripes that move").."\n".. + S("Image animate from A to D in 4s cycle"), + inventory_image = animated_image, + inventory_overlay = animated_overlay, + color = "#ff0000", +}) + +core.register_craftitem("testitems:wield_image_animation", { + description = S("Wield Animated Test Item").."\n".. + S("Looks like the Animated Test Item, ".. + "but only animated for the wield mesh."), + inventory_image = "testnodes_anim.png^[invert:rgb^[verticalframe:4:0", + wield_image = animated_image, +}) + +core.register_craftitem("testitems:wield_image_animation_overlay", { + description = S("Wield Animated Test Item With Overlay").."\n".. + S("Looks like the animated Test Item With Overlay, ".. + "but only animated for the wield mesh."), + inventory_image = "testnodes_anim.png^[invert:rgb^[verticalframe:4:0", + inventory_overlay = "testitems_animation_overlay.png^[verticalframe:4:0", + wield_image = animated_image, + wield_overlay = animated_overlay, + color = "#ff0000", +}) diff --git a/games/devtest/mods/testitems/textures/testitems_animation_overlay.png b/games/devtest/mods/testitems/textures/testitems_animation_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..6751c89086fcf5eec1b80247454b4f9945f46f09 GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^0zmA*!3HFSYrjteQdXWWjv*0;-(K0s$zZ_4Z1Deo zNtKK37RP&=IvNgos!rayi;=;ByFz&u14E9}*IWjM4`10bm>429akAU5ZR?okv2P>L O6b4UMKbLh*2~7a;jUyre literal 0 HcmV?d00001 diff --git a/src/client/item_visuals_manager.cpp b/src/client/item_visuals_manager.cpp index 7503d496b..0c7937493 100644 --- a/src/client/item_visuals_manager.cpp +++ b/src/client/item_visuals_manager.cpp @@ -10,10 +10,28 @@ #include "itemdef.h" #include "inventory.h" -ItemVisualsManager::ItemVisuals::~ItemVisuals() { - if (wield_mesh.mesh) - wield_mesh.mesh->drop(); -} +struct ItemVisualsManager::ItemVisuals +{ + ItemMesh item_mesh; + Palette *palette; + + AnimationInfo inventory_normal; + AnimationInfo inventory_overlay; + + ItemVisuals() : + palette(nullptr) + {} + + ~ItemVisuals() + { + inventory_normal.freeFrames(); + inventory_overlay.freeFrames(); + if (item_mesh.mesh) + item_mesh.mesh->drop(); + } + + DISABLE_CLASS_COPY(ItemVisuals); +}; ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item, Client *client) const @@ -24,13 +42,18 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It IItemDefManager *idef = client->idef(); const ItemDefinition &def = item.getDefinition(idef); - std::string inventory_image = item.getInventoryImage(idef); - std::string inventory_overlay = item.getInventoryOverlay(idef); - std::string cache_key = def.name; - if (!inventory_image.empty()) - cache_key += "/" + inventory_image; - if (!inventory_overlay.empty()) - cache_key += ":" + inventory_overlay; + ItemImageDef inventory_image = item.getInventoryImage(idef); + ItemImageDef inventory_overlay = item.getInventoryOverlay(idef); + + // Key only consists of item name + image name, + // because animation currently cannot be overridden by meta + std::ostringstream os(def.name); + if (!inventory_image.name.empty()) + os << "/" << inventory_image.name; + if (!inventory_overlay.name.empty()) + os << ":" << inventory_overlay.name; + std::string cache_key = os.str(); + // Skip if already in cache auto it = m_cached_item_visuals.find(cache_key); @@ -43,36 +66,105 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It ITextureSource *tsrc = client->getTextureSource(); // Create new ItemVisuals - auto cc = std::make_unique(); + auto iv = std::make_unique(); - cc->inventory_texture = NULL; - if (!inventory_image.empty()) - cc->inventory_texture = tsrc->getTexture(inventory_image); - getItemMesh(client, item, &(cc->wield_mesh)); + auto populate_texture_and_animation = [tsrc]( + const ItemImageDef &image, + AnimationInfo &animation) + { + int frame_length_ms = 0; + auto frames = std::make_unique>(); + if (image.name.empty()) { + // no-op + } else if (image.animation.type == TileAnimationType::TAT_NONE) { + frames->push_back({0, tsrc->getTexture(image.name)}); + } else { + // Animated + // Get inventory texture frames + *frames = createAnimationFrames(tsrc, image.name, image.animation, frame_length_ms); + } + animation = AnimationInfo(frames.release(), frame_length_ms); + // `frames` are freed in `ItemVisuals::~ItemVisuals` + }; - cc->palette = tsrc->getPalette(def.palette_image); + populate_texture_and_animation(inventory_image, iv->inventory_normal); + populate_texture_and_animation(inventory_overlay, iv->inventory_overlay); + + createItemMesh(client, def, + iv->inventory_normal, + iv->inventory_overlay, + &(iv->item_mesh)); + + iv->palette = tsrc->getPalette(def.palette_image); // Put in cache - ItemVisuals *ptr = cc.get(); - m_cached_item_visuals[cache_key] = std::move(cc); + ItemVisuals *ptr = iv.get(); + m_cached_item_visuals[cache_key] = std::move(iv); return ptr; } -video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item, +// Needed because `ItemVisuals` is not known in the header. +ItemVisualsManager::ItemVisualsManager() +{ + m_main_thread = std::this_thread::get_id(); +} + +ItemVisualsManager::~ItemVisualsManager() +{ +} + +void ItemVisualsManager::clear() +{ + m_cached_item_visuals.clear(); +} + + +video::ITexture *ItemVisualsManager::getInventoryTexture(const ItemStack &item, Client *client) const { ItemVisuals *iv = createItemVisuals(item, client); if (!iv) return nullptr; - return iv->inventory_texture; + + // Texture animation update (if >1 frame) + return iv->inventory_normal.getTexture(client->getAnimationTime()); } -ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, Client *client) const +video::ITexture *ItemVisualsManager::getInventoryOverlayTexture(const ItemStack &item, + Client *client) const { ItemVisuals *iv = createItemVisuals(item, client); if (!iv) return nullptr; - return &(iv->wield_mesh); + + // Texture animation update (if >1 frame) + return iv->inventory_overlay.getTexture(client->getAnimationTime()); +} + +ItemMesh *ItemVisualsManager::getItemMesh(const ItemStack &item, Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + return iv ? &(iv->item_mesh) : nullptr; +} + +AnimationInfo *ItemVisualsManager::getInventoryAnimation(const ItemStack &item, + Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv || iv->inventory_normal.getFrameCount() <= 1) + return nullptr; + return &iv->inventory_normal; +} + +// Get item inventory overlay animation +// returns nullptr if it is not animated +AnimationInfo *ItemVisualsManager::getInventoryOverlayAnimation(const ItemStack &item, + Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv || iv->inventory_overlay.getFrameCount() <= 1) + return nullptr; + return &iv->inventory_overlay; } Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const diff --git a/src/client/item_visuals_manager.h b/src/client/item_visuals_manager.h index de346b141..6ac0ce702 100644 --- a/src/client/item_visuals_manager.h +++ b/src/client/item_visuals_manager.h @@ -20,21 +20,29 @@ namespace video { class ITexture; } struct ItemVisualsManager { - ItemVisualsManager() - { - m_main_thread = std::this_thread::get_id(); - } + ItemVisualsManager(); + ~ItemVisualsManager(); - void clear() { - m_cached_item_visuals.clear(); - } + /// Clears the cached visuals + void clear(); // Get item inventory texture video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const; - // Get item wield mesh + // Get item inventory overlay texture + video::ITexture* getInventoryOverlayTexture(const ItemStack &item, Client *client) const; + + // Get item inventory animation + // returns nullptr if it is not animated + AnimationInfo *getInventoryAnimation(const ItemStack &item, Client *client) const; + + // Get item inventory overlay animation + // returns nullptr if it is not animated + AnimationInfo *getInventoryOverlayAnimation(const ItemStack &item, Client *client) const; + + // Get item mesh // Once said to return nullptr if there is an inventory image, but this is wrong - ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const; + ItemMesh *getItemMesh(const ItemStack &item, Client *client) const; // Get item palette Palette* getPalette(const ItemStack &item, Client *client) const; @@ -44,21 +52,7 @@ struct ItemVisualsManager video::SColor getItemstackColor(const ItemStack &stack, Client *client) const; private: - struct ItemVisuals - { - video::ITexture *inventory_texture; - ItemMesh wield_mesh; - Palette *palette; - - ItemVisuals(): - inventory_texture(nullptr), - palette(nullptr) - {} - - ~ItemVisuals(); - - DISABLE_CLASS_COPY(ItemVisuals); - }; + struct ItemVisuals; // The id of the thread that is allowed to use irrlicht directly std::thread::id m_main_thread; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 894e97339..6db57de03 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -5,17 +5,26 @@ #include "tile.h" #include +video::ITexture *AnimationInfo::getTexture(float animation_time) +{ + if (getFrameCount() == 0) + return nullptr; + + // Figure out current frame + u16 frame = (u32)(animation_time * 1000.0f / std::max(1, m_frame_length_ms)) + % m_frame_count; + + assert(frame < m_frames->size()); + return (*m_frames)[frame].texture; +} + void AnimationInfo::updateTexture(video::SMaterial &material, float animation_time) { - // Figure out current frame - u16 frame = (u16)(animation_time * 1000 / m_frame_length_ms) % m_frame_count; - // Only adjust if frame changed - if (frame != m_frame) { - m_frame = frame; - assert(m_frame < m_frames->size()); - material.setTexture(0, (*m_frames)[m_frame].texture); + video::ITexture *texture = getTexture(animation_time); + if (texture) { + material.setTexture(0, texture); } -}; +} void TileLayer::applyMaterialOptions(video::SMaterial &material, int layer) const { diff --git a/src/client/tile.h b/src/client/tile.h index c14ccb9b1..c9517cd86 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -158,14 +158,34 @@ struct AnimationInfo { m_frames(tile.frames) {}; + AnimationInfo(std::vector *frames, u16 frame_length_ms) : + m_frame_length_ms(frame_length_ms), + m_frame_count(frames->size()), + m_frames(frames) + {}; + + void freeFrames() + { + delete m_frames; + m_frames = nullptr; + } + + size_t getFrameCount() const + { + return m_frames ? m_frame_count : 0; + } + void updateTexture(video::SMaterial &material, float animation_time); + // Returns nullptr if texture did not change since last time + video::ITexture *getTexture(float animation_time); + private: - u16 m_frame = 0; // last animation frame u16 m_frame_length_ms = 0; u16 m_frame_count = 1; - /// @note not owned by this struct + /// @note by default not owned by this struct + /// TODO. Change this to a shared pointer. std::vector *m_frames = nullptr; }; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index dc43a5d45..41cd846e4 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -301,17 +301,8 @@ void WieldMeshSceneNode::setExtruded(const TileDef &d0, const TileLayer &l0, extractTexture(d1, l1, tsrc), wield_scale); // Add color m_buffer_info.clear(); - m_buffer_info.emplace_back(0, l0.has_color, l0.color); - m_buffer_info.emplace_back(1, l1.has_color, l1.color); -} - -void WieldMeshSceneNode::setExtruded(const std::string &image, - const std::string &overlay, v3f wield_scale, ITextureSource *tsrc) -{ - video::ITexture *texture = tsrc->getTexture(image); - video::ITexture *overlay_texture = - overlay.empty() ? nullptr : tsrc->getTexture(overlay); - setExtruded(texture, overlay_texture, wield_scale); + m_buffer_info.emplace_back(0, l0); + m_buffer_info.emplace_back(1, l1); } void WieldMeshSceneNode::setExtruded(video::ITexture *texture, @@ -322,11 +313,13 @@ void WieldMeshSceneNode::setExtruded(video::ITexture *texture, return; } + // Get mesh from cache core::dimension2d dim = texture->getSize(); scene::IMesh *original = g_extrusion_mesh_cache->create(dim); scene::SMesh *mesh = cloneStaticMesh(original); original->drop(); + // Set texture mesh->getMeshBuffer(0)->getMaterial().setTexture(0, texture); if (overlay_texture) { // duplicate the extruded mesh for the overlay @@ -416,6 +409,38 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, return mesh; } +std::vector createAnimationFrames(ITextureSource *tsrc, + const std::string &image_name, const TileAnimationParams &animation, + int &result_frame_length_ms) +{ + result_frame_length_ms = 0; + + if (image_name.empty() || animation.type == TileAnimationType::TAT_NONE) + return {}; + + video::ITexture *orginal_texture = tsrc->getTexture(image_name); + if (!orginal_texture) + return {}; + + int frame_count = 1; + auto orginal_size = orginal_texture->getOriginalSize(); + animation.determineParams(orginal_size, &frame_count, &result_frame_length_ms, nullptr); + + std::vector frames(frame_count); + std::ostringstream os(std::ios::binary); + for (int i = 0; i < frame_count; i++) { + os.str(""); + os << image_name; + animation.getTextureModifer(os, orginal_size, i); + + u32 id; + frames[i].texture = tsrc->getTextureForMesh(os.str(), &id); + frames[i].texture_id = id; + } + + return frames; +} + void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool check_wield_image) { ITextureSource *tsrc = client->getTextureSource(); @@ -438,16 +463,46 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che m_buffer_info.clear(); m_base_color = item_visuals->getItemstackColor(item, client); - const std::string wield_image = item.getWieldImage(idef); - const std::string wield_overlay = item.getWieldOverlay(idef); + const ItemImageDef wield_image = item.getWieldImage(idef); + const ItemImageDef wield_overlay = item.getWieldOverlay(idef); const v3f wield_scale = item.getWieldScale(idef); // If wield_image needs to be checked and is defined, it overrides everything else - if (!wield_image.empty() && check_wield_image) { - setExtruded(wield_image, wield_overlay, wield_scale, tsrc); - m_buffer_info.emplace_back(0); - // overlay is white, if present - m_buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); + if (!wield_image.name.empty() && check_wield_image) { + video::ITexture *wield_texture; + video::ITexture *wield_overlay_texture = nullptr; + + int frame_length_ms; + m_wield_image_frames = createAnimationFrames(tsrc, + wield_image.name, wield_image.animation, frame_length_ms); + + auto &l0 = m_buffer_info.emplace_back(0); + if (m_wield_image_frames.empty()) { + wield_texture = tsrc->getTexture(wield_image.name); + } else { + wield_texture = m_wield_image_frames[0].texture; + l0.animation_info = std::make_unique( + &m_wield_image_frames, frame_length_ms); + } + + // Overlay + if (!wield_overlay.name.empty()) { + int overlay_frame_length_ms; + m_wield_overlay_frames = createAnimationFrames(tsrc, + wield_overlay.name, wield_overlay.animation, overlay_frame_length_ms); + + // overlay is white, if present + auto &l1 = m_buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); + if (m_wield_overlay_frames.empty()) { + wield_overlay_texture = tsrc->getTexture(wield_overlay.name); + } else { + wield_overlay_texture = m_wield_overlay_frames[0].texture; + l1.animation_info = std::make_unique( + &m_wield_overlay_frames, overlay_frame_length_ms); + } + } + + setExtruded(wield_texture, wield_overlay_texture, wield_scale); // initialize the color setColor(video::SColor(0xFFFFFFFF)); return; @@ -457,8 +512,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che if (def.type == ITEM_NODE) { switch (f.drawtype) { case NDT_AIRLIKE: - setExtruded("no_texture_airlike.png", "", - v3f(1), tsrc); + setExtruded(tsrc->getTexture("no_texture_airlike.png"), nullptr, v3f(1)); break; case NDT_SIGNLIKE: case NDT_TORCHLIKE: @@ -510,17 +564,19 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che setColor(video::SColor(0xFFFFFFFF)); return; } else { - const std::string inventory_image = item.getInventoryImage(idef); - if (!inventory_image.empty()) { - const std::string inventory_overlay = item.getInventoryOverlay(idef); - setExtruded(inventory_image, inventory_overlay, def.wield_scale, tsrc); + video::ITexture* inventory_texture = item_visuals->getInventoryTexture(item, client); + if (inventory_texture) { + video::ITexture* inventory_overlay = item_visuals->getInventoryOverlayTexture(item, + client); + setExtruded(inventory_texture, inventory_overlay, wield_scale); } else { - setExtruded("no_texture.png", "", def.wield_scale, tsrc); + setExtruded(tsrc->getTexture("no_texture.png"), nullptr, wield_scale); } - m_buffer_info.emplace_back(0); + m_buffer_info.emplace_back(0, item_visuals->getInventoryAnimation(item, client)); // overlay is white, if present - m_buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); + m_buffer_info.emplace_back(1, item_visuals->getInventoryOverlayAnimation(item, client), + true, video::SColor(0xFFFFFFFF)); // initialize the color setColor(video::SColor(0xFFFFFFFF)); @@ -599,12 +655,13 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) m_meshnode->setVisible(true); } -void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) +void createItemMesh(Client *client, const ItemDefinition &def, + AnimationInfo &animation_normal, + AnimationInfo &animation_overlay, + ItemMesh *result) { ITextureSource *tsrc = client->getTextureSource(); - IItemDefManager *idef = client->getItemDefManager(); const NodeDefManager *ndef = client->getNodeDefManager(); - const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); assert(result); @@ -615,19 +672,26 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) // Shading is on by default result->needs_shading = true; + video::ITexture *inventory_texture = animation_normal.getTexture(0.0f), + *inventory_overlay_texture = animation_overlay.getTexture(0.0f); + // If inventory_image is defined, it overrides everything else - const std::string inventory_image = item.getInventoryImage(idef); - const std::string inventory_overlay = item.getInventoryOverlay(idef); - if (!inventory_image.empty()) { - mesh = getExtrudedMesh(tsrc, inventory_image, inventory_overlay); - result->buffer_info.emplace_back(0); + if (inventory_texture) { + mesh = getExtrudedMesh(inventory_texture, inventory_overlay_texture); + + result->buffer_info.emplace_back(0, &animation_normal); + // overlay is white, if present - result->buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); + result->buffer_info.emplace_back(1, &animation_overlay, + true, video::SColor(0xFFFFFFFF)); + // Items with inventory images do not need shading result->needs_shading = false; } else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) { // Fallback image for airlike node - mesh = getExtrudedMesh(tsrc, "no_texture_airlike.png", inventory_overlay); + mesh = getExtrudedMesh(tsrc->getTexture("no_texture_airlike.png"), + inventory_overlay_texture); result->buffer_info.emplace_back(0); + // overlay is white, if present result->buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); result->needs_shading = false; @@ -636,21 +700,17 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) case NDT_PLANTLIKE: { const TileLayer &l0 = f.tiles[0].layers[0]; const TileLayer &l1 = f.tiles[0].layers[1]; - mesh = getExtrudedMesh( - extractTexture(f.tiledef[0], l0, tsrc), - extractTexture(f.tiledef[1], l1, tsrc)); + mesh = getExtrudedMesh(l0.texture, l1.texture); // Add color - result->buffer_info.emplace_back(0, l0.has_color, l0.color); - result->buffer_info.emplace_back(1, l1.has_color, l1.color); + result->buffer_info.emplace_back(0, l0); + result->buffer_info.emplace_back(1, l1); break; } case NDT_PLANTLIKE_ROOTED: { // Use the plant tile const TileLayer &l0 = f.special_tiles[0].layers[0]; - mesh = getExtrudedMesh( - extractTexture(f.tiledef_special[0], l0, tsrc), nullptr - ); - result->buffer_info.emplace_back(0, l0.has_color, l0.color); + mesh = getExtrudedMesh(l0.texture); + result->buffer_info.emplace_back(0, l0); break; } default: { @@ -689,27 +749,19 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) result->mesh = mesh; } -scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, - const std::string &imagename, const std::string &overlay_name) -{ - video::ITexture *texture = tsrc->getTexture(imagename); - video::ITexture *overlay_texture = - overlay_name.empty() ? nullptr : tsrc->getTexture(overlay_name); - return getExtrudedMesh(texture, overlay_texture); -} - scene::SMesh *getExtrudedMesh(video::ITexture *texture, video::ITexture *overlay_texture) { if (!texture) return nullptr; - // get mesh + // Get mesh core::dimension2d dim = texture->getSize(); scene::IMesh *original = g_extrusion_mesh_cache->create(dim); scene::SMesh *mesh = cloneStaticMesh(original); original->drop(); + // Set texture mesh->getMeshBuffer(0)->getMaterial().setTexture(0, texture); if (overlay_texture) { scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 0b5f7be81..d9149a0c2 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -26,6 +26,8 @@ struct ItemStack; struct TileDef; class Client; class ITextureSource; +struct ItemDefinition; +struct TileAnimationParams; class ShadowRenderer; /* @@ -56,6 +58,14 @@ public: layer(layer) {} + ItemMeshBufferInfo(int layer, const AnimationInfo *animation, + bool override_c = false, video::SColor color = {}) : + override_color(color), override_color_set(override_c), layer(layer) + { + if (animation) + animation_info = std::make_unique(*animation); + } + ItemMeshBufferInfo(int layer_num, const TileLayer &layer); void applyOverride(video::SColor &dest) const { @@ -109,9 +119,6 @@ public: void setExtruded(const TileDef &d0, const TileLayer &l0, const TileDef &d1, const TileLayer &l1, v3f wield_scale, ITextureSource *tsrc); - // Set apperance from texture name - void setExtruded(const std::string &image, const std::string &overlay, - v3f wield_scale, ITextureSource *tsrc); void setItem(const ItemStack &item, Client *client, bool check_wield_image = true); @@ -153,6 +160,11 @@ private: */ video::SColor m_base_color; + // Empty if wield image is empty or not animated + // Owned by this class to get AnimationInfo for the mesh buffer info + std::vector m_wield_image_frames; + std::vector m_wield_overlay_frames; + // Bounding box culling is disabled for this type of scene node, // so this variable is just required so we can implement // getBoundingBox() and is set to an empty box. @@ -161,14 +173,20 @@ private: ShadowRenderer *m_shadow; }; +std::vector createAnimationFrames(ITextureSource *tsrc, + const std::string &image_name, const TileAnimationParams &animation, + int& result_frame_length_ms); + +scene::SMesh *getExtrudedMesh(video::ITexture *texture, + video::ITexture *overlay_texture = nullptr); + /** * NOTE: The item mesh is only suitable for inventory rendering (due to its * material types). In-world rendering of items must go through WieldMeshSceneNode. */ -void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); - -scene::SMesh *getExtrudedMesh(video::ITexture *texture, - video::ITexture *overlay_texture); - -scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename, - const std::string &overlay_name); +// This is only used to initially generate an ItemMesh +// To get the mesh, use ItemVisualsManager::getItemMesh(item, client) instead +void createItemMesh(Client *client, const ItemDefinition &def, + AnimationInfo &animation_normal, + AnimationInfo &animation_overlay, + ItemMesh *result); diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 9bfeddfc4..bb68937db 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -48,8 +48,9 @@ void drawItemStack( bool draw_overlay = false; - const std::string inventory_image = item.getInventoryImage(idef); - const std::string inventory_overlay = item.getInventoryOverlay(idef); + // Null if inventory_image is empty + video::ITexture *inventory_texture = item_visuals->getInventoryTexture(item, client); + video::ITexture *inventory_overlay = item_visuals->getInventoryOverlayTexture(item, client); bool has_mesh = false; ItemMesh *imesh; @@ -59,8 +60,8 @@ void drawItemStack( viewrect.clipAgainst(*clip); // Render as mesh if animated or no inventory image - if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { - imesh = item_visuals->getWieldMesh(item, client); + if ((enable_animations && rotation_kind < IT_ROT_NONE) || !inventory_texture) { + imesh = item_visuals->getItemMesh(item, client); has_mesh = imesh && imesh->mesh; } if (has_mesh) { @@ -152,36 +153,33 @@ void drawItemStack( driver->setTransform(video::ETS_PROJECTION, oldProjMat); driver->setViewPort(oldViewPort); - draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); + draw_overlay = def.type == ITEM_NODE && !inventory_texture; } else { // Otherwise just draw as 2D - video::ITexture *texture = item_visuals->getInventoryTexture(item, client); video::SColor color; - if (texture) { + if (inventory_texture) { color = item_visuals->getItemstackColor(item, client); } else { color = video::SColor(255, 255, 255, 255); ITextureSource *tsrc = client->getTextureSource(); - texture = tsrc->getTexture("no_texture.png"); - if (!texture) + inventory_texture = tsrc->getTexture("no_texture.png"); + if (!inventory_texture) return; } const video::SColor colors[] = { color, color, color, color }; - draw2DImageFilterScaled(driver, texture, rect, - core::rect({0, 0}, core::dimension2di(texture->getOriginalSize())), + draw2DImageFilterScaled(driver, inventory_texture, rect, + core::rect({0, 0}, core::dimension2di(inventory_texture->getOriginalSize())), clip, colors, true); draw_overlay = true; } // draw the inventory_overlay - if (!inventory_overlay.empty() && draw_overlay) { - ITextureSource *tsrc = client->getTextureSource(); - video::ITexture *overlay_texture = tsrc->getTexture(inventory_overlay); - core::dimension2d dimens = overlay_texture->getOriginalSize(); + if (inventory_overlay && draw_overlay) { + core::dimension2d dimens = inventory_overlay->getOriginalSize(); core::rect srcrect(0, 0, dimens.Width, dimens.Height); - draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true); + draw2DImageFilterScaled(driver, inventory_overlay, rect, srcrect, clip, 0, true); } if (def.type == ITEM_TOOL && item.wear != 0) { diff --git a/src/inventory.cpp b/src/inventory.cpp index ae8d4038e..50a61d398 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -256,40 +256,44 @@ std::string ItemStack::getShortDescription(const IItemDefManager *itemdef) const return desc; } -std::string ItemStack::getInventoryImage(const IItemDefManager *itemdef) const +ItemImageDef ItemStack::getInventoryImage(const IItemDefManager *itemdef) const { - std::string texture = metadata.getString("inventory_image"); - if (texture.empty()) - texture = getDefinition(itemdef).inventory_image; + ItemImageDef image = getDefinition(itemdef).inventory_image; + std::string meta_image = metadata.getString("inventory_image"); + if (!meta_image.empty()) + image = meta_image; - return texture; + return image; } -std::string ItemStack::getInventoryOverlay(const IItemDefManager *itemdef) const +ItemImageDef ItemStack::getInventoryOverlay(const IItemDefManager *itemdef) const { - std::string texture = metadata.getString("inventory_overlay"); - if (texture.empty()) - texture = getDefinition(itemdef).inventory_overlay; + ItemImageDef image = getDefinition(itemdef).inventory_overlay; + std::string meta_image = metadata.getString("inventory_overlay"); + if (!meta_image.empty()) + image = meta_image; - return texture; + return image; } -std::string ItemStack::getWieldImage(const IItemDefManager *itemdef) const +ItemImageDef ItemStack::getWieldImage(const IItemDefManager *itemdef) const { - std::string texture = metadata.getString("wield_image"); - if (texture.empty()) - texture = getDefinition(itemdef).wield_image; + ItemImageDef image = getDefinition(itemdef).wield_image; + std::string meta_image = metadata.getString("wield_image"); + if (!meta_image.empty()) + image = meta_image; - return texture; + return image; } -std::string ItemStack::getWieldOverlay(const IItemDefManager *itemdef) const +ItemImageDef ItemStack::getWieldOverlay(const IItemDefManager *itemdef) const { - std::string texture = metadata.getString("wield_overlay"); - if (texture.empty()) - texture = getDefinition(itemdef).wield_overlay; + ItemImageDef image = getDefinition(itemdef).wield_overlay; + std::string meta_image = metadata.getString("wield_overlay"); + if (!meta_image.empty()) + image = meta_image; - return texture; + return image; } v3f ItemStack::getWieldScale(const IItemDefManager *itemdef) const diff --git a/src/inventory.h b/src/inventory.h index 290c5cf7d..5f6d0faa9 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -36,10 +36,10 @@ struct ItemStack std::string getDescription(const IItemDefManager *itemdef) const; std::string getShortDescription(const IItemDefManager *itemdef) const; - std::string getInventoryImage(const IItemDefManager *itemdef) const; - std::string getInventoryOverlay(const IItemDefManager *itemdef) const; - std::string getWieldImage(const IItemDefManager *itemdef) const; - std::string getWieldOverlay(const IItemDefManager *itemdef) const; + ItemImageDef getInventoryImage(const IItemDefManager *itemdef) const; + ItemImageDef getInventoryOverlay(const IItemDefManager *itemdef) const; + ItemImageDef getWieldImage(const IItemDefManager *itemdef) const; + ItemImageDef getWieldOverlay(const IItemDefManager *itemdef) const; v3f getWieldScale(const IItemDefManager *itemdef) const; /* diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 17f46dedc..27303d8e0 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -82,6 +82,27 @@ void TouchInteraction::deSerialize(std::istream &is) pointed_object = (TouchInteractionMode)tmp; } +void ItemImageDef::serialize(std::ostream &os, u16 protocol_version) const +{ + if (protocol_version < 51) { + // Use first frame if animation is not supported + std::string image_to_send = name; + animation.extractFirstFrame(image_to_send); + os << serializeString16(image_to_send); + return; + } + os << serializeString16(name); + animation.serialize(os, protocol_version); + +} +void ItemImageDef::deSerialize(std::istream &is, u16 protocol_version) +{ + name = deSerializeString16(is); + if (protocol_version < 51) + return; + animation.deSerialize(is, protocol_version); +} + /* ItemDefinition */ @@ -153,10 +174,10 @@ void ItemDefinition::reset() name.clear(); description.clear(); short_description.clear(); - inventory_image.clear(); - inventory_overlay.clear(); - wield_image.clear(); - wield_overlay.clear(); + inventory_image.reset(); + inventory_overlay.reset(); + wield_image.reset(); + wield_overlay.reset(); palette_image.clear(); color = video::SColor(0xFFFFFFFF); wield_scale = v3f(1.0, 1.0, 1.0); @@ -187,8 +208,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, type); os << serializeString16(name); os << serializeString16(description); - os << serializeString16(inventory_image); - os << serializeString16(wield_image); + inventory_image.serialize(os, protocol_version); + wield_image.serialize(os, protocol_version); writeV3F32(os, wield_scale); writeS16(os, stack_max); writeU8(os, usable); @@ -217,8 +238,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const writeF32(os, range); os << serializeString16(palette_image); writeARGB8(os, color); - os << serializeString16(inventory_overlay); - os << serializeString16(wield_overlay); + inventory_overlay.serialize(os, protocol_version); + wield_overlay.serialize(os, protocol_version); os << serializeString16(short_description); @@ -273,8 +294,8 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) name = deSerializeString16(is); description = deSerializeString16(is); - inventory_image = deSerializeString16(is); - wield_image = deSerializeString16(is); + inventory_image.deSerialize(is, protocol_version); + wield_image.deSerialize(is, protocol_version); wield_scale = readV3F32(is); stack_max = readS16(is); usable = readU8(is); @@ -303,8 +324,8 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) range = readF32(is); palette_image = deSerializeString16(is); color = readARGB8(is); - inventory_overlay = deSerializeString16(is); - wield_overlay = deSerializeString16(is); + inventory_overlay .deSerialize(is, protocol_version); + wield_overlay.deSerialize(is, protocol_version); // If you add anything here, insert it inside the try-catch // block to not need to increase the version. diff --git a/src/itemdef.h b/src/itemdef.h index fbbadaab0..76e5c1642 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -16,6 +16,7 @@ #include "tool.h" #include "util/pointabilities.h" #include "util/pointedthing.h" +#include "tileanimation.h" struct ToolCapabilities; struct ItemDefinition; @@ -56,6 +57,28 @@ struct TouchInteraction void deSerialize(std::istream &is); }; +struct ItemImageDef +{ + // May be extended to support meshes in the future + std::string name; + TileAnimationParams animation; + + ItemImageDef &operator=(const std::string &other_name) + { + this->name = other_name; + return *this; + } + + void reset() + { + animation.type = TileAnimationType::TAT_NONE; + name.clear(); + } + + void serialize(std::ostream &os, u16 protocol_version) const; + void deSerialize(std::istream &is, u16 protocol_version); +}; + struct ItemDefinition { /* @@ -69,10 +92,10 @@ struct ItemDefinition /* Visual properties */ - std::string inventory_image; // Optional for nodes, mandatory for tools/craftitems - std::string inventory_overlay; // Overlay of inventory_image. - std::string wield_image; // If empty, inventory_image or mesh (only nodes) is used - std::string wield_overlay; // Overlay of wield_image. + ItemImageDef inventory_image; // Optional for nodes, mandatory for tools/craftitems + ItemImageDef inventory_overlay; // Overlay of inventory_image. + ItemImageDef wield_image; // If empty, inventory_image or mesh (only nodes) is used + ItemImageDef wield_overlay; // Overlay of wield_image. std::string palette_image; // If specified, the item will be colorized based on this video::SColor color; // The fallback color of the node. v3f wield_scale; diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index c7e2a9827..b240c2be6 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -71,10 +71,13 @@ PROTOCOL VERSION 50 Support for TOCLIENT_SPAWN_PARTICLE_BATCH [scheduled bump for 5.14.0] + PROTOCOL VERSION 51 + Only send first frame of animated item/wield images to older client + [scheduled bump for 5.15.0] */ // Note: Also update core.protocol_versions in builtin when bumping -const u16 LATEST_PROTOCOL_VERSION = 50; +const u16 LATEST_PROTOCOL_VERSION = 51; // See also formspec [Version History] in doc/lua_api.md const u16 FORMSPEC_API_VERSION = 10; diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 86bc47238..77a50ea79 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "client/wieldmesh.h" // createAnimationFrames #endif #include "log.h" #include "settings.h" @@ -712,34 +713,17 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, else layer->color = color; - // Animation parameters - int frame_count = 1; + // Animation if (layer->material_flags & MATERIAL_FLAG_ANIMATION) { - assert(layer->texture); int frame_length_ms = 0; - tiledef.animation.determineParams(layer->texture->getOriginalSize(), - &frame_count, &frame_length_ms, NULL); - layer->animation_frame_count = frame_count; - layer->animation_frame_length_ms = frame_length_ms; - } - - if (frame_count == 1) { - layer->material_flags &= ~MATERIAL_FLAG_ANIMATION; - } else { - assert(layer->texture); - if (!layer->frames) - layer->frames = new std::vector(); - layer->frames->resize(frame_count); - - std::ostringstream os(std::ios::binary); - for (int i = 0; i < frame_count; i++) { - os.str(""); - os << tiledef.name; - tiledef.animation.getTextureModifer(os, - layer->texture->getOriginalSize(), i); - - FrameSpec &frame = (*layer->frames)[i]; - frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id); + std::vector frames = createAnimationFrames( + tsrc, tiledef.name, tiledef.animation, frame_length_ms); + if (frames.size() > 1) { + layer->frames = new std::vector(frames); + layer->animation_frame_count = layer->frames->size(); + layer->animation_frame_length_ms = frame_length_ms; + } else { + layer->material_flags &= ~MATERIAL_FLAG_ANIMATION; } } } diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index cd53781ab..0b8c5fdcb 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -68,10 +68,20 @@ void read_item_definition(lua_State* L, int index, getstringfield(L, index, "description", def.description); getstringfield(L, index, "short_description", def.short_description); - getstringfield(L, index, "inventory_image", def.inventory_image); - getstringfield(L, index, "inventory_overlay", def.inventory_overlay); - getstringfield(L, index, "wield_image", def.wield_image); - getstringfield(L, index, "wield_overlay", def.wield_overlay); + + lua_getfield(L, index, "inventory_image"); + def.inventory_image = read_item_image_definition(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "inventory_overlay"); + def.inventory_overlay = read_item_image_definition(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "wield_image"); + def.wield_image = read_item_image_definition(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "wield_overlay"); + def.wield_overlay = read_item_image_definition(L, -1); + lua_pop(L, 1); + getstringfield(L, index, "palette", def.palette_image); // Read item color. @@ -212,13 +222,13 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) } lua_pushstring(L, type.c_str()); lua_setfield(L, -2, "type"); - lua_pushstring(L, i.inventory_image.c_str()); + push_item_image_definition(L, i.inventory_image); lua_setfield(L, -2, "inventory_image"); - lua_pushstring(L, i.inventory_overlay.c_str()); + push_item_image_definition(L, i.inventory_overlay); lua_setfield(L, -2, "inventory_overlay"); - lua_pushstring(L, i.wield_image.c_str()); + push_item_image_definition(L, i.wield_image); lua_setfield(L, -2, "wield_image"); - lua_pushstring(L, i.wield_overlay.c_str()); + push_item_image_definition(L, i.wield_overlay); lua_setfield(L, -2, "wield_overlay"); lua_pushstring(L, i.palette_image.c_str()); lua_setfield(L, -2, "palette_image"); @@ -1543,11 +1553,11 @@ void push_inventory_lists(lua_State *L, const Inventory &inv) void read_inventory_list(lua_State *L, int tableindex, Inventory *inv, const char *name, IGameDef *gdef, int forcesize) { - if(tableindex < 0) + if (tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; // If nil, delete list - if(lua_isnil(L, tableindex)){ + if (lua_isnil(L, tableindex)) { inv->deleteList(name); return; } @@ -1570,6 +1580,40 @@ void read_inventory_list(lua_State *L, int tableindex, } } +/******************************************************************************/ +ItemImageDef read_item_image_definition(lua_State *L, int index) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + + ItemImageDef item_image; + + if(lua_isstring(L, index)){ + item_image.name = lua_tostring(L, index); + } + else if(lua_istable(L, index)) + { + getstringfield(L, index, "name", item_image.name); + + lua_getfield(L, index, "animation"); + item_image.animation = read_animation_definition(L, -1); + lua_pop(L, 1); + } + + return item_image; +} + +/******************************************************************************/ +void push_item_image_definition(lua_State *L, const ItemImageDef &item_image) +{ + /* FIXME: inventory_image.animation, inventory_overlay.animation, wield_image.animation + * and wield_overlay.animation, because for nodes we don't push "tiles" (yet) and + * we don't have a push_TileAnimationParams function (yet). */ + + lua_pushstring(L, item_image.name.c_str()); + +} + /******************************************************************************/ struct TileAnimationParams read_animation_definition(lua_State *L, int index) { diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 0587ed7f3..71bf0a210 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -82,6 +82,9 @@ ItemStack read_item(lua_State *L, int index, IItemDefManager *idef); struct TileAnimationParams read_animation_definition(lua_State *L, int index); +ItemImageDef read_item_image_definition(lua_State *L, int index); +void push_item_image_definition(lua_State *L, const ItemImageDef &item_image); + PointabilityType read_pointability_type(lua_State *L, int index); Pointabilities read_pointabilities(lua_State *L, int index); void push_pointability_type(lua_State *L, PointabilityType pointable); diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index a3dc6965d..1a6bc5fdb 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -3,6 +3,7 @@ // Copyright (C) 2016 sfan5 #include "tileanimation.h" #include "util/serialize.h" +#include "util/string.h" void TileAnimationParams::serialize(std::ostream &os, u16 protocol_ver) const { @@ -88,6 +89,35 @@ void TileAnimationParams::getTextureModifer(std::ostream &os, v2u32 texture_size } } +void TileAnimationParams::extractFirstFrame(std::string &name) const +{ + if (name.empty()) + return; + + switch(type) { + case TAT_VERTICAL_FRAMES: { + // Can't use "[verticalframe", since the the server doesn't know the texture size. + std::ostringstream oss; + str_texture_modifiers_escape(name); + oss << "[combine:" << + vertical_frames.aspect_w << "x" << + vertical_frames.aspect_h << + ":0,0=" << name; + name = oss.str(); + break; + } case TAT_SHEET_2D: { + std::ostringstream oss; + oss << name << "^[sheet:" << + sheet_2d.frames_w << "x" << + sheet_2d.frames_h << ":0,0"; + name = oss.str(); + break; + } case TAT_NONE: + default: + break; + } +} + v2f TileAnimationParams::getTextureCoords(v2u32 texture_size, int frame) const { v2u32 ret(0, 0); diff --git a/src/tileanimation.h b/src/tileanimation.h index 27ae3b7c7..2c1f91c97 100644 --- a/src/tileanimation.h +++ b/src/tileanimation.h @@ -37,8 +37,13 @@ struct TileAnimationParams void serialize(std::ostream &os, u16 protocol_ver) const; void deSerialize(std::istream &is, u16 protocol_ver); + void determineParams(v2u32 texture_size, int *frame_count, int *frame_length_ms, v2u32 *frame_size) const; void getTextureModifer(std::ostream &os, v2u32 texture_size, int frame) const; v2f getTextureCoords(v2u32 texture_size, int frame) const; + + // Modifies the texture name such that it only contains the first frame + // If the texture_size is know (client code), getTextureModifer should be used instead + void extractFirstFrame(std::string &name) const; }; diff --git a/src/util/string.h b/src/util/string.h index 78881a9a4..9407647ed 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -500,6 +500,16 @@ inline void str_formspec_escape(std::string &str) str_replace(str, "$", "\\$"); } +/** + * Escapes characters to nest texture modifiers + */ +inline void str_texture_modifiers_escape(std::string &str) +{ + str_replace(str, "\\", "\\\\"); + str_replace(str, "^", "\\^"); + str_replace(str, ":", "\\:"); +} + /** * Replace all occurrences of the character \p from in \p str with \p to. *