]> git.lizzy.rs Git - minetest.git/commitdiff
Dual wielding
authorLizzy Fleckenstein <eliasfleckenstein@web.de>
Sat, 10 Dec 2022 14:31:22 +0000 (15:31 +0100)
committerLizzy Fleckenstein <eliasfleckenstein@web.de>
Tue, 7 Mar 2023 17:18:18 +0000 (18:18 +0100)
13 files changed:
doc/lua_api.txt
src/client/camera.cpp
src/client/camera.h
src/client/client.cpp
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 0de5d8c7afba4ed007e833db855f8fa71c540b7a..a5ad1cf90cd0a8e168e52c3da86fd9697a9b88f3 100644 (file)
@@ -3332,6 +3332,16 @@ 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.
+    * Is not created automatically, use `InvRef:set_size`
+    * 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.
+    *  The offhand item cannot have its own range or liquids_pointable and
+        will always reuse the characteristics from the hand item.
 
 Colors
 ======
@@ -3510,8 +3520,8 @@ vectors are written like this: `(x, y, z)`:
     * Returns a vector where the function `func` has been applied to each
       component.
 * `vector.combine(v, w, func)`:
-       * Returns a vector where the function `func` has combined both components of `v` and `w`
-         for each component
+    * Returns a vector where the function `func` has combined both components of `v` and `w`
+      for each component
 * `vector.equals(v1, v2)`:
     * Returns a boolean, `true` if the vectors are identical.
 * `vector.sort(v1, v2)`:
@@ -4634,12 +4644,12 @@ Callbacks:
       used for updating the entity state.
 * `on_deactivate(self, removal)`
     * Called when the object is about to get removed or unloaded.
-       * `removal`: boolean indicating whether the object is about to get removed.
-         Calling `object:remove()` on an active object will call this with `removal=true`.
-         The mapblock the entity resides in being unloaded will call this with `removal=false`.
-       * Note that this won't be called if the object hasn't been activated in the first place.
-         In particular, `minetest.clear_objects({mode = "full"})` won't call this,
-         whereas `minetest.clear_objects({mode = "quick"})` might call this.
+    * `removal`: boolean indicating whether the object is about to get removed.
+      Calling `object:remove()` on an active object will call this with `removal=true`.
+      The mapblock the entity resides in being unloaded will call this with `removal=false`.
+    * Note that this won't be called if the object hasn't been activated in the first place.
+      In particular, `minetest.clear_objects({mode = "full"})` won't call this,
+      whereas `minetest.clear_objects({mode = "quick"})` might call this.
 * `on_step(self, dtime, moveresult)`
     * Called on every server tick, after movement and collision processing.
     * `dtime`: elapsed time since last call
@@ -7796,7 +7806,7 @@ Player properties need to be saved manually.
 
         collisionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },  -- default
         selectionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, rotate = false },
-               -- { xmin, ymin, zmin, xmax, ymax, zmax } in nodes from object position.
+        -- { xmin, ymin, zmin, xmax, ymax, zmax } in nodes from object position.
         -- Collision boxes cannot rotate, setting `rotate = true` on it has no effect.
         -- If not set, the selection box copies the collision box, and will also not rotate.
         -- If `rotate = false`, the selection box will not rotate with the object itself, remaining fixed to the axes.
@@ -8216,18 +8226,18 @@ 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 in cases where 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.
         -- default: minetest.item_place
 
         on_secondary_use = function(itemstack, user, pointed_thing),
-        -- Same as on_place but called when not pointing at a node.
-        -- 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.
+        -- Same as on_place but called when not pointing at a node,
+        -- whereas `user` is the same as `placer` above.
         -- default: nil
 
         on_drop = function(itemstack, dropper, pos),
index ba621d15bd7f9232537f76b471a51056780aea10..68bed8ee2708802bd1905a4bd3b112049a2bb6d4 100644 (file)
@@ -46,6 +46,236 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define WIELDMESH_AMPLITUDE_X 7.0f
 #define WIELDMESH_AMPLITUDE_Y 10.0f
 
