]> git.lizzy.rs Git - minetest.git/commitdiff
Dual Wielding
authorElias Fleckenstein <eliasfleckenstein@web.de>
Thu, 12 May 2022 11:24:36 +0000 (13:24 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Sat, 23 Jul 2022 18:11:53 +0000 (20:11 +0200)
14 files changed:
doc/lua_api.txt
src/client/camera.cpp
src/client/camera.h
src/client/client.cpp
src/client/client.h
src/client/game.cpp
src/itemdef.cpp
src/itemdef.h
src/network/serverpackethandler.cpp
src/player.cpp
src/player.h
src/script/common/c_content.cpp
src/server/player_sao.cpp
src/server/player_sao.h

index 29198c4278cde8c986beb93ac3ec1cbb058d7d58..b4ad10162d8af320f62f789a506e8c073581e4a6 100644 (file)
@@ -3261,6 +3261,13 @@ Player Inventory lists
 * `hand`: list containing an override for the empty hand
     * Is not created automatically, use `InvRef:set_size`
     * Is only used to enhance the empty hand's tool capabilities
+* `offhand`: list containing the offhand wielded item.
+    * Will be used for placements and secondary uses if the
+        main hand does not have any node_place_prediction, on_place
+        or on_secondary_use callbacks.
+    * Is passed to on_place and on_secondary_use callbacks; make sure
+        mods are aware of the itemstack not neccessarily being
+        located in the main hand.
 
 Colors
 ======
@@ -4707,13 +4714,13 @@ Privileges
 
 Privileges provide a means for server administrators to give certain players
 access to special abilities in the engine, games or mods.
-For example, game moderators may need to travel instantly to any place in the world, 
+For example, game moderators may need to travel instantly to any place in the world,
 this ability is implemented in `/teleport` command which requires `teleport` privilege.
 
 Registering privileges
 ----------------------
 
-A mod can register a custom privilege using `minetest.register_privilege` function 
+A mod can register a custom privilege using `minetest.register_privilege` function
 to give server administrators fine-grained access control over mod functionality.
 
 For consistency and practical reasons, privileges should strictly increase the abilities of the user.
@@ -4722,7 +4729,7 @@ Do not register custom privileges that e.g. restrict the player from certain in-
 Checking privileges
 -------------------
 
-A mod can call `minetest.check_player_privs` to test whether a player has privileges 
+A mod can call `minetest.check_player_privs` to test whether a player has privileges
 to perform an operation.
 Also, when registering a chat command with `minetest.register_chatcommand` a mod can
 declare privileges that the command requires using the `privs` field of the command
@@ -4786,7 +4793,7 @@ Minetest includes the following settings to control behavior of privileges:
 
    * `default_privs`: defines privileges granted to new players.
    * `basic_privs`: defines privileges that can be granted/revoked by players having
-    the `basic_privs` privilege. This can be used, for example, to give 
+    the `basic_privs` privilege. This can be used, for example, to give
     limited moderation powers to selected users.
 
 'minetest' namespace reference
@@ -7948,8 +7955,10 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
         },
 
         on_place = function(itemstack, placer, pointed_thing),
-        -- When the 'place' key was pressed with the item in hand
+        -- When the 'place' key was pressed with the item one of the hands
         -- and a node was pointed at.
+        -- 'itemstack' may be the offhand item iff the main hand has
+        -- no on_place handler and no node_placement_prediction.
         -- Shall place item and return the leftover itemstack
         -- or nil to not modify the inventory.
         -- The placer may be any ObjectRef or nil.
@@ -7957,6 +7966,8 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
 
         on_secondary_use = function(itemstack, user, pointed_thing),
         -- Same as on_place but called when not pointing at a node.
+        -- 'itemstack' may be the offhand item iff the main hand has
+        -- no on_secondary_use handler.
         -- Function must return either nil if inventory shall not be modified,
         -- or an itemstack to replace the original itemstack.
         -- The user may be any ObjectRef or nil.
