]> git.lizzy.rs Git - minetest.git/commitdiff
Automatic item and node colorization (#5640)
authorDániel Juhász <juhdanad@gmail.com>
Tue, 20 Jun 2017 09:19:56 +0000 (09:19 +0000)
committerLoïc Blot <nerzhul@users.noreply.github.com>
Tue, 20 Jun 2017 09:19:56 +0000 (11:19 +0200)
* Automatic item and node colorization

Now nodes with a palette yield colored item stacks, and colored items
place colored nodes by default. The client predicts the colorization.

* Backwards compatibility

* Use nil

* Style fixes

* Fix code style

* Document changes

builtin/game/falling.lua
builtin/game/item.lua
doc/lua_api.txt
src/game.cpp
src/inventory.cpp
src/inventory.h
src/script/lua_api/l_inventory.cpp
src/script/lua_api/l_inventory.h

index b1beb1ab0c47bd5f20ff03d67862f76f1a2c7c8f..1ac4f70817d6fa07c8b427484d2bcbbf64cc39a2 100644 (file)
@@ -93,7 +93,7 @@ core.register_entity(":__builtin:falling_node", {
                                core.remove_node(np)
                                if nd and nd.buildable_to == false then
                                        -- Add dropped items
-                                       local drops = core.get_node_drops(n2.name, "")
+                                       local drops = core.get_node_drops(n2, "")
                                        for _, dropped_item in pairs(drops) do
                                                core.add_item(np, dropped_item)
                                        end
@@ -145,9 +145,9 @@ function core.spawn_falling_node(pos)
 end
 
 local function drop_attached_node(p)
-       local nn = core.get_node(p).name
+       local n = core.get_node(p)
        core.remove_node(p)
-       for _, item in pairs(core.get_node_drops(nn, "")) do
+       for _, item in pairs(core.get_node_drops(n, "")) do
                local pos = {
                        x = p.x + math.random()/2 - 0.25,
                        y = p.y + math.random()/2 - 0.25,
index e36745f930f9ee7dd3b94f1063a2c6c820e38e5e..f6de2c33979131edd991350b48339a6be606bf0c 100644 (file)
@@ -155,12 +155,35 @@ function core.yaw_to_dir(yaw)
        return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
 end
 
-function core.get_node_drops(nodename, toolname)
+function core.get_node_drops(node, toolname)
+       -- Compatibility, if node is string
+       local nodename = node
+       local param2 = 0
+       -- New format, if node is table
+       if (type(node) == "table") then
+               nodename = node.name
+               param2 = node.param2
+       end
        local def = core.registered_nodes[nodename]
        local drop = def and def.drop
        if drop == nil then
                -- default drop
-               return {nodename}
+               local stack = ItemStack(nodename)
+               if def then
+                       local type = def.paramtype2
+                       if (type == "color") or (type == "colorfacedir") or
+                                       (type == "colorwallmounted") then
+                               local meta = stack:get_meta()
+                               local color_part = param2
+                               if (type == "colorfacedir") then
+                                       color_part = math.floor(color_part / 32) * 32;
+                               elseif (type == "colorwallmounted") then
+                                       color_part = math.floor(color_part / 8) * 8;
+                               end
+                               meta:set_int("palette_index", color_part)
+                       end
+               end
+               return {stack:to_string()}
        elseif type(drop) == "string" then
                -- itemstring drop
                return {drop}
@@ -258,7 +281,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
                .. def.name .. " at " .. core.pos_to_string(place_to))
 
        local oldnode = core.get_node(place_to)
-       local newnode = {name = def.name, param1 = 0, param2 = param2}
+       local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
 
        -- Calculate direction for wall mounted stuff like torches and signs
        if def.place_param2 ~= nil then
@@ -286,6 +309,25 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
                end
        end
 
+       local metatable = itemstack:get_meta():to_table().fields
+
+       -- Transfer color information
+       if metatable.palette_index and not def.place_param2 then
+               local color_divisor = nil
+               if def.paramtype2 == "color" then
+                       color_divisor = 1
+               elseif def.paramtype2 == "colorwallmounted" then
+                       color_divisor = 8
+               elseif def.paramtype2 == "colorfacedir" then
+                       color_divisor = 32
+               end
+               if color_divisor then
+                       local color = math.floor(metatable.palette_index / color_divisor)
+                       local other = newnode.param2 % color_divisor
+                       newnode.param2 = color * color_divisor + other
+               end
+       end
+
        -- Check if the node is attached and if it can be placed there
        if core.get_item_group(def.name, "attached_node") ~= 0 and
                not builtin_shared.check_attached_node(place_to, newnode) then
@@ -474,7 +516,7 @@ function core.node_dig(pos, node, digger)
                .. node.name .. " at " .. core.pos_to_string(pos))
 
        local wielded = digger:get_wielded_item()
-       local drops = core.get_node_drops(node.name, wielded:get_name())
+       local drops = core.get_node_drops(node, wielded:get_name())
 
        local wdef = wielded:get_definition()
        local tp = wielded:get_tool_capabilities()
index a3413221b6998f33a8f216caa699c49cc128625f..9bd92b34a89ecddd97e11273b8e0c506ad15f32b 100644 (file)
@@ -531,9 +531,11 @@ for conversion.
 If the `ItemStack`'s metadata contains the `color` field, it will be
 lost on placement, because nodes on the map can only use palettes.
 
-If the `ItemStack`'s metadata contains the `palette_index` field, you
-currently must manually convert between it and the node's `param2` with
-custom `on_place` and `on_dig` callbacks.
+If the `ItemStack`'s metadata contains the `palette_index` field, it is
+automatically transferred between node and item forms by the engine,
+when a player digs or places a colored node.
+You can disable this feature by setting the `drop` field of the node
+to itself (without metadata).
 
 ### Colored items in craft recipes
 Craft recipes only support item strings, but fortunately item strings
@@ -3326,8 +3328,9 @@ An `InvRef` is a reference to an inventory.
 * `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`
 * `room_for_item(listname, stack):` returns `true` if the stack of items
   can be fully added to the list
-* `contains_item(listname, stack)`: returns `true` if the stack of items
-  can be fully taken from the list
+* `contains_item(listname, stack, [match_meta])`: returns `true` if
+  the stack of items can be fully taken from the list.
+  If `match_meta` is false, only the items' names are compared (default: `false`).
 * `remove_item(listname, stack)`: take as many items as specified from the list,
   returns the items that were actually removed (as an `ItemStack`) -- note that
   any item metadata is ignored, so attempting to remove a specific unique
index 325557723dd44868627fb2fd3337113a0b30a81b..b81efc33fbb457281d6aa9d192e1a16b4ba09338 100644 (file)
@@ -774,8 +774,8 @@ class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactor
 };
 
 
-bool nodePlacementPrediction(Client &client,
-               const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos)
+bool nodePlacementPrediction(Client &client, const ItemDefinition &playeritem_def,
+       const ItemStack &playeritem, v3s16 nodepos, v3s16 neighbourpos)
 {
        std::string prediction = playeritem_def.node_placement_prediction;
        INodeDefManager *nodedef = client.ndef();
@@ -818,11 +818,13 @@ bool nodePlacementPrediction(Client &client,
                        return false;
                }
 
+               const ContentFeatures &predicted_f = nodedef->get(id);
+
                // Predict param2 for facedir and wallmounted nodes
                u8 param2 = 0;
 
-               if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED ||
-                               nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) {
+               if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+                               predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
                        v3s16 dir = nodepos - neighbourpos;
 
                        if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
@@ -834,8 +836,8 @@ bool nodePlacementPrediction(Client &client,
                        }
                }
 
-               if (nodedef->get(id).param_type_2 == CPT2_FACEDIR ||
-                               nodedef->get(id).param_type_2 == CPT2_COLORED_FACEDIR) {
+               if (predicted_f.param_type_2 == CPT2_FACEDIR ||
+                               predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
                        v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
 
                        if (abs(dir.X) > abs(dir.Z)) {
@@ -848,7 +850,7 @@ bool nodePlacementPrediction(Client &client,
                assert(param2 <= 5);
 
                //Check attachment if node is in group attached_node
-               if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) {
+               if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
                        static v3s16 wallmounted_dirs[8] = {
                                v3s16(0, 1, 0),
                                v3s16(0, -1, 0),
@@ -859,8 +861,8 @@ bool nodePlacementPrediction(Client &client,
                        };
                        v3s16 pp;
 
-                       if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED ||
-                                       nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED)
+                       if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+                                       predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
                                pp = p + wallmounted_dirs[param2];
                        else
                                pp = p + v3s16(0, -1, 0);
@@ -869,6 +871,28 @@ bool nodePlacementPrediction(Client &client,
                                return false;
                }
 
+               // Apply color
+               if ((predicted_f.param_type_2 == CPT2_COLOR
+                               || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
+                               || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
+                       const std::string &indexstr = playeritem.metadata.getString(
+                               "palette_index", 0);
+                       if (!indexstr.empty()) {
+                               s32 index = mystoi(indexstr);
+                               if (predicted_f.param_type_2 == CPT2_COLOR) {
+                                       param2 = index;
+                               } else if (predicted_f.param_type_2
+                                               == CPT2_COLORED_WALLMOUNTED) {
+                                       // param2 = pure palette index + other
+                                       param2 = (index & 0xf8) | (param2 & 0x07);
+                               } else if (predicted_f.param_type_2
+                                               == CPT2_COLORED_FACEDIR) {
+                                       // param2 = pure palette index + other
+                                       param2 = (index & 0xe0) | (param2 & 0x1f);
+                               }
+                       }
+               }
+
                // Add node to client map
                MapNode n(id, 0, param2);
 
@@ -1277,8 +1301,9 @@ class Game {
                        const core::line3d<f32> &shootline, bool liquids_pointable,
                        bool look_for_object, const v3s16 &camera_offset);
        void handlePointingAtNothing(const ItemStack &playerItem);
-       void handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def,
-                       const ToolCapabilities &playeritem_toolcap, f32 dtime);
+       void handlePointingAtNode(const PointedThing &pointed,
+               const ItemDefinition &playeritem_def, const ItemStack &playeritem,
+               const ToolCapabilities &playeritem_toolcap, f32 dtime);
        void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
                        const v3f &player_position, bool show_debug);
        void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
@@ -3599,7 +3624,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
                if (playeritem.name.empty() && hand_def.tool_capabilities != NULL) {
                        playeritem_toolcap = *hand_def.tool_capabilities;
                }
-               handlePointingAtNode(pointed, playeritem_def, playeritem_toolcap, dtime);
+               handlePointingAtNode(pointed, playeritem_def, playeritem,
+                       playeritem_toolcap, dtime);
        } else if (pointed.type == POINTEDTHING_OBJECT) {
                handlePointingAtObject(pointed, playeritem, player_position, show_debug);
        } else if (isLeftPressed()) {
@@ -3734,8 +3760,9 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem)
 }
 
 
-void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def,
-               const ToolCapabilities &playeritem_toolcap, f32 dtime)
+void Game::handlePointingAtNode(const PointedThing &pointed,
+       const ItemDefinition &playeritem_def, const ItemStack &playeritem,
+       const ToolCapabilities &playeritem_toolcap, f32 dtime)
 {
        v3s16 nodepos = pointed.node_undersurface;
        v3s16 neighbourpos = pointed.node_abovesurface;
@@ -3795,7 +3822,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
                        // If the wielded item has node placement prediction,
                        // make that happen
                        bool placed = nodePlacementPrediction(*client,
-                                       playeritem_def,
+                                       playeritem_def, playeritem,
                                        nodepos, neighbourpos);
 
                        if (placed) {
index 25f524150191d164606b8dfefa8918acc660f2d6..5ce82737a940ae52d402940e7b10bf81839962f0 100644 (file)
@@ -658,7 +658,7 @@ bool InventoryList::roomForItem(const ItemStack &item_) const
        return false;
 }
 
-bool InventoryList::containsItem(const ItemStack &item) const
+bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const
 {
        u32 count = item.count;
        if(count == 0)
@@ -669,9 +669,9 @@ bool InventoryList::containsItem(const ItemStack &item) const
        {
                if(count == 0)
                        break;
-               if(i->name == item.name)
-               {
-                       if(i->count >= count)
+               if (i->name == item.name
+                               && (!match_meta || (i->metadata == item.metadata))) {
+                       if (i->count >= count)
                                return true;
                        else
                                count -= i->count;
index e3e8187081807f9a5a478e672c4737e0c7a14311..04c8156c8f8d9518b270ba93f457b2429687e8cb 100644 (file)
@@ -223,9 +223,10 @@ class InventoryList
        // Checks whether there is room for a given item
        bool roomForItem(const ItemStack &item) const;
 
-       // Checks whether the given count of the given item name
+       // Checks whether the given count of the given item
        // exists in this inventory list.
-       bool containsItem(const ItemStack &item) const;
+       // If match_meta is false, only the items' names are compared.
+       bool containsItem(const ItemStack &item, bool match_meta) const;
 
        // Removes the given count of the given item name from
        // this inventory list. Walks the list in reverse order.
index f5e76a7b6989cb3c67f440b267b436c594045699..e92197c141f8b9f1330a33baa28a34af9def9837 100644 (file)
@@ -325,8 +325,8 @@ int InvRef::l_room_for_item(lua_State *L)
        return 1;
 }
 
-// contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false
-// Returns true if the list contains the given count of the given item name
+// contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false
+// Returns true if the list contains the given count of the given item
 int InvRef::l_contains_item(lua_State *L)
 {
        NO_MAP_LOCK_REQUIRED;
@@ -334,8 +334,11 @@ int InvRef::l_contains_item(lua_State *L)
        const char *listname = luaL_checkstring(L, 2);
        ItemStack item = read_item(L, 3, getServer(L)->idef());
        InventoryList *list = getlist(L, ref, listname);
-       if(list){
-               lua_pushboolean(L, list->containsItem(item));
+       bool match_meta = false;
+       if (lua_isboolean(L, 4))
+               match_meta = lua_toboolean(L, 4);
+       if (list) {
+               lua_pushboolean(L, list->containsItem(item, match_meta));
        } else {
                lua_pushboolean(L, false);
        }
index 91d41c0d02431584b0285877ac8f4d7c3c810ee8..502827a11ef166202a198395b5d47d201079e0ce 100644 (file)
@@ -93,7 +93,7 @@ class InvRef : public ModApiBase {
        // Returns true if the item completely fits into the list
        static int l_room_for_item(lua_State *L);
 
-       // contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false
+       // contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false
        // Returns true if the list contains the given count of the given item name
        static int l_contains_item(lua_State *L);