Compare commits

...

8 Commits

Author SHA1 Message Date
Andrey
8d899f69f9
Merge f3fd2f6eaa8d078b6fe667eea5cfbe4e830599ce into 9a1501ae89ffe79c38dbd6756c9e7ed647dd7dc1 2024-06-28 00:14:20 +02:00
grorp
9a1501ae89
CIrrDeviceSDL: Fix numpad key events not having correct KeyInput.Char (#14780)
Allows you to change viewing range using numpad +/- again. This fix also works with the current unreleased version of SDL 3.

The keycodes for numpad keys (both SDL keycodes and Irrlicht keycodes) are not the same as the keycodes for the equivalent non-numpad keys and don't correspond to chars, so I mapped them to chars manually.

Since I think the resolution of https://github.com/minetest/minetest/issues/13770 was "just disable numlock", I made sure to only do this for the numpad number keys if numlock is enabled.
2024-06-27 14:44:44 +02:00
Andrey2470T
f3fd2f6eaa Added 'offhand' parameter to the callbacks, some code cleanups 2024-04-08 11:23:12 +03:00
Andrey2470T
8167c04b83 Force set/get_wielded_item() to access to itemstack of current used hand 2024-04-08 11:16:02 +03:00
Andrey2470T
147e481b2b Some fixes of the merge conflicts 2024-04-08 11:16:02 +03:00
Elias Fleckenstein
a71d47edb6 Add keybind to swap items between hands 2024-04-08 11:16:02 +03:00
Elias Fleckenstein
9e45cc0fd9 Add setting to mirror hands 2024-04-08 11:16:02 +03:00
Lizzy Fleckenstein
4acba9b648 Dual wielding 2024-04-08 11:16:02 +03:00
23 changed files with 691 additions and 271 deletions

@ -318,6 +318,9 @@ view_bobbing_amount (View bobbing factor) float 1.0 0.0 7.9
# For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double.
fall_bobbing_amount (Fall bobbing factor) float 0.03 0.0 100.0
# Draw main hand on left side of screen, offhand on right side.
swap_hands (Swap hands) bool false
[**Camera]
# Field of view in degrees.
@ -2402,6 +2405,9 @@ keymap_fullscreen (Fullscreen key) key KEY_F11
# Key for dropping the currently selected item.
keymap_drop (Drop item key) key KEY_KEY_Q
# Key for swapping items between main hand and offhand.
keymap_swap_offhand (Swap hand items) key KEY_KEY_F
# Key to use view zoom when possible.
keymap_zoom (View zoom key) key KEY_KEY_Z

@ -3666,6 +3666,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.
ItemStack transaction order
---------------------------
@ -9218,19 +9228,21 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- When item is used with the 'punch/mine' key pointing at nothing (air)
},
on_place = function(itemstack, placer, pointed_thing),
-- When the 'place' key was pressed with the item in hand
on_place = function(itemstack, placer, pointed_thing, offhand),
-- When the 'place' key was pressed with the item in 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.
-- 'offhand' is a boolean indicating whether the callback was called
-- from the item of the main hand (false) or the offhand (true).
-- 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.
on_secondary_use = function(itemstack, user, pointed_thing, offhand),
-- 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),