index df75c52d63488a47780958862797c890b7067e93..5ca4e4908eeaee8c978dabb192b27070794fbdf2 100644 (file)
@@ -45,6 +45,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define WIELDMESH_AMPLITUDE_X 7.0f
 #define WIELDMESH_AMPLITUDE_Y 10.0f
 
+#define HANDS (int i = 0, s = +1; i <= 1; i++, s *= -1) // i is index, s is sign
+
 Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
        m_draw_control(draw_control),
        m_client(client),
@@ -62,9 +64,12 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re
        // all other 3D scene nodes and before the GUI.
        m_wieldmgr = smgr->createNewSceneManager();
        m_wieldmgr->addCameraSceneNode();
-       m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1, false);
-       m_wieldnode->setItem(ItemStack(), m_client);
-       m_wieldnode->drop(); // m_wieldmgr grabbed it
+
+       for HANDS {
+               m_wieldnode[i] = new WieldMeshSceneNode(m_wieldmgr, -1, false);
+               m_wieldnode[i]->setItem(ItemStack(), m_client);
+               m_wieldnode[i]->drop(); // m_wieldmgr grabbed it
+       }
 
        /* TODO: Add a callback function so these can be updated when a setting
         *       changes.  At this point in time it doesn't matter (e.g. /set
@@ -151,12 +156,16 @@ void Camera::step(f32 dtime)
                        m_view_bobbing_fall = -1; // Mark the effect as finished
        }
 
-       bool was_under_zero = m_wield_change_timer < 0;
-       m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
+       for HANDS {
+               bool was_under_zero = m_wield_change_timer[i];
+               m_wield_change_timer[i] = MYMIN(m_wield_change_timer[i] + dtime, 0.125);
+
+               if (m_wield_change_timer[i] >= 0 && was_under_zero) {
+                       m_wieldnode[i]->setItem(m_wield_item_next[i], m_client);
 
-       if (m_wield_change_timer >= 0 && was_under_zero) {
-               m_wieldnode->setItem(m_wield_item_next, m_client);
-               m_wieldnode->setNodeLightColor(m_player_light_color);
+                       if (i == 0)
+                               m_wieldnode[i]->setNodeLightColor(m_player_light_color);
+               }
        }
 
        if (m_view_bobbing_state != 0)
@@ -197,24 +206,27 @@ void Camera::step(f32 dtime)
                }
        }
 
-       if (m_digging_button != -1) {
-               f32 offset = dtime * 3.5f;
-               float m_digging_anim_was = m_digging_anim;
-               m_digging_anim += offset;
-               if (m_digging_anim >= 1)
-               {
-                       m_digging_anim = 0;
-                       m_digging_button = -1;
-               }
-               float lim = 0.15;
-               if(m_digging_anim_was < lim && m_digging_anim >= lim)
-               {
-                       if (m_digging_button == 0) {
-                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT));
-                       } else if(m_digging_button == 1) {
-                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT));
+       for HANDS {
+               if (m_digging_button[i] != -1) {
+                       f32 offset = dtime * 3.5f;
+                       float m_digging_anim_was = m_digging_anim[i];
+                       m_digging_anim[i] += offset;
+                       if (m_digging_anim[i] >= 1)
+                       {
+                               m_digging_anim[i] = 0;
+                               m_digging_button[i] = -1;
+                       }
+                       float lim = 0.15;
+                       if(m_digging_anim_was < lim && m_digging_anim[i] >= lim)
+                       {
+                               if (m_digging_button[i] == 0) {
+                                       m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT));
+                               } else if(m_digging_button[i] == 1) {
+                                       m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT));
+                               }
                        }
                }
+
        }
 }
 
@@ -514,52 +526,68 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
        if (m_arm_inertia)
                addArmInertia(player->getYaw());
 
-       // Position the wielded item
-       //v3f wield_position = v3f(45, -35, 65);
-       v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
-       //v3f wield_rotation = v3f(-100, 120, -100);
-       v3f wield_rotation = v3f(-100, 120, -100);
-       wield_position.Y += fabs(m_wield_change_timer)*320 - 40;
-       if(m_digging_anim < 0.05 || m_digging_anim > 0.5)
-       {
-               f32 frac = 1.0;
-               if(m_digging_anim > 0.5)
-                       frac = 2.0 * (m_digging_anim - 0.5);
-               // This value starts from 1 and settles to 0
-               f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f);
-               //f32 ratiothing2 = pow(ratiothing, 0.5f);
-               f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0;
-               wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f);
-               //wield_position.Z += frac * 5.0 * ratiothing2;
-               wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f);
-               wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f);
-               //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
-               //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
-       }
-       if (m_digging_button != -1)
-       {
-               f32 digfrac = m_digging_anim;
-               wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
-               wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI);
-               wield_position.Z += 25 * 0.5;
-
-               // Euler angles are PURE EVIL, so why not use quaternions?
-               core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
-               core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
-               core::quaternion quat_slerp;
-               quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI));
-               quat_slerp.toEuler(wield_rotation);
-               wield_rotation *= core::RADTODEG;
-       } else {
-               f32 bobfrac = my_modf(m_view_bobbing_anim);
-               wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0;
-               wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
-       }
-       m_wieldnode->setPosition(wield_position);
-       m_wieldnode->setRotation(wield_rotation);
-
        m_player_light_color = player->light_color;
-       m_wieldnode->setNodeLightColor(m_player_light_color);
+
+       for HANDS {
+               // Position the wielded item
+               //v3f wield_position = v3f(45, -35, 65);
+               v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
+               //v3f wield_rotation = v3f(-100, 120, -100);
+               v3f wield_rotation = v3f(-100, 120, -100);
+
+               wield_position.Y += fabs(m_wield_change_timer[i])*320 - 40;
+               if(m_digging_anim[i] < 0.05 || m_digging_anim[i] > 0.5)
+               {
+                       f32 frac = 1.0;
+                       if(m_digging_anim[i] > 0.5)
+                               frac = 2.0 * (m_digging_anim[i] - 0.5);
+                       // This value starts from 1 and settles to 0
+                       f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f);
+                       //f32 ratiothing2 = pow(ratiothing, 0.5f);
+                       f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0;
+                       wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f);
+                       //wield_position.Z += frac * 5.0 * ratiothing2;
+                       wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f);
+                       wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f);
+                       //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
+                       //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
+               }
+               if (m_digging_button[i] != -1)
+               {
+                       f32 digfrac = m_digging_anim[i];
+                       wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
+                       wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI);
+                       wield_position.Z += 25 * 0.5;
+
+                       // Euler angles are PURE EVIL, so why not use quaternions?
+                       core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
+                       //core::quaternion quat_end(v3f(s * 80, 30, s * 100) * core::DEGTORAD);
+                       core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
+                       core::quaternion quat_slerp;
+                       quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI));
+                       quat_slerp.W *= s;
+                       quat_slerp.X *= s;
+                       quat_slerp.toEuler(wield_rotation);
+                       wield_rotation *= core::RADTODEG;
+                       wield_position.X *= s;
+               } else {
+                       f32 bobfrac = my_modf(m_view_bobbing_anim);
+                       wield_position.X *= s;
+                       wield_position.X -= sin(bobfrac*M_PI*2.0+M_PI*i) * 3.0 * s;
+                       wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI+M_PI*i) * 3.0;
+               }
+
+               m_wieldnode[i]->setPosition(wield_position);
+               m_wieldnode[i]->setRotation(wield_rotation);
+
+               m_wieldnode[i]->setNodeLightColor(m_player_light_color);
+
+               if (i == 1) {
+                       m_wieldnode[i]->setVisible(
+                               (m_wield_item_next[i].name != "" && m_wield_change_timer[i] > 0)
+                               || (m_offhand_wield_item_old && m_wield_change_timer[i] < 0));
+               }
+       }
 
        // Set render distance
        updateViewingRange();
@@ -607,21 +635,23 @@ void Camera::updateViewingRange()
        m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS);
 }
 
-void Camera::setDigging(s32 button)
+void Camera::setDigging(s32 button, int hand)
 {
-       if (m_digging_button == -1)
-               m_digging_button = button;
+       if (m_digging_button[hand] == -1)
+               m_digging_button[hand] = button;
 }
 
-void Camera::wield(const ItemStack &item)
+void Camera::wield(const ItemStack &item, int hand)
 {
-       if (item.name != m_wield_item_next.name ||
-                       item.metadata != m_wield_item_next.metadata) {
-               m_wield_item_next = item;
-               if (m_wield_change_timer > 0)
-                       m_wield_change_timer = -m_wield_change_timer;
-               else if (m_wield_change_timer == 0)
-                       m_wield_change_timer = -0.001;
+       if (item.name != m_wield_item_next[hand].name ||
+                       item.metadata != m_wield_item_next[hand].metadata) {
+               if (hand == 1)
+                       m_offhand_wield_item_old = m_wield_item_next[hand].name != "";
+               m_wield_item_next[hand] = item;
+               if (m_wield_change_timer[hand] > 0)
+                       m_wield_change_timer[hand] = -m_wield_change_timer[hand];
+               else if (m_wield_change_timer[hand] == 0)
+                       m_wield_change_timer[hand] = -0.001;
        }
 }
 
index cbf248d9756d8ccb9ee4d9731889d64e44c5e0d2..bfc403cadd10ec829a055e2ff316c05fd6fb4431 100644 (file)
@@ -146,11 +146,12 @@ class Camera
        void updateViewingRange();
 
        // Start digging animation
-       // Pass 0 for left click, 1 for right click
-       void setDigging(s32 button);
+       // button: Pass 0 for left click, 1 for right click
+       // hand: 0 for main hand, 1 for offhand
+       void setDigging(s32 button, int hand);
 
        // Replace the wielded item mesh
-       void wield(const ItemStack &item);
+       void wield(const ItemStack &item, int hand);
 
        // Draw the wielded tool.
        // This has to happen *after* the main scene is drawn.
@@ -196,7 +197,7 @@ class Camera
        scene::ICameraSceneNode *m_cameranode = nullptr;
 
        scene::ISceneManager *m_wieldmgr = nullptr;
-       WieldMeshSceneNode *m_wieldnode = nullptr;
+       WieldMeshSceneNode *m_wieldnode[2] = {nullptr, nullptr};
 
        // draw control
        MapDrawControl& m_draw_control;
@@ -246,15 +247,17 @@ class Camera
        f32 m_view_bobbing_fall = 0.0f;
 
        // Digging animation frame (0 <= m_digging_anim < 1)
-       f32 m_digging_anim = 0.0f;
+       f32 m_digging_anim[2] = {0.0f, 0.0f};
+
        // If -1, no digging animation
        // If 0, left-click digging animation
        // If 1, right-click digging animation
-       s32 m_digging_button = -1;
+       s32 m_digging_button[2] = {-1, -1};
 
        // Animation when changing wielded item
-       f32 m_wield_change_timer = 0.125f;
-       ItemStack m_wield_item_next;
+       f32 m_wield_change_timer[2] = {0.125f, 0.125f};
+       ItemStack m_wield_item_next[2];
+       bool m_offhand_wield_item_old;
 
        CameraMode m_camera_mode = CAMERA_MODE_FIRST;
 
index 31bbf24635d8abd273ac441305b7857df3b841c4..0f6cd36da97f1d78ef8d4cc95bbcfc949faca864 100644 (file)
@@ -1487,6 +1487,8 @@ bool Client::updateWieldedItem()
                list->setModified(false);
        if (auto *list = player->inventory.getList("hand"))
                list->setModified(false);
+       if (auto *list = player->inventory.getList("offhand"))
+               list->setModified(false);
 
        return true;
 }
index bdcc2a3dda0bce9a80b837344250ca4c30243509..b920f7575c32f601a44eef5ee1f06b2078d21782 100644 (file)
@@ -275,6 +275,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
        // Returns true if the inventory of the local player has been
        // updated from the server. If it is true, it is set to false.
        bool updateWieldedItem();
+       bool updateOffhandWieldedItem();
 
        /* InventoryManager interface */
        Inventory* getInventory(const InventoryLocation &loc) override;
