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