@ -129,9 +129,9 @@ EM_BOOL CIrrDeviceSDL::MouseLeaveCallback(int eventType, const EmscriptenMouseEv
}
#endif
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE irrlichtKey)
{
switch (key) {
switch (irrlichtKey) {
// keys which are known to have safe special character interpretation
// could need changes over time (removals and additions!)
case KEY_RETURN:
@ -189,24 +189,68 @@ bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
}
}
int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock)
{
switch (irrlichtKey) {
// special cases that always return a char regardless of how the SDL keycode
// looks
switch (key) {
case KEY_RETURN:
case KEY_ESCAPE:
return (int)key;
return (int)irrlichtKey;
// This is necessary for keys on the numpad because they don't use the same
// keycodes as their non-numpad versions (whose keycodes correspond to chars),
// but have their own SDL keycodes and their own Irrlicht keycodes (which
// don't correspond to chars).
case KEY_MULTIPLY:
return '*';
case KEY_ADD:
return '+';
case KEY_SUBTRACT:
return '-';
case KEY_DIVIDE:
return '/';
default:
break;
}
if (numlock) {
// Number keys on the numpad are also affected, but we only want them
// to produce number chars when numlock is enabled.
switch (irrlichtKey) {
case KEY_NUMPAD0:
return '0';
case KEY_NUMPAD1:
return '1';
case KEY_NUMPAD2:
return '2';
case KEY_NUMPAD3:
return '3';
case KEY_NUMPAD4:
return '4';
case KEY_NUMPAD5:
return '5';
case KEY_NUMPAD6:
return '6';
case KEY_NUMPAD7:
return '7';
case KEY_NUMPAD8:
return '8';
case KEY_NUMPAD9:
return '9';
default:
break;
}
}
// SDL in-place ORs values with no character representation with 1<<30
// https://wiki.libsdl.org/SDL2/SDLKeycodeLookup
if (assumedChar & (1 << 30))
// This also affects the numpad keys btw.
if (sdlKey & (1 << 30))
return 0;
switch (key) {
switch (irrlichtKey) {
case KEY_PRIOR:
case KEY_NEXT:
case KEY_HOME:
@ -218,7 +262,7 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
case KEY_NUMLOCK:
return 0;
default:
return assumedChar;
return sdlKey;
}
}
@ -825,7 +869,8 @@ bool CIrrDeviceSDL::run()
irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN);
irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0;
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key);
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
postEventFromUser(irrevent);
} break;

@ -273,10 +273,10 @@ class CIrrDeviceSDL : public CIrrDeviceStub
#endif
// Check if a key is a known special character with no side effects on text boxes.
static bool keyIsKnownSpecial(EKEY_CODE key);
static bool keyIsKnownSpecial(EKEY_CODE irrlichtKey);
// Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0).
static int findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key);
static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock);
// Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus.
void resetReceiveTextInputEvents();