index caa83ce136d5e72a7395b613d8b17df531b29468..bc27920fc31a0c5435ff6dc1008ae2f000c118c9 100644 (file)
@@ -746,7 +746,8 @@ class Game {
                        bool look_for_object, const v3s16 &camera_offset);
        void handlePointingAtNothing(const ItemStack &playerItem);
        void handlePointingAtNode(const PointedThing &pointed,
-                       const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
+                       const ItemStack &selected_item, const ItemStack &hand_item,
+                       const ItemStack &place_item, 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,
@@ -3055,7 +3056,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                Calculate what block is the crosshair pointing to
        */
 
-       ItemStack selected_item, hand_item;
+       ItemStack selected_item, hand_item, offhand_item, use_item;
        const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
 
        const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
@@ -3098,6 +3099,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                        !runData.btn_down_for_dig,
                        camera_offset);
 
+       player->getOffhandWieldedItem(&offhand_item, &use_item, itemdef_manager, pointed);
+
        if (pointed != runData.pointed_old)
                infostream << "Pointing at " << pointed.dump() << std::endl;
 
@@ -3161,7 +3164,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                                !client->getScript()->on_item_use(selected_item, pointed)))
                        client->interact(INTERACT_USE, pointed);
        } else if (pointed.type == POINTEDTHING_NODE) {
-               handlePointingAtNode(pointed, selected_item, hand_item, dtime);
+               handlePointingAtNode(pointed, selected_item, hand_item, use_item, dtime);
        } else if (pointed.type == POINTEDTHING_OBJECT) {
                v3f player_position  = player->getPosition();
                bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
@@ -3174,13 +3177,13 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
                        client->getScript()->on_item_use(selected_item, pointed);
        } else if (wasKeyPressed(KeyType::PLACE)) {
-               handlePointingAtNothing(selected_item);
+               handlePointingAtNothing(use_item);
        }
 
        runData.pointed_old = pointed;
 
        if (runData.punching || wasKeyPressed(KeyType::DIG))