+// Returns the fractional part of x
+inline f32 my_modf(f32 x)
+{
+       f32 dummy;
+       return modff(x, &dummy);
+}
+
+WieldNode::WieldNode(HandIndex index, Client *client, scene::ISceneManager *mgr) :
+       m_index(index),
+       m_direction(index == MAINHAND ? +1 : -1),
+       m_client(client),
+       m_meshnode(new WieldMeshSceneNode(mgr, -1, false)),
+       m_player_light_color(0xFFFFFFFF)
+{
+       m_meshnode->setItem(ItemStack(), m_client);
+       m_meshnode->drop(); // mgr grabbed it
+}
+
+void WieldNode::step(f32 dtime)
+{
+       bool was_under_zero = m_change_timer < 0;
+       m_change_timer = MYMIN(m_change_timer + dtime, 0.125);
+
+       if (m_change_timer >= 0 && was_under_zero) {
+               m_meshnode->setItem(m_item_next, m_client);
+               m_meshnode->setNodeLightColor(m_player_light_color);
+       }
+
+       if (m_digging_button == -1)
+               return;
+
+       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));
+               }
+       }
+}
+
+static inline v2f dir(const v2f &pos_dist)
+{
+       f32 x = pos_dist.X - WIELDMESH_OFFSET_X;
+       f32 y = pos_dist.Y - WIELDMESH_OFFSET_Y;
+
+       f32 x_abs = std::fabs(x);
+       f32 y_abs = std::fabs(y);
+
+       if (x_abs >= y_abs) {
+               y *= (1.0f / x_abs);
+               x /= x_abs;
+       }
+
+       if (y_abs >= x_abs) {
+               x *= (1.0f / y_abs);
+               y /= y_abs;
+       }
+
+       return v2f(std::fabs(x), std::fabs(y));
+}
+
+void WieldNode::addArmInertia(f32 player_yaw, v3f camera_direction)
+{
+       m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw,
+               -100.0f, 100.0f) / 0.016f) * 0.01f;
+       m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - camera_direction.Y) / 0.016f);
+       f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_offset.X);
+       f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_offset.Y);
+
+       if (m_cam_vel.X > 1.0f || m_cam_vel.Y > 1.0f) {
+               /*
+                       The arm moves relative to the camera speed,
+                       with an acceleration factor.
+               */
+
+               if (m_cam_vel.X > 1.0f) {
+                       if (m_cam_vel.X > m_cam_vel_old.X)
+                               m_cam_vel_old.X = m_cam_vel.X;
+
+                       f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f));
+                       m_offset.X += (m_last_cam_pos.X < player_yaw ? acc_X : -acc_X) * m_direction;
+
+                       if (m_last_cam_pos.X != player_yaw)
+                               m_last_cam_pos.X = player_yaw;
+
+                       m_offset.X = rangelim(m_offset.X,
+                               WIELDMESH_OFFSET_X - (WIELDMESH_AMPLITUDE_X * 0.5f),
+                               WIELDMESH_OFFSET_X + (WIELDMESH_AMPLITUDE_X * 0.5f));
+               }
+
+               if (m_cam_vel.Y > 1.0f) {
+                       if (m_cam_vel.Y > m_cam_vel_old.Y)
+                               m_cam_vel_old.Y = m_cam_vel.Y;
+
+                       f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f));
+                       m_offset.Y +=
+                               m_last_cam_pos.Y > camera_direction.Y ? acc_Y : -acc_Y;
+
+                       if (m_last_cam_pos.Y != camera_direction.Y)
+                               m_last_cam_pos.Y = camera_direction.Y;
+
+                       m_offset.Y = rangelim(m_offset.Y,
+                               WIELDMESH_OFFSET_Y - (WIELDMESH_AMPLITUDE_Y * 0.5f),
+                               WIELDMESH_OFFSET_Y + (WIELDMESH_AMPLITUDE_Y * 0.5f));
+               }
+
+               m_arm_dir = dir(m_offset);
+       } else {
+               /*
+                       Now the arm gets back to its default position when the camera stops,
+                       following a vector, with a smooth deceleration factor.
+               */
+
+               f32 dec_X = 0.35f * (std::min(15.0f, m_cam_vel_old.X) * (1.0f +
+                       (1.0f - m_arm_dir.X))) * (gap_X / 20.0f);
+
+               f32 dec_Y = 0.25f * (std::min(15.0f, m_cam_vel_old.Y) * (1.0f +
+                       (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f);
+
+               if (gap_X < 0.1f)
+                       m_cam_vel_old.X = 0.0f;
+
+               m_offset.X -=
+                       m_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X;
+
+               if (gap_Y < 0.1f)
+                       m_cam_vel_old.Y = 0.0f;
+
+               m_offset.Y -=
+                       m_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y;
+       }
+}
+
+void WieldNode::update(video::SColor player_light_color, f32 view_bobbing_anim, f32 tool_reload_ratio)
+{
+       m_player_light_color = player_light_color;
+
+       // Position the wielded item
+       //v3f pos = v3f(45, -35, 65);
+       v3f pos = v3f(m_offset.X, m_offset.Y, 65);
+       //v3f rot = v3f(-100, 120, -100);
+       v3f rot = v3f(-100, 120, -100);
+
+       if (m_index == OFFHAND)
+               tool_reload_ratio = 1.0f;
+
+       pos.Y += fabs(m_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;
+               pos.Y -= frac * 25.0 * pow(ratiothing2, 1.7f);
+               //rot.Z += frac * 5.0 * ratiothing2;
+               pos.X -= frac * 35.0 * pow(ratiothing2, 1.1f);
+               rot.Y += frac * 70.0 * pow(ratiothing2, 1.4f);
+               //rot.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
+               //rot.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
+       }
+       if (m_digging_button != -1)
+       {
+               f32 digfrac = m_digging_anim;
+               pos.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
+               pos.Y += 24 * sin(digfrac * 1.8 * M_PI);
+               pos.Z += 25 * 0.5;
+
+               // Euler angles are PURE EVIL, so why not use quaternions?
+               core::quaternion quat_begin(rot * 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 *= m_direction;
+               quat_slerp.X *= m_direction;
+               quat_slerp.toEuler(rot);
+               rot *= core::RADTODEG;
+               pos.X *= m_direction;
+       } else {
+               f32 bobfrac = my_modf(view_bobbing_anim);
+               pos.X *= m_direction;
+               pos.X -= sin(bobfrac*M_PI*2.0+M_PI*m_index) * 3.0 * m_direction;
+               pos.Y += sin(my_modf(bobfrac*2.0)*M_PI+M_PI*m_index) * 3.0;
+       }
+
+       m_meshnode->setPosition(pos);
+       m_meshnode->setRotation(rot);
+
+       m_meshnode->setNodeLightColor(m_player_light_color);
+
+       if (m_index == OFFHAND) {
+               m_meshnode->setVisible(
+                       m_change_timer > 0 ? !m_item_next.name.empty() : m_item_old);
+       }
+
+}
+
+void WieldNode::setDigging(s32 button)
+{
+       if (m_digging_button == -1)
+               m_digging_button = button;
+}
+
+void WieldNode::wield(const ItemStack &item)
+{
+       if (item.name == m_item_next.name &&
+                       item.metadata == m_item_next.metadata)
+               return;
+
+       m_item_old = m_item_next.name != "";
+       m_item_next = item;
+       if (m_change_timer > 0)
+               m_change_timer = -m_change_timer;
+       else if (m_change_timer == 0)
+               m_change_timer = -0.001;
+}
+
 Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
        m_draw_control(draw_control),
        m_client(client),
@@ -63,9 +293,9 @@ 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
+
+       m_wieldnodes[MAINHAND] = new WieldNode(MAINHAND, m_client, m_wieldmgr);
+       m_wieldnodes[ OFFHAND] = new WieldNode( OFFHAND, m_client, m_wieldmgr);
 
        /* 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
@@ -88,6 +318,8 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re
 
 Camera::~Camera()
 {
+       for (auto node : m_wieldnodes)
+               delete node;
        m_wieldmgr->drop();
 }
 
@@ -136,15 +368,11 @@ void Camera::notifyFovChange()
        }
 }
 
-// Returns the fractional part of x
-inline f32 my_modf(f32 x)
-{
-       double dummy;
-       return modf(x, &dummy);
-}
-
 void Camera::step(f32 dtime)
 {
+       for (auto node : m_wieldnodes)
+               node->step(dtime);
+
        if(m_view_bobbing_fall > 0)
        {
                m_view_bobbing_fall -= 3 * dtime;
@@ -152,14 +380,6 @@ 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);
-
-       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 (m_view_bobbing_state != 0)
        {
                //f32 offset = dtime * m_view_bobbing_speed * 0.035;
@@ -197,119 +417,12 @@ 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));
-                       }
-               }
-       }
-}
-
-static inline v2f dir(const v2f &pos_dist)
-{
-       f32 x = pos_dist.X - WIELDMESH_OFFSET_X;
-       f32 y = pos_dist.Y - WIELDMESH_OFFSET_Y;
-
-       f32 x_abs = std::fabs(x);
-       f32 y_abs = std::fabs(y);
-
-       if (x_abs >= y_abs) {
-               y *= (1.0f / x_abs);
-               x /= x_abs;
-       }
-
-       if (y_abs >= x_abs) {
-               x *= (1.0f / y_abs);
-               y /= y_abs;
-       }
-
-       return v2f(std::fabs(x), std::fabs(y));
 }
 
 void Camera::addArmInertia(f32 player_yaw)
 {
-       m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw,
-               -100.0f, 100.0f) / 0.016f) * 0.01f;
-       m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - m_camera_direction.Y) / 0.016f);
-       f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_wieldmesh_offset.X);
-       f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_wieldmesh_offset.Y);
-
-       if (m_cam_vel.X > 1.0f || m_cam_vel.Y > 1.0f) {
-               /*
-                   The arm moves relative to the camera speed,
-                   with an acceleration factor.
-               */
-
-               if (m_cam_vel.X > 1.0f) {
-                       if (m_cam_vel.X > m_cam_vel_old.X)
-                               m_cam_vel_old.X = m_cam_vel.X;
-
-                       f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f));
-                       m_wieldmesh_offset.X += m_last_cam_pos.X < player_yaw ? acc_X : -acc_X;
-
-                       if (m_last_cam_pos.X != player_yaw)
-                               m_last_cam_pos.X = player_yaw;
-
-                       m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X,
-                               WIELDMESH_OFFSET_X - (WIELDMESH_AMPLITUDE_X * 0.5f),
-                               WIELDMESH_OFFSET_X + (WIELDMESH_AMPLITUDE_X * 0.5f));
-               }
-
-               if (m_cam_vel.Y > 1.0f) {
-                       if (m_cam_vel.Y > m_cam_vel_old.Y)
-                               m_cam_vel_old.Y = m_cam_vel.Y;
-
-                       f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f));
-                       m_wieldmesh_offset.Y +=
-                               m_last_cam_pos.Y > m_camera_direction.Y ? acc_Y : -acc_Y;
-
-                       if (m_last_cam_pos.Y != m_camera_direction.Y)
-                               m_last_cam_pos.Y = m_camera_direction.Y;
-
-                       m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y,
-                               WIELDMESH_OFFSET_Y - (WIELDMESH_AMPLITUDE_Y * 0.5f),
-                               WIELDMESH_OFFSET_Y + (WIELDMESH_AMPLITUDE_Y * 0.5f));
-               }
-
-               m_arm_dir = dir(m_wieldmesh_offset);
-       } else {
-               /*
-                   Now the arm gets back to its default position when the camera stops,
-                   following a vector, with a smooth deceleration factor.
-               */
-
-               f32 dec_X = 0.35f * (std::min(15.0f, m_cam_vel_old.X) * (1.0f +
-                       (1.0f - m_arm_dir.X))) * (gap_X / 20.0f);
-
-               f32 dec_Y = 0.25f * (std::min(15.0f, m_cam_vel_old.Y) * (1.0f +
-                       (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f);
-
-               if (gap_X < 0.1f)
-                       m_cam_vel_old.X = 0.0f;
-
-               m_wieldmesh_offset.X -=
-                       m_wieldmesh_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X;
-
-               if (gap_Y < 0.1f)
-                       m_cam_vel_old.Y = 0.0f;
-
-               m_wieldmesh_offset.Y -=
-                       m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y;
-       }
+       for (auto node : m_wieldnodes)
+               node->addArmInertia(player_yaw, m_camera_direction);
 }
 
 void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
@@ -522,52 +635,10 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
        if (m_arm_inertia)
                addArmInertia(yaw);
 
-       // 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 (auto node : m_wieldnodes)
+               node->update(m_player_light_color, m_view_bobbing_anim, tool_reload_ratio);
 
        // Set render distance
        updateViewingRange();
@@ -615,22 +686,14 @@ void Camera::updateViewingRange()
        m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS);
 }
 