@ -45,6 +45,242 @@ 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 frac_part(f32 x)
{
f32 integral_part;
return modff(x, &integral_part);
}
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
}
int WieldNode::getDirection()
{
return g_settings->getBool("swap_hands") ? -m_direction : m_direction;
}
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 get_arm_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) * getDirection();
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 = get_arm_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)
{
int direction = getDirection();
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 *= direction;
quat_slerp.X *= direction;
quat_slerp.toEuler(rot);
rot *= core::RADTODEG;
pos.X *= direction;
} else {
f32 bobfrac = frac_part(view_bobbing_anim);
pos.X *= direction;
pos.X -= sin(bobfrac*M_PI*2.0+M_PI*m_index) * 3.0 * direction;
pos.Y += sin(frac_part(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),
@ -62,9 +298,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
@ -87,6 +323,8 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re
Camera::~Camera()
{
for (auto node : m_wieldnodes)
delete node;
m_wieldmgr->drop();
}
@ -118,15 +356,11 @@ void Camera::notifyFovChange()
}
}
// Returns the fractional part of x
inline f32 my_modf(f32 x)
{
float dummy;
return std::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;
@ -134,14 +368,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;
@ -170,7 +396,7 @@ void Camera::step(f32 dtime)
}
else {
float was = m_view_bobbing_anim;
m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset);
m_view_bobbing_anim = frac_part(m_view_bobbing_anim + offset);
bool step = (was == 0 ||
(was < 0.5f && m_view_bobbing_anim >= 0.5f) ||
(was > 0.5f && m_view_bobbing_anim <= 0.5f));
@ -179,119 +405,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)
@ -386,7 +505,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
if (m_cache_view_bobbing_amount != 0.0f && m_view_bobbing_anim != 0.0f &&
m_camera_mode < CAMERA_MODE_THIRD) {
f32 bobfrac = my_modf(m_view_bobbing_anim * 2);
f32 bobfrac = frac_part(m_view_bobbing_anim * 2);
f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
f32 bobknob = 1.2;
@ -513,46 +632,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(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
v3f wield_rotation = v3f(-100, 120, -100);
wield_position.Y += std::abs(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 = (easeCurve(ratiothing*0.5))*2.0;
wield_position.Y -= frac * 25.0f * std::pow(ratiothing2, 1.7f);
wield_position.X -= frac * 35.0f * std::pow(ratiothing2, 1.1f);
wield_rotation.Y += frac * 70.0f * std::pow(ratiothing2, 1.4f);
}
if (m_digging_button != -1)
{
f32 digfrac = m_digging_anim;
wield_position.X -= 50 * std::sin(std::pow(digfrac, 0.8f) * M_PI);
wield_position.Y += 24 * std::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, std::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 -= std::sin(bobfrac*M_PI*2.0) * 3.0;
wield_position.Y += std::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();
@ -594,22 +677,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)

@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <array>
#include <list>
#include <optional>
#include "player.h"
class LocalPlayer;
struct MapDrawControl;
@ -72,6 +73,48 @@ struct Nametag
}
};
class WieldNode
{
public:
WieldNode(HandIndex index, Client *client, scene::ISceneManager *mgr);
int getDirection();
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};
/*
@ -166,11 +209,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.
@ -219,8 +262,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;
@ -247,12 +291,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;
@ -269,17 +307,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;

@ -1568,6 +1568,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;
}

@ -727,6 +727,7 @@ class Game {
void processItemSelection(u16 *new_playeritem);
void dropSelectedItem(bool single_item = false);
void swapOffhand();
void openInventory();
void openConsole(float scale, const wchar_t *line=NULL);
void toggleFreeMove();
@ -775,9 +776,10 @@ class Game {
const core::line3d<f32> &shootline, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object, const v3s16 &camera_offset);
void handlePointingAtNothing(const ItemStack &playerItem);
void handlePointingAtNothing(HandIndex used_hand);
void handlePointingAtNode(const PointedThing &pointed,
const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
const ItemStack &selected_item, const ItemStack &hand_item,
HandIndex used_hand, 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,
@ -1857,19 +1859,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;
}
@ -2066,6 +2068,8 @@ void Game::processKeyInput()
{
if (wasKeyDown(KeyType::DROP)) {
dropSelectedItem(isKeyDown(KeyType::SNEAK));
} else if (wasKeyDown(KeyType::SWAP_OFFHAND)) {
swapOffhand();
} else if (wasKeyDown(KeyType::AUTOFORWARD)) {
toggleAutoforward();
} else if (wasKeyDown(KeyType::BACKWARD)) {
@ -2245,6 +2249,29 @@ void Game::dropSelectedItem(bool single_item)
}
void Game::swapOffhand()
{
IMoveAction *a = new IMoveAction();
a->count = 0;
a->from_inv.setCurrentPlayer();
a->from_list = "main";
a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
a->to_inv.setCurrentPlayer();
a->to_list = "offhand";
a->to_i = 0;
ItemStack selected;
client->getEnv().getLocalPlayer()->getWieldedItem(&selected, nullptr);
if (selected.name == "") {
std::swap(a->from_list, a->to_list);
std::swap(a->from_i, a->to_i);
}
client->inventoryAction(a);
}
void Game::openInventory()
{
/*
@ -3305,7 +3332,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);
@ -3410,12 +3437,14 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
else
runData.repeat_place_timer = 0;
HandIndex cur_used_hand = player->getCurrentUsedHand(itemdef_manager, pointed);
if (selected_def.usable && isKeyDown(KeyType::DIG)) {
if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
!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, cur_used_hand, 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);
@ -3428,13 +3457,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(cur_used_hand);
}
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();
@ -3549,7 +3578,7 @@ PointedThing Game::updatePointedThing(
}
void Game::handlePointingAtNothing(const ItemStack &playerItem)
void Game::handlePointingAtNothing(HandIndex used_hand)
{
infostream << "Attempted to place item while pointing at nothing" << std::endl;
PointedThing fauxPointed;
@ -3559,7 +3588,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,
HandIndex used_hand, f32 dtime)
{
v3s16 nodepos = pointed.node_undersurface;
v3s16 neighborpos = pointed.node_abovesurface;
@ -3593,11 +3623,14 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
if ((wasKeyPressed(KeyType::PLACE) ||
runData.repeat_place_timer >= m_repeat_place_time) &&
client->checkPrivilege("interact")) {
LocalPlayer *player = client->getEnv().getLocalPlayer();
player->current_used_hand = used_hand;
runData.repeat_place_timer = 0;
infostream << "Place button pressed while looking at ground" << std::endl;
// Placing animation (always shown for feedback)
camera->setDigging(1);
camera->setDigging(1, used_hand);
soundmaker->m_player_rightpunch_sound = SoundSpec();
@ -3605,12 +3638,22 @@ 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,
ItemStack place_item;
if (used_hand == MAINHAND)
player->getWieldedItem(&place_item, nullptr);
else
player->getOffhandWieldedItem(&place_item);
auto &def = place_item.getDefinition(itemdef_manager);
bool placed = nodePlacement(def, place_item, nodepos, neighborpos,
pointed, meta);
if (placed && client->modsLoaded())
client->getScript()->on_placenode(pointed, def);
// Resets the hand index after 'on_place' callback run.
player->current_used_hand = MAINHAND;
}
}
@ -4030,7 +4073,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,
@ -4077,7 +4120,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;
@ -4148,9 +4191,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);
camera->wield(offhand_item, OFFHAND);
}
/*

@ -45,6 +45,7 @@ void KeyCache::populate()
key[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward");
key[KeyType::DROP] = getKeySetting("keymap_drop");
key[KeyType::SWAP_OFFHAND] = getKeySetting("keymap_swap_offhand");
key[KeyType::INVENTORY] = getKeySetting("keymap_inventory");
key[KeyType::CHAT] = getKeySetting("keymap_chat");
key[KeyType::CMD] = getKeySetting("keymap_cmd");

@ -42,6 +42,7 @@ class KeyType
// Other
DROP,
SWAP_OFFHAND,
INVENTORY,
CHAT,
CMD,

@ -147,6 +147,7 @@ void set_default_settings()
settings->setDefault("keymap_dig", "KEY_LBUTTON");
settings->setDefault("keymap_place", "KEY_RBUTTON");
settings->setDefault("keymap_drop", "KEY_KEY_Q");
settings->setDefault("keymap_swap_offhand", "KEY_KEY_F");
settings->setDefault("keymap_zoom", "KEY_KEY_Z");
settings->setDefault("keymap_inventory", "KEY_KEY_I");
settings->setDefault("keymap_aux1", "KEY_KEY_E");
@ -280,6 +281,7 @@ void set_default_settings()
settings->setDefault("enable_clouds", "true");
settings->setDefault("view_bobbing_amount", "1.0");
settings->setDefault("fall_bobbing_amount", "0.03");
settings->setDefault("swap_hands", "false");
settings->setDefault("enable_3d_clouds", "true");
settings->setDefault("cloud_radius", "12");
settings->setDefault("menu_clouds", "true");

@ -58,6 +58,7 @@ enum
GUI_ID_KEY_CONSOLE_BUTTON,
GUI_ID_KEY_SNEAK_BUTTON,
GUI_ID_KEY_DROP_BUTTON,
GUI_ID_KEY_SWAP_OFFHAND_BUTTON,
GUI_ID_KEY_INVENTORY_BUTTON,
GUI_ID_KEY_HOTBAR_PREV_BUTTON,
GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
@ -387,6 +388,7 @@ void GUIKeyChangeMenu::init_keys()
this->add_key(GUI_ID_KEY_JUMP_BUTTON, wstrgettext("Jump"), "keymap_jump");
this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wstrgettext("Sneak"), "keymap_sneak");
this->add_key(GUI_ID_KEY_DROP_BUTTON, wstrgettext("Drop"), "keymap_drop");
this->add_key(GUI_ID_KEY_SWAP_OFFHAND_BUTTON, wstrgettext("Swap hand items"), "keymap_swap_offhand");
this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wstrgettext("Inventory"), "keymap_inventory");
this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON, wstrgettext("Prev. item"), "keymap_hotbar_previous");
this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON, wstrgettext("Next item"), "keymap_hotbar_next");

@ -142,6 +142,8 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
node_placement_prediction = def.node_placement_prediction;
place_param2 = def.place_param2;
wallmounted_rotate_vertical = def.wallmounted_rotate_vertical;
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;
@ -195,6 +197,8 @@ void ItemDefinition::reset()
node_placement_prediction.clear();
place_param2.reset();
wallmounted_rotate_vertical = false;
has_on_place = false;
has_on_secondary_use = false;
touch_interaction = TouchInteraction();
}
@ -252,6 +256,9 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
sound_use.serializeSimple(os, protocol_version);
sound_use_air.serializeSimple(os, protocol_version);
writeU8(os, has_on_place);
writeU8(os, has_on_secondary_use);
os << (u8)place_param2.has_value(); // protocol_version >= 43
if (place_param2)
os << *place_param2;
@ -340,6 +347,9 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
sound_use.deSerializeSimple(is, protocol_version);
sound_use_air.deSerializeSimple(is, protocol_version);
has_on_place = readU8(is);
has_on_secondary_use = readU8(is);
if (is.eof())
throw SerializationError("");

@ -121,6 +121,10 @@ struct ItemDefinition
// "" = no prediction
std::string node_placement_prediction;
std::optional<u8> place_param2;
bool has_on_place;
bool has_on_secondary_use;
bool wallmounted_rotate_vertical;
TouchInteraction touch_interaction;

@ -893,9 +893,9 @@ 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);
f32 max_d = BS * getToolRange(selected_item, hand_item, m_itemdef);
ItemStack selected_item, main_item;
player->getWieldedItem(&selected_item, &main_item);
f32 max_d = BS * getToolRange(selected_item, main_item, m_itemdef);
// Cube diagonal * 1.5 for maximal supported node extents:
// sqrt(3) * 1.5 ≅ 2.6
@ -919,6 +919,12 @@ static inline void getWieldedItem(const PlayerSAO *playersao, std::optional<Item
playersao->getWieldedItem(&(*ret));
}
static inline void getOffhandWieldedItem(const PlayerSAO *playersao, std::optional<ItemStack> &offhand)
{
offhand = ItemStack();
playersao->getOffhandWieldedItem(&(*offhand));
}
void Server::handleCommand_Interact(NetworkPacket *pkt)
{
/*
@ -1159,8 +1165,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Get player's wielded item
// See also: Game::handleDigging
ItemStack selected_item, hand_item;
player->getWieldedItem(&selected_item, &hand_item);
ItemStack selected_item, main_item;
player->getWieldedItem(&selected_item, &main_item);
// Get diggability and expected digging time
DigParams params = getDigParams(m_nodedef->get(n).groups,
@ -1169,7 +1175,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) {
@ -1227,15 +1233,23 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Place block or right-click object
case INTERACT_PLACE: {
std::optional<ItemStack> selected_item;
getWieldedItem(playersao, selected_item);
std::optional<ItemStack> main_item, offhand_item, place_item;
getWieldedItem(playersao, main_item);
getOffhandWieldedItem(playersao, offhand_item);
HandIndex used_hand = playersao->getCurrentUsedHand(m_itemdef, pointed);
if (used_hand == MAINHAND)
place_item = main_item;
else
place_item = offhand_item;
// 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) {
@ -1250,18 +1264,32 @@ 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))
playersao->getPlayer()->current_used_hand = used_hand;
if (m_script->item_OnSecondaryUse(used_hand == MAINHAND ? main_item : offhand_item,
playersao, pointed, (bool)used_hand)) {
if (used_hand == OFFHAND
? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
: (main_item.has_value() && playersao->setWieldedItem(*main_item)))
SendInventory(player, true);
}
pointed_object->rightClick(playersao);
} else if (m_script->item_OnPlace(selected_item, playersao, pointed)) {
// Placement was handled in lua
playersao->getPlayer()->current_used_hand = MAINHAND;
// Apply returned ItemStack
if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
pointed_object->rightClick(playersao);
}
else {
playersao->getPlayer()->current_used_hand = used_hand;
if (m_script->item_OnPlace(used_hand == MAINHAND ? main_item : offhand_item,
playersao, pointed, (bool)used_hand)) {
// Placement was handled in lua
// Apply returned ItemStack
if (used_hand == OFFHAND
? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
: (main_item.has_value() && playersao->setWieldedItem(*main_item)))
SendInventory(player, true);
}
playersao->getPlayer()->current_used_hand = MAINHAND;
}
if (pointed.type != POINTEDTHING_NODE)
@ -1303,19 +1331,31 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Rightclick air
case INTERACT_ACTIVATE: {
std::optional<ItemStack> selected_item;
getWieldedItem(playersao, selected_item);
std::optional<ItemStack> main_item, offhand_item, place_item;
getWieldedItem(playersao, main_item);
getOffhandWieldedItem(playersao, offhand_item);
HandIndex used_hand = playersao->getCurrentUsedHand(m_itemdef, pointed);
if (used_hand == MAINHAND)
place_item = main_item;
else
place_item = offhand_item;
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)) {
playersao->getPlayer()->current_used_hand = used_hand;
if (m_script->item_OnSecondaryUse(used_hand == MAINHAND ? main_item : offhand_item,
playersao, pointed, (bool)used_hand)) {
// Apply returned ItemStack
if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
if (used_hand == OFFHAND
? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
: (main_item.has_value() && playersao->setWieldedItem(*main_item)))
SendInventory(player, true);
}
playersao->getPlayer()->current_used_hand = MAINHAND;
return;
}

@ -41,6 +41,7 @@ Player::Player(const char *name, IItemDefManager *idef):
craft->setWidth(3);
inventory.addList("craftpreview", 1);
inventory.addList("craftresult", 1);
inventory.addList("offhand", 1);
inventory.setModified(false);
// Can be redefined via Lua
@ -95,8 +96,12 @@ ItemStack &Player::getWieldedItem(ItemStack *selected, ItemStack *hand) const
const InventoryList *mlist = inventory.getList("main"); // TODO: Make this generic
const InventoryList *hlist = inventory.getList("hand");
if (mlist && m_wield_index < mlist->getSize())
*selected = mlist->getItem(m_wield_index);
if (current_used_hand == MAINHAND) {
if (mlist && m_wield_index < mlist->getSize())
*selected = mlist->getItem(m_wield_index);
}
else
getOffhandWieldedItem(selected);
if (hand && hlist)
*hand = hlist->getItem(0);
@ -105,6 +110,84 @@ ItemStack &Player::getWieldedItem(ItemStack *selected, ItemStack *hand) const
return (hand && selected->name.empty()) ? *hand : *selected;
}
void Player::getOffhandWieldedItem(ItemStack *offhand) const
{
assert(offhand);
const InventoryList *olist = inventory.getList("offhand");
if (olist)
*offhand = olist->getItem(0);
}
HandIndex Player::getCurrentUsedHand(IItemDefManager *idef, const PointedThing &pointed) const
{
ItemStack main, offhand;
getWieldedItem(&main, nullptr);
getOffhandWieldedItem(&offhand);
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
return (HandIndex)(offhand_usable && !main_usable);
}
/*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);

@ -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>
#include <functional>
@ -34,6 +35,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
#define PLAYERNAME_ALLOWED_CHARS_USER_EXPL "'a' to 'z', 'A' to 'Z', '0' to '9', '-', '_'"
enum HandIndex { MAINHAND = 0, OFFHAND = 1 };
struct PlayerFovSpec
{
f32 fov;
@ -222,6 +225,14 @@ 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
void getOffhandWieldedItem(ItemStack *offhand) const;
HandIndex getCurrentUsedHand(IItemDefManager *idef, const PointedThing &pointed) const;
void setWieldIndex(u16 index);
u16 getWieldIndex() const { return m_wield_index; }
@ -247,6 +258,8 @@ class Player
u32 hud_flags;
s32 hud_hotbar_itemcount;
HandIndex current_used_hand { MAINHAND };
protected:
char m_name[PLAYERNAME_SIZE];
v3f m_speed; // velocity; in BS-space

@ -83,6 +83,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, "pointabilities");

@ -61,7 +61,7 @@ bool ScriptApiItem::item_OnDrop(ItemStack &item,
}
bool ScriptApiItem::item_OnPlace(std::optional<ItemStack> &ret_item,
ServerActiveObject *placer, const PointedThing &pointed)
ServerActiveObject *placer, const PointedThing &pointed, bool offhand)
{
SCRIPTAPI_PRECHECKHEADER
@ -81,7 +81,9 @@ bool ScriptApiItem::item_OnPlace(std::optional<ItemStack> &ret_item,
objectrefGetOrCreate(L, placer);
pushPointedThing(pointed);
PCALL_RES(lua_pcall(L, 3, 1, error_handler));
lua_pushboolean(L, (int)offhand);
PCALL_RES(lua_pcall(L, 4, 1, error_handler));
if (!lua_isnil(L, -1)) {
try {
ret_item = read_item(L, -1, getServer()->idef());
@ -126,7 +128,7 @@ bool ScriptApiItem::item_OnUse(std::optional<ItemStack> &ret_item,
}
bool ScriptApiItem::item_OnSecondaryUse(std::optional<ItemStack> &ret_item,
ServerActiveObject *user, const PointedThing &pointed)
ServerActiveObject *user, const PointedThing &pointed, bool offhand)
{
SCRIPTAPI_PRECHECKHEADER
@ -139,7 +141,9 @@ bool ScriptApiItem::item_OnSecondaryUse(std::optional<ItemStack> &ret_item,
LuaItemStack::create(L, item);
objectrefGetOrCreate(L, user);
pushPointedThing(pointed);
PCALL_RES(lua_pcall(L, 3, 1, error_handler));
lua_pushboolean(L, (int)offhand);
PCALL_RES(lua_pcall(L, 4, 1, error_handler));
if (!lua_isnil(L, -1)) {
try {
ret_item = read_item(L, -1, getServer()->idef());

@ -46,11 +46,11 @@ class ScriptApiItem
bool item_OnDrop(ItemStack &item,
ServerActiveObject *dropper, v3f pos);
bool item_OnPlace(std::optional<ItemStack> &item,
ServerActiveObject *placer, const PointedThing &pointed);
ServerActiveObject *placer, const PointedThing &pointed, bool offhand);
bool item_OnUse(std::optional<ItemStack> &item,
ServerActiveObject *user, const PointedThing &pointed);
bool item_OnSecondaryUse(std::optional<ItemStack> &item,
ServerActiveObject *user, const PointedThing &pointed);
ServerActiveObject *user, const PointedThing &pointed, bool offhand);
bool item_OnCraft(ItemStack &item, ServerActiveObject *user,
const InventoryList *old_craft_grid, const InventoryLocation &craft_inv);
bool item_CraftPredict(ItemStack &item, ServerActiveObject *user,

@ -455,7 +455,7 @@ int ModApiEnv::l_place_node(lua_State *L)
// Place it with a nullptr placer (appears in Lua as nil)
// or the given ObjectRef
bool success = scriptIfaceItem->item_OnPlace(item, placer, pointed);
bool success = scriptIfaceItem->item_OnPlace(item, placer, pointed, false);
lua_pushboolean(L, success);
return 1;
}

@ -576,11 +576,42 @@ ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
return m_player->getWieldedItem(selected, hand);
}
void PlayerSAO::getOffhandWieldedItem(ItemStack *offhand) const
{
m_player->getOffhandWieldedItem(offhand);
}
HandIndex PlayerSAO::getCurrentUsedHand(IItemDefManager *idef, const PointedThing &pointed) const
{
return m_player->getCurrentUsedHand(idef, pointed);
}
bool PlayerSAO::setWieldedItem(const ItemStack &item)
{
InventoryList *mlist = m_player->inventory.getList(getWieldList());
if (mlist) {
mlist->changeItem(m_player->getWieldIndex(), item);
InventoryList *list;
u16 list_index;
if (m_player->current_used_hand == MAINHAND) {
list = m_player->inventory.getList(getWieldList());
list_index = m_player->getWieldIndex();
}
else {
list = m_player->inventory.getList("offhand");
list_index = 0;
}
if (list) {
list->changeItem(list_index, item);
return true;
}
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;

@ -25,6 +25,10 @@ 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"
#include "player.h"
class IItemDefManager;
/*
PlayerSAO needs some internals exposed.
@ -134,7 +138,10 @@ class PlayerSAO : public UnitSAO
std::string getWieldList() const override { return "main"; }
u16 getWieldIndex() const override;
ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const override;
void getOffhandWieldedItem(ItemStack *offhand) const;
HandIndex getCurrentUsedHand(IItemDefManager *idef, const PointedThing &pointed) const;
bool setWieldedItem(const ItemStack &item) override;
bool setOffhandWieldedItem(const ItemStack &item);
/*
PlayerSAO-specific