* `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
======
* 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)`:
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
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.
},
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),
#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),
// 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
Camera::~Camera()
{
+ for (auto node : m_wieldnodes)
+ delete node;
m_wieldmgr->drop();
}
}
}
-// 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;
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;
}
}
}
-
- 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)
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();
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)
}
};
+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};
/*
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.
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;
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;
// 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;
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;
}
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,
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;
}
*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;
// 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) {
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);
!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;
!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);
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();
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;
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();
// 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())
client->setCrack(-1, nodepos);
}
- camera->setDigging(0); // Dig animation
+ camera->setDigging(0, MAINHAND); // Dig animation
}
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;
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);
}
/*
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;
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
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)
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) {};
}
// "" = no prediction
std::string node_placement_prediction;
u8 place_param2;
+ bool has_on_place;
+ bool has_on_secondary_use;
/*
Some helpful methods
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
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)
{
/*
// 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,
// 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) {
// 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) {
<< 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);
}
// 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);
}
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);
#include "constants.h"
#include "network/networkprotocol.h"
#include "util/basic_macros.h"
+#include "util/pointedthing.h"
#include <list>
#include <mutex>
// 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; }
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");
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());
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;
#include "network/networkprotocol.h"
#include "unit_sao.h"
#include "util/numeric.h"
+#include "util/pointedthing.h"
/*
PlayerSAO needs some internals exposed.
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