diff --git a/builtin/game/register.lua b/builtin/game/register.lua index d6ada6920..506a2fbee 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -618,6 +618,7 @@ core.registered_allow_player_inventory_actions, core.register_allow_player_inven core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration() core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration() core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_registration() +core.registered_on_hotbar_slot_selected, core.register_on_hotbar_slot_selected = make_registration() -- A bunch of registrations are read by the C++ side once on env init, so we cannot -- allow them to change afterwards (see s_env.cpp). diff --git a/doc/lua_api.md b/doc/lua_api.md index afb44d489..f7fba3bbd 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6331,6 +6331,14 @@ Call these functions only at load time! The set is a table where the keys are hashes and the values are `true`. * `modified_block_count` is the number of entries in the set. * Note: callbacks must be registered at mod load time. +* `core.register_on_hotbar_slot_selected(function(player, inventory, list_name, hotbar_slot, prev_hotbar_slot, is_from_scrolling))` + * Called when the player selects a hotbar slot, including re-selecting current slot + * `player`: `ObjectRef` - Player that selected their hotbar slot + * `inventory`: type `InvRef` + * `list_name`: string - the name of the hotbar list + * `hotbar_slot`: integer - the newly selected hotbar slot + * `prev_hotbar_slot`: integer - the previously selected hotbar slot, can be the same as `hotbar_slot` if the player selected the same slot + * `is_from_scrolling`: boolean - true if the input came from a scroll action, or the prev/next slot input keys Setting-related --------------- @@ -7890,6 +7898,7 @@ For historical reasons, the use of an -s suffix in these names is inconsistent. * `core.registered_on_modchannel_message` * `core.registered_on_liquid_transformed` * `core.registered_on_mapblocks_changed` +* `core.registered_on_hotbar_slot_selected` Class reference =============== diff --git a/src/client/game.cpp b/src/client/game.cpp index f340f98e4..73e212086 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2036,6 +2036,9 @@ void Game::processItemSelection(u16 *new_playeritem) if (max_item == 0) return; max_item -= 1; + u16 prev_playeritem = *new_playeritem; + bool player_input_happened = false; + bool change_was_from_scrolling = false; /* Item selection using mouse wheel */ @@ -2053,6 +2056,10 @@ void Game::processItemSelection(u16 *new_playeritem) if (wasKeyDown(KeyType::HOTBAR_PREV)) dir = 1; + if (dir != 0) { + player_input_happened = true; + change_was_from_scrolling = true; + } if (dir < 0) *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; else if (dir > 0) @@ -2064,18 +2071,37 @@ void Game::processItemSelection(u16 *new_playeritem) for (u16 i = 0; i <= max_item; i++) { if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) { *new_playeritem = i; + player_input_happened = true; + change_was_from_scrolling = false; break; } } if (g_touchcontrols) { std::optional selection = g_touchcontrols->getHotbarSelection(); - if (selection) + if (selection) { *new_playeritem = *selection; + player_input_happened = true; + change_was_from_scrolling = false; + } } // Clamp selection again in case it wasn't changed but max_item was *new_playeritem = MYMIN(*new_playeritem, max_item); + + if (player_input_happened) { + if (prev_playeritem != *new_playeritem) { + client->setPlayerItem(*new_playeritem); + } + // notify server of hotbar item selected + IHotbarSlotSelectedAction* a = new IHotbarSlotSelectedAction(); + a->inv.setCurrentPlayer(); + a->list = "main"; + a->selected_slot = *new_playeritem; + a->prev_selected_slot = prev_playeritem; + a->from_scroll = change_was_from_scrolling; + client->inventoryAction(a); + } } diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 9704da7d0..83afcd903 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -104,6 +104,8 @@ InventoryAction *InventoryAction::deSerialize(std::istream &is) a = new IDropAction(is); } else if (type == "Craft") { a = new ICraftAction(is); + } else if (type == "HotbarSlotSelected") { + a = new IHotbarSlotSelectedAction(is); } return a; @@ -1017,3 +1019,54 @@ bool getCraftingResult(Inventory *inv, ItemStack &result, return found; } +/* + IHotbarSlotSelectedAction +*/ + +IHotbarSlotSelectedAction::IHotbarSlotSelectedAction(std::istream& is) +{ + std::string ts; + + std::getline(is, ts, ' '); + inv.deSerialize(ts); + + std::getline(is, list, ' '); + + std::getline(is, ts, ' '); + selected_slot = stoi(ts); + + std::getline(is, ts, ' '); + prev_selected_slot = stoi(ts); + + std::getline(is, ts, ' '); + from_scroll = stoi(ts); +} + +void IHotbarSlotSelectedAction::apply(InventoryManager* mgr, ServerActiveObject* player, IGameDef* gamedef) +{ + Inventory* inv_hotbar = mgr->getInventory(inv); + + if (!inv_hotbar) { + warningstream << "IHotbarSlotSelectedAction::apply(): FAIL: source inventory not found: " + << "inv=\"" << inv.dump() << "\"" << std::endl; + return; + } + + InventoryList* list_wield = inv_hotbar->getList(list); + + /* + If the list doesn't exist + */ + if (!list_wield) { + warningstream << "IHotbarSlotSelectedAction::apply(): FAIL: source list not found: " + << "list=\"" << list << "\"" << std::endl; + return; + } + ServerScripting* sa = PLAYER_TO_SA(player); + sa->on_hotbar_slot_selected(player, inv, list, selected_slot, prev_selected_slot, from_scroll); +} + +void IHotbarSlotSelectedAction::clientApply(InventoryManager* mgr, IGameDef* gamedef) +{ + // Optional operation that is run on the client to make lag less apparent. +} diff --git a/src/inventorymanager.h b/src/inventorymanager.h index df559629d..266b8b733 100644 --- a/src/inventorymanager.h +++ b/src/inventorymanager.h @@ -109,7 +109,8 @@ public: enum class IAction : u16 { Move, Drop, - Craft + Craft, + HotbarSlotSelected, }; struct InventoryAction @@ -247,3 +248,35 @@ struct ICraftAction : public InventoryAction bool getCraftingResult(Inventory *inv, ItemStack &result, std::vector &output_replacements, bool decrementInput, IGameDef *gamedef); + +struct IHotbarSlotSelectedAction : public InventoryAction +{ + InventoryLocation inv; + std::string list; + s16 selected_slot = -1; + s16 prev_selected_slot = -1; + bool from_scroll = false; + + IHotbarSlotSelectedAction() = default; + + IHotbarSlotSelectedAction(std::istream& is); + + IAction getType() const + { + return IAction::HotbarSlotSelected; + } + + void serialize(std::ostream& os) const + { + os<<"HotbarSlotSelected "; + os<craft_inv)) return; + } + /* + Handle HotbarSlotSelected action special cases + */ + else if (a->getType() == IAction::HotbarSlotSelected) { + IHotbarSlotSelectedAction *ha = (IHotbarSlotSelectedAction*)a.get(); + ha->inv.applyCurrentPlayer(player->getName()); + // Note: `IHotbarSlotSelectedAction::clientApply` is empty, thus nothing to revert. } else { // Unknown action. Ignored. return; diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index 6cd46a3d9..e5045d253 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -236,6 +236,35 @@ void ScriptApiPlayer::on_authplayer(const std::string &name, const std::string & runCallbacks(3, RUN_CALLBACKS_MODE_FIRST); } +void ScriptApiPlayer::on_hotbar_slot_selected( + ServerActiveObject* player, + InventoryLocation& loc, + const std::string &list, + int hotbar_slot, + int prev_hotbar_slot, + bool is_from_scroll +) +{ + SCRIPTAPI_PRECHECKHEADER + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_hotbar_slot_selected"); + // param 1 + objectrefGetOrCreate(L, player); + // param 2 + InvRef::create(L, loc); + // param 3 + lua_pushstring(L, list.c_str()); + // param 4 + lua_pushinteger(L, hotbar_slot + 1); + // param 5 + lua_pushinteger(L, prev_hotbar_slot + 1); + // param 6 + lua_pushboolean(L, is_from_scroll); + + runCallbacks(6, RUN_CALLBACKS_MODE_LAST); +} + void ScriptApiPlayer::pushMoveArguments( const MoveAction &ma, int count, ServerActiveObject *player) diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index f74134952..62604d752 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -38,6 +38,14 @@ public: void on_playerReceiveFields(ServerActiveObject *player, const std::string &formname, const StringMap &fields); void on_authplayer(const std::string &name, const std::string &ip, bool is_success); + void on_hotbar_slot_selected( + ServerActiveObject* player, + InventoryLocation& loc, + const std::string& list, + int hotbar_slot, + int prev_hotbar_slot, + bool is_from_scroll + ); // Player inventory callbacks // Return number of accepted items to be moved