-void Camera::setDigging(s32 button)
+void Camera::setDigging(s32 button, HandIndex hand)
 {
-       if (m_digging_button == -1)
-               m_digging_button = button;
+       m_wieldnodes[hand]->setDigging(button);
 }
 
-void Camera::wield(const ItemStack &item)
+void Camera::wield(const ItemStack &item, HandIndex 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;
-       }
+       m_wieldnodes[hand]->wield(item);
 }
 
 void Camera::drawWieldedTool(irr::core::matrix4* translation)
index 1018af55a7baae93b99a4b7c290ec273d7617eda..ae27a5e423c423be3c55b5e8559a2ea1c68406c4 100644 (file)
@@ -71,6 +71,49 @@ struct Nametag
        }
 };
 
+enum HandIndex { MAINHAND = 0, OFFHAND = 1 };
+
+class WieldNode
+{
+public:
+       WieldNode(HandIndex index, Client *client, scene::ISceneManager *mgr);
+       void step(f32 dtime);
+       void addArmInertia(f32 player_yaw, v3f camera_direction);
+       void update(video::SColor player_light_color, f32 view_bobbing_anim, f32 tool_reload_ratio);
+       void setDigging(s32 button);
+       void wield(const ItemStack &item);
+
+private:
+       HandIndex m_index;
+       int m_direction;
+
+       Client *m_client;
+       WieldMeshSceneNode *m_meshnode = nullptr;
+
+       // Digging animation frame (0 <= m_digging_anim < 1)
+       f32 m_digging_anim = 0.0f;
+
+       // If -1, no digging animation
+       // If 0, left-click digging animation
+       // If 1, right-click digging animation
+       s32 m_digging_button = -1;
+
+       // Animation when changing wielded item
+       f32 m_change_timer = 0.125f;
+       ItemStack m_item_next;
+       bool m_item_old = false;
+
+       // Last known light color of the player
+       video::SColor m_player_light_color;
+
+       // Arm inertia
+       v2f m_offset = v2f(55.0f, -35.0f);
+       v2f m_arm_dir;
+       v2f m_cam_vel;
+       v2f m_cam_vel_old;
+       v2f m_last_cam_pos;
+};
+
 enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT};
 
 /*
@@ -165,11 +208,11 @@ 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
+       void setDigging(s32 button, HandIndex hand);
 
        // Replace the wielded item mesh
-       void wield(const ItemStack &item);
+       void wield(const ItemStack &item, HandIndex hand);
 
        // Draw the wielded tool.
        // This has to happen *after* the main scene is drawn.
@@ -218,8 +261,9 @@ class Camera
        scene::ISceneNode *m_headnode = nullptr;
        scene::ICameraSceneNode *m_cameranode = nullptr;
 
+       WieldNode *m_wieldnodes[2];
+
        scene::ISceneManager *m_wieldmgr = nullptr;
-       WieldMeshSceneNode *m_wieldnode = nullptr;
 
        // draw control
        MapDrawControl& m_draw_control;
@@ -246,12 +290,6 @@ class Camera
        bool m_fov_transition_active = false;
        f32 m_fov_diff, m_transition_time;
 
-       v2f m_wieldmesh_offset = v2f(55.0f, -35.0f);
-       v2f m_arm_dir;
-       v2f m_cam_vel;
-       v2f m_cam_vel_old;
-       v2f m_last_cam_pos;
-
        // Field of view and aspect ratio stuff
        f32 m_aspect = 1.0f;
        f32 m_fov_x = 1.0f;
@@ -268,17 +306,6 @@ class Camera
        // Fall view bobbing
        f32 m_view_bobbing_fall = 0.0f;
 
-       // Digging animation frame (0 <= m_digging_anim < 1)
-       f32 m_digging_anim = 0.0f;
-       // If -1, no digging animation
-       // If 0, left-click digging animation
-       // If 1, right-click digging animation
-       s32 m_digging_button = -1;
-
-       // Animation when changing wielded item
-       f32 m_wield_change_timer = 0.125f;
-       ItemStack m_wield_item_next;
-
        CameraMode m_camera_mode = CAMERA_MODE_FIRST;
 
        f32 m_cache_fall_bobbing_amount;
index 6f2647d503a795acb202f05b983796128ac06f38..8760b416da43c922ecbdd6f9d170db6813ebba9b 100644 (file)
@@ -1535,6 +1535,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 edd0039e9240c259f7254b911164bf94dc6842fa..e31db44a831768ee47532ca630c060ff274cfe55 100644 (file)
@@ -841,7 +841,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,
@@ -1833,19 +1834,19 @@ inline bool Game::handleCallbacks()
 
        if (g_gamecallback->changepassword_requested) {
                (new GUIPasswordChange(guienv, guiroot, -1,
-                                      &g_menumgr, client, texture_src))->drop();
+                                          &g_menumgr, client, texture_src))->drop();
                g_gamecallback->changepassword_requested = false;
        }
 
        if (g_gamecallback->changevolume_requested) {
                (new GUIVolumeChange(guienv, guiroot, -1,
-                                    &g_menumgr, texture_src))->drop();
+                                        &g_menumgr, texture_src))->drop();
                g_gamecallback->changevolume_requested = false;
        }
 
        if (g_gamecallback->keyconfig_requested) {
                (new GUIKeyChangeMenu(guienv, guiroot, -1,
-                                     &g_menumgr, texture_src))->drop();
+                                         &g_menumgr, texture_src))->drop();
                g_gamecallback->keyconfig_requested = false;
        }
 
@@ -2149,7 +2150,7 @@ void Game::processItemSelection(u16 *new_playeritem)
        *new_playeritem = player->getWieldIndex();
        s32 wheel = input->getMouseWheel();
        u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
-                   player->hud_hotbar_itemcount - 1);
+                       player->hud_hotbar_itemcount - 1);
 
        s32 dir = wheel;
 
@@ -3175,9 +3176,9 @@ void Game::updateSound(f32 dtime)
        // Update sound listener
        v3s16 camera_offset = camera->getOffset();
        sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
-                             v3f(0, 0, 0), // velocity
-                             camera->getDirection(),
-                             camera->getCameraNode()->getUpVector());
+                                 v3f(0, 0, 0), // velocity
+                                 camera->getDirection(),
+                                 camera->getCameraNode()->getUpVector());
 
        bool mute_sound = g_settings->getBool("mute_sound");
        if (mute_sound) {
@@ -3219,7 +3220,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, place_item;
        const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
 
        const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
@@ -3260,6 +3261,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                        !runData.btn_down_for_dig,
                        camera_offset);
 
+       player->getOffhandWieldedItem(&offhand_item, &place_item, itemdef_manager, pointed);
+
        if (pointed != runData.pointed_old)
                infostream << "Pointing at " << pointed.dump() << std::endl;
 
@@ -3325,7 +3328,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, place_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);
@@ -3338,13 +3341,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(place_item);
        }
 
        runData.pointed_old = pointed;
 
        if (runData.punching || wasKeyPressed(KeyType::DIG))
-               camera->setDigging(0); // dig animation
+               camera->setDigging(0, MAINHAND); // dig animation
 
        input->clearWasKeyPressed();
        input->clearWasKeyReleased();
@@ -3468,7 +3471,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 neighborpos = pointed.node_abovesurface;
@@ -3506,7 +3510,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, MAINHAND);
+               else
+                       camera->setDigging(1, OFFHAND);
 
                soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
 
@@ -3514,8 +3521,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, neighborpos,
+               auto &def = place_item.getDefinition(itemdef_manager);
+               bool placed = nodePlacement(def, place_item, nodepos, neighborpos,
                        pointed, meta);
 
                if (placed && client->modsLoaded())
@@ -3907,7 +3914,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
                client->setCrack(-1, nodepos);
        }
 
-       camera->setDigging(0);  // Dig animation
+       camera->setDigging(0, MAINHAND);  // Dig animation
 }
 
 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
@@ -3951,7 +3958,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                direct_brightness = client->getEnv().getClientMap()
                                .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
                                        daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
-                                   / 255.0;
+                                       / 255.0;
        }
 
        float time_of_day_smooth = runData.time_of_day_smooth;
@@ -4073,9 +4080,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, MAINHAND);
+               player->getOffhandWieldedItem(&offhand_item, nullptr, itemdef_manager, PointedThing());
+               camera->wield(offhand_item, OFFHAND);
        }
 
        /*
index 4d695083515e640780f6a822f945a53c3da42d78..e7e4948a3eaea64c24e89643eac57b6f42e870bb 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;
        sound_use = def.sound_use;
@@ -124,6 +126,8 @@ void ItemDefinition::reset()
        range = -1;
        node_placement_prediction.clear();
        place_param2 = 0;
+       has_on_place = false;
+       has_on_secondary_use = false;
 }
 
 void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
@@ -173,6 +177,9 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
 
        sound_use.serialize(os, protocol_version);
        sound_use_air.serialize(os, protocol_version);
+
+       writeU8(os, has_on_place);
+       writeU8(os, has_on_secondary_use);
 }
 
 void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
@@ -230,6 +237,9 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
 
                sound_use.deSerialize(is, protocol_version);
                sound_use_air.deSerialize(is, protocol_version);
+
+               has_on_place = readU8(is);
+               has_on_secondary_use = readU8(is);
        } catch(SerializationError &e) {};
 }
 
index 3bb27559ecedf3353d4e98d5762c7b7371186527..3dbf65d200d152102cddd24f58cef92747e83b7b 100644 (file)
@@ -88,6 +88,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 89e2e2a1cd8f7bc2a61a4b85df274a7f55a6cbdb..1e49538dc863a9babddd44952e9d3209b46b3fe3 100644 (file)
@@ -887,10 +887,10 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt)
 
 bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what)
 {
-       ItemStack selected_item, hand_item;
-       player->getWieldedItem(&selected_item, &hand_item);
+       ItemStack selected_item, main_item;
+       player->getWieldedItem(&selected_item, &main_item);
        f32 max_d = BS * getToolRange(selected_item.getDefinition(m_itemdef),
-                       hand_item.getDefinition(m_itemdef));
+                       main_item.getDefinition(m_itemdef));
 
        // Cube diagonal * 1.5 for maximal supported node extents:
        // sqrt(3) * 1.5 ≅ 2.6
@@ -914,6 +914,14 @@ 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)
 {
        /*
@@ -1154,8 +1162,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
                        // Get player's wielded item
                        // See also: Game::handleDigging
-                       ItemStack selected_item, hand_item;
-                       playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item);
+                       ItemStack selected_item, main_item;
+                       playersao->getPlayer()->getWieldedItem(&selected_item, &main_item);
 
                        // Get diggability and expected digging time
                        DigParams params = getDigParams(m_nodedef->get(n).groups,
@@ -1164,7 +1172,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                        // If can't dig, try hand
                        if (!params.diggable) {
                                params = getDigParams(m_nodedef->get(n).groups,
-                                       &hand_item.getToolCapabilities(m_itemdef));
+                                       &main_item.getToolCapabilities(m_itemdef));
                        }
                        // If can't dig, ignore dig
                        if (!params.diggable) {
@@ -1222,15 +1230,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) {
@@ -1245,17 +1254,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);
                }
 
@@ -1298,17 +1311,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 8742454d2962dd751a952e8e3f2d6dbed2a03430..044f2cc26adcecd7436b4b57c89053c6ff9bc957 100644 (file)
@@ -115,6 +115,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 7c8077d38728fd5892bbb4a19d4d9850a286bf90..42d6af3f0377d992e27a0e9f06f7297b8b7dcaf3 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>
 
@@ -200,6 +201,13 @@ 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`
+       // return value: whether to use main or offhand for placing
+       bool getOffhandWieldedItem(ItemStack *offhand, ItemStack *place,
+                       IItemDefManager *idef, const PointedThing &pointed) const;
+
        void setWieldIndex(u16 index);
        u16 getWieldIndex() const { return m_wield_index; }
 
index 4be6457d8eb3f6536eed8188806a9b9f7cf897bd..8b03f091044e8bb0a0d5fa1559dee9cbca98ffcf 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 9aa7ce39fc405c5af6b3d93ad5a440ec8f6b41fd..16983ef4d024e7dac4569dbebd015090553c3529 100644 (file)
@@ -531,6 +531,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());
@@ -541,6 +546,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 bd190d323ad1575220bb2409c86e1071f6853a19..eeaab6370829039d804fd79af7438252f9decfef 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