-               camera->setDigging(0); // dig animation
+               camera->setDigging(0, 0); // dig animation
 
        input->clearWasKeyPressed();
        input->clearWasKeyReleased();
@@ -3300,7 +3303,8 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem)
 
 
 void Game::handlePointingAtNode(const PointedThing &pointed,
-       const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
+       const ItemStack &selected_item, const ItemStack &hand_item,
+       const ItemStack &place_item, f32 dtime)
 {
        v3s16 nodepos = pointed.node_undersurface;
        v3s16 neighbourpos = pointed.node_abovesurface;
@@ -3338,7 +3342,10 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
                infostream << "Place button pressed while looking at ground" << std::endl;
 
                // Placing animation (always shown for feedback)
-               camera->setDigging(1);
+               if (place_item == selected_item)
+                       camera->setDigging(1, 0);
+               else
+                       camera->setDigging(1, 1);
 
                soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
 
@@ -3346,8 +3353,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
                // make that happen
                // And also set the sound and send the interact
                // But first check for meta formspec and rightclickable
-               auto &def = selected_item.getDefinition(itemdef_manager);
-               bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
+               auto &def = place_item.getDefinition(itemdef_manager);
+               bool placed = nodePlacement(def, place_item, nodepos, neighbourpos,
                        pointed, meta);
 
                if (placed && client->modsLoaded())
@@ -3737,7 +3744,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
                client->setCrack(-1, nodepos);
        }
 
-       camera->setDigging(0);  // Dig animation
+       camera->setDigging(0, 0);  // Dig animation
 }
 
 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
@@ -3897,9 +3904,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
 
        if (client->updateWieldedItem()) {
                // Update wielded tool
-               ItemStack selected_item, hand_item;
+               ItemStack selected_item, hand_item, offhand_item;
                ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
-               camera->wield(tool_item);
+               camera->wield(tool_item, 0);
+               player->getOffhandWieldedItem(&offhand_item, nullptr, itemdef_manager, PointedThing());
+               camera->wield(offhand_item, 1);
        }
 
        /*
index a34805b8e7fab859e0a4c3a353b0b7e5ee8caee9..ca24c6cfa8a17cd497fa024169f8e924bddad897 100644 (file)
@@ -76,6 +76,8 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
        groups = def.groups;
        node_placement_prediction = def.node_placement_prediction;
        place_param2 = def.place_param2;
+       has_on_place = def.has_on_place;
+       has_on_secondary_use = def.has_on_secondary_use;
        sound_place = def.sound_place;
        sound_place_failed = def.sound_place_failed;
        range = def.range;
@@ -120,6 +122,8 @@ void ItemDefinition::reset()
        range = -1;
        node_placement_prediction = "";
        place_param2 = 0;
+       has_on_place = false;
+       has_on_secondary_use = false;
 }
 
 void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
@@ -166,6 +170,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
        os << serializeString16(short_description);
 
        os << place_param2;
+       writeU8(os, has_on_place);
+       writeU8(os, has_on_secondary_use);
 }
 
 void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
@@ -220,6 +226,8 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
                short_description = deSerializeString16(is);
 
                place_param2 = readU8(is); // 0 if missing
+               has_on_place = readU8(is);
+               has_on_secondary_use = readU8(is);
        } catch(SerializationError &e) {};
 }
 
index 035717379f927a058ca841e6325105551142c329..9d9eba2b8dad9d0575ebf456c3488e3ad93a448d 100644 (file)
@@ -87,6 +87,8 @@ struct ItemDefinition
        // "" = no prediction
        std::string node_placement_prediction;
        u8 place_param2;
+       bool has_on_place;
+       bool has_on_secondary_use;
 
        /*
                Some helpful methods
index 4b9de488c76b168cbb3e6f563177fe823c63e22d..25e5d49e249544a80f8d0f3d4454133ac737e5a8 100644 (file)
@@ -911,6 +911,13 @@ static inline void getWieldedItem(const PlayerSAO *playersao, Optional<ItemStack
        playersao->getWieldedItem(&(*ret));
 }
 
+static inline bool getOffhandWieldedItem(const PlayerSAO *playersao, Optional<ItemStack> &offhand, Optional<ItemStack> &place, IItemDefManager *idef, const PointedThing &pointed)
+{
+       offhand = ItemStack();
+       place = ItemStack();
+       return playersao->getOffhandWieldedItem(&(*offhand), &(*place), idef, pointed);
+}
+
 void Server::handleCommand_Interact(NetworkPacket *pkt)
 {
        /*
@@ -1219,15 +1226,16 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        // Place block or right-click object
        case INTERACT_PLACE: {
-               Optional<ItemStack> selected_item;
-               getWieldedItem(playersao, selected_item);
+               Optional<ItemStack> main_item, offhand_item, place_item;
+               getWieldedItem(playersao, main_item);
+               bool use_offhand = getOffhandWieldedItem(playersao, offhand_item, place_item, m_itemdef, pointed);
 
                // Reset build time counter
                if (pointed.type == POINTEDTHING_NODE &&
-                               selected_item->getDefinition(m_itemdef).type == ITEM_NODE)
+                               place_item->getDefinition(m_itemdef).type == ITEM_NODE)
                        getClient(peer_id)->m_time_from_building = 0.0;
 
-               const bool had_prediction = !selected_item->getDefinition(m_itemdef).
+               const bool had_prediction = !place_item->getDefinition(m_itemdef).
                        node_placement_prediction.empty();
 
                if (pointed.type == POINTEDTHING_OBJECT) {
@@ -1242,17 +1250,21 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                                        << pointed_object->getDescription() << std::endl;
 
                        // Do stuff
-                       if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
-                               if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
+                       if (m_script->item_OnSecondaryUse(use_offhand ? offhand_item : main_item, playersao, pointed)) {
+                               if (use_offhand
+                                               ? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
+                                               : (main_item.has_value() && playersao->setWieldedItem(*main_item)))
                                        SendInventory(playersao, true);
                        }
 
                        pointed_object->rightClick(playersao);
-               } else if (m_script->item_OnPlace(selected_item, playersao, pointed)) {
+               } else if (m_script->item_OnPlace(use_offhand ? offhand_item : main_item, playersao, pointed)) {
                        // Placement was handled in lua
 
                        // Apply returned ItemStack
-                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
+                       if (use_offhand
+                                       ? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
+                                       : (main_item.has_value() && playersao->setWieldedItem(*main_item)))
                                SendInventory(playersao, true);
                }
 
@@ -1295,17 +1307,20 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        // Rightclick air
        case INTERACT_ACTIVATE: {
-               Optional<ItemStack> selected_item;
-               getWieldedItem(playersao, selected_item);
+               Optional<ItemStack> main_item, offhand_item, place_item;
+               getWieldedItem(playersao, main_item);
+               bool use_offhand = getOffhandWieldedItem(playersao, offhand_item, place_item, m_itemdef, pointed);
 
                actionstream << player->getName() << " activates "
-                               << selected_item->name << std::endl;
+                               << place_item->name << std::endl;
 
                pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING
 
-               if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
+               if (m_script->item_OnSecondaryUse(use_offhand ? offhand_item : main_item, playersao, pointed)) {
                        // Apply returned ItemStack
-                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
+                       if (use_offhand
+                                       ? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
+                                       : (main_item.has_value() && playersao->setWieldedItem(*main_item)))
                                SendInventory(playersao, true);
                }
 
index 1e064c1dac557d43c92ab9663c9d552f4c3f8e7b..e5a8d3b0a4cbffae20e68c7d1c392fbfcce6eb51 100644 (file)
@@ -114,6 +114,46 @@ ItemStack &Player::getWieldedItem(ItemStack *selected, ItemStack *hand) const
        return (hand && selected->name.empty()) ? *hand : *selected;
 }
 
+bool Player::getOffhandWieldedItem(ItemStack *offhand, ItemStack *place, IItemDefManager *idef, const PointedThing &pointed) const
+{
+       assert(offhand);
+
+       ItemStack main;
+
+       const InventoryList *mlist = inventory.getList("main");
+       const InventoryList *olist = inventory.getList("offhand");
+
+       if (olist)
+               *offhand = olist->getItem(0);
+
+       if (mlist && m_wield_index < mlist->getSize())
+               main = mlist->getItem(m_wield_index);
+
+       const ItemDefinition &main_def = main.getDefinition(idef);
+       const ItemDefinition &offhand_def = offhand->getDefinition(idef);
+       bool main_usable, offhand_usable;
+
+       // figure out which item to use for placements
+
+       if (pointed.type == POINTEDTHING_NODE) {
+               // an item can be used on nodes if it has a place handler or prediction
+               main_usable = main_def.has_on_place || main_def.node_placement_prediction != "";
+               offhand_usable = offhand_def.has_on_place || offhand_def.node_placement_prediction != "";
+       } else {
+               // an item can be used on anything else if it has a secondary use handler
+               main_usable = main_def.has_on_secondary_use;
+               offhand_usable = offhand_def.has_on_secondary_use;
+       }
+
+       // main hand has priority
+       bool use_offhand = offhand_usable && !main_usable;
+
+       if (place)
+               *place = use_offhand ? *offhand : main;
+
+       return use_offhand;
+}
+
 u32 Player::addHud(HudElement *toadd)
 {
        MutexAutoLock lock(m_mutex);
index beca82f66953025ac8a9123bfebf9b9ff24d1cd6..248ae93886f1ffaf71a37116d80c1b9627a9933b 100644 (file)
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "constants.h"
 #include "network/networkprotocol.h"
 #include "util/basic_macros.h"
+#include "util/pointedthing.h"
 #include <list>
 #include <mutex>
 
@@ -187,6 +188,10 @@ class Player
 
        // Returns non-empty `selected` ItemStack. `hand` is a fallback, if specified
        ItemStack &getWieldedItem(ItemStack *selected, ItemStack *hand) const;
+
+       // item currently in secondary hand is returned in `offhand`
+       // item to use for place / secondary_use (either main or offhand) is (optionally) returned in `place`
+       bool getOffhandWieldedItem(ItemStack *offhand, ItemStack *place, IItemDefManager *idef, const PointedThing &pointed) const;
        void setWieldIndex(u16 index);
        u16 getWieldIndex() const { return m_wield_index; }
 
index 1669800256360b743644a2fbedec83e7bac4b187..5c112d1d1012a2274ed34b25575b3dcb5b4aeef4 100644 (file)
@@ -81,6 +81,16 @@ void read_item_definition(lua_State* L, int index,
        def.usable = lua_isfunction(L, -1);
        lua_pop(L, 1);
 
+       lua_pushstring(L, "on_place");
+       lua_rawget(L, index);
+       def.has_on_place = lua_isfunction(L, -1);
+       lua_pop(L, 1);
+
+       lua_pushstring(L, "on_secondary_use");
+       lua_rawget(L, index);
+       def.has_on_secondary_use = lua_isfunction(L, -1);
+       lua_pop(L, 1);
+
        getboolfield(L, index, "liquids_pointable", def.liquids_pointable);
 
        lua_getfield(L, index, "tool_capabilities");
index a58a0397f0bb70b24c5fd9db99ace3127efb5523..7b134b3911cdb0166a455fc9eda532aaa80b2e0c 100644 (file)
@@ -525,6 +525,11 @@ ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
        return m_player->getWieldedItem(selected, hand);
 }
 
+bool PlayerSAO::getOffhandWieldedItem(ItemStack *offhand, ItemStack *place, IItemDefManager *itemdef_manager, PointedThing pointed) const
+{
+       return m_player->getOffhandWieldedItem(offhand, place, itemdef_manager, pointed);
+}
+
 bool PlayerSAO::setWieldedItem(const ItemStack &item)
 {
        InventoryList *mlist = m_player->inventory.getList(getWieldList());
@@ -535,6 +540,16 @@ bool PlayerSAO::setWieldedItem(const ItemStack &item)
        return false;
 }
 
+bool PlayerSAO::setOffhandWieldedItem(const ItemStack &item)
+{
+       InventoryList *olist = m_player->inventory.getList("offhand");
+       if (olist) {
+               olist->changeItem(0, item);
+               return true;
+       }
+       return false;
+}
+
 void PlayerSAO::disconnected()
 {
        m_peer_id = PEER_ID_INEXISTENT;
index 5f48cae67af887c2a28cf55ee134407a2b93f787..8bf64bea7f9f7a2be92334bb2e14f9c63bf39b85 100644 (file)
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "network/networkprotocol.h"
 #include "unit_sao.h"
 #include "util/numeric.h"
+#include "util/pointedthing.h"
 
 /*
        PlayerSAO needs some internals exposed.
@@ -130,7 +131,9 @@ class PlayerSAO : public UnitSAO
        std::string getWieldList() const override { return "main"; }
        u16 getWieldIndex() const override;
        ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const override;
+       bool getOffhandWieldedItem(ItemStack *offhand, ItemStack *place, IItemDefManager *itemdef_manager, PointedThing pointed) const;
        bool setWieldedItem(const ItemStack &item) override;
+       bool setOffhandWieldedItem(const ItemStack &item);
 
        /*
                PlayerSAO-specific