diff --git a/zone/client.cpp b/zone/client.cpp index 3845357a6..cc8b9f8cb 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -10567,3 +10567,65 @@ std::vector Client::GetPartyMembers() return clients_to_update; } + +void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items) +{ + if (bag_items.empty()) + { + return; + } + + // todo: maybe some common functions for SE_SummonItem and SE_SummonItemIntoBag + + const EQ::ItemData* bag_item = database.GetItem(bag_item_id); + if (!bag_item) + { + Message(Chat::Red, fmt::format("Unable to summon item [{}]. Item not found.", bag_item_id).c_str()); + return; + } + + if (CheckLoreConflict(bag_item)) + { + DuplicateLoreMessage(bag_item_id); + return; + } + + int bag_item_charges = 1; // just summoning a single bag + EQ::ItemInstance* summoned_bag = database.CreateItem(bag_item_id, bag_item_charges); + if (!summoned_bag || !summoned_bag->IsClassBag()) + { + Message(Chat::Red, fmt::format("Failed to summon bag item [{}]", bag_item_id).c_str()); + safe_delete(summoned_bag); + return; + } + + for (const auto& item : bag_items) + { + uint8 open_slot = summoned_bag->FirstOpenSlot(); + if (open_slot == 0xff) + { + Message(Chat::Red, "Attempting to summon item in to bag, but there is no room in the summoned bag!"); + break; + } + + const EQ::ItemData* current_item = database.GetItem(item.item_id); + + if (CheckLoreConflict(current_item)) + { + DuplicateLoreMessage(item.item_id); + } + else + { + EQ::ItemInstance* summoned_bag_item = database.CreateItem(item.item_id, item.charges); + if (summoned_bag_item) + { + summoned_bag->PutItem(open_slot, *summoned_bag_item); + safe_delete(summoned_bag_item); + } + } + } + + PushItemOnCursor(*summoned_bag); + SendItemPacket(EQ::invslot::slotCursor, summoned_bag, ItemPacketLimbo); + safe_delete(summoned_bag); +} diff --git a/zone/client.h b/zone/client.h index aa703f050..952c52fca 100644 --- a/zone/client.h +++ b/zone/client.h @@ -913,6 +913,7 @@ public: void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, ServerLootItem_Struct** bag_item_data = 0); bool AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0); bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQ::invslot::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); + void SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items); void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id, bool recurse = true); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index fee64d493..66fb2f47d 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -2229,6 +2229,31 @@ void Lua_Client::UntrainDiscBySpellID(uint16 spell_id, bool update_client) { self->UntrainDiscBySpellID(spell_id, update_client); } +void Lua_Client::SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_items_table) { + Lua_Safe_Call_Void(); + if (luabind::type(bag_items_table) != LUA_TTABLE) { + return; + } + + std::vector bagged_items; + + luabind::raw_iterator end; // raw_iterator uses lua_rawget + for (luabind::raw_iterator it(bag_items_table); it != end; ++it) + { + // verify array element is a table for item details + if (luabind::type(*it) == LUA_TTABLE) + { + // no need to try/catch, quest lua parser already catches exceptions + ServerLootItem_Struct item{}; + item.item_id = luabind::object_cast((*it)["item_id"]); + item.charges = luabind::object_cast((*it)["charges"]); + bagged_items.emplace_back(item); + } + } + + self->SummonBaggedItems(bag_item_id, bagged_items); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -2604,7 +2629,8 @@ luabind::scope lua_register_client() { .def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem) .def("SetGMStatus", (void(Lua_Client::*)(int32))&Lua_Client::SetGMStatus) .def("UntrainDiscBySpellID", (void(Lua_Client::*)(uint16))&Lua_Client::UntrainDiscBySpellID) - .def("UntrainDiscBySpellID", (void(Lua_Client::*)(uint16,bool))&Lua_Client::UntrainDiscBySpellID); + .def("UntrainDiscBySpellID", (void(Lua_Client::*)(uint16,bool))&Lua_Client::UntrainDiscBySpellID) + .def("SummonBaggedItems", (void(Lua_Client::*)(uint32,luabind::adl::object))&Lua_Client::SummonBaggedItems); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index b49cb9b31..0e1f1be9e 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -215,6 +215,7 @@ public: bool attuned); void SummonItem(uint32 item_id, int charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, bool attuned, int to_slot); + void SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_items_table); void SetStats(int type, int value); void IncStats(int type, int value); void DropItem(int slot_id); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 9c810c7ef..81c20917e 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -5712,6 +5712,57 @@ XS(XS_Client_UntrainDiscBySpellID) { XSRETURN_EMPTY; } +XS(XS_Client_SummonBaggedItems); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SummonBaggedItems) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Client::SummonBaggedItems(THIS, uint32 bag_item_id, ARRAYREF bag_items_array)"); // @categories Inventory and Items, Script Utility + } + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + + uint32 bag_item_id = (uint32) SvUV(ST(1)); + + // verify we're receiving a reference to an array type + SV* bag_items_avref = ST(2); + if (!bag_items_avref || !SvROK(bag_items_avref) || SvTYPE(SvRV(bag_items_avref)) != SVt_PVAV) + { + Perl_croak(aTHX_ "Client::SummonBaggedItems second argument is not a reference to an array"); + } + + // dereference into the array + AV* bag_items_av = (AV*)SvRV(bag_items_avref); + + std::vector bagged_items; + + auto count = av_len(bag_items_av) + 1; + for (int i = 0; i < count; ++i) + { + SV** element = av_fetch(bag_items_av, i, 0); + + // verify array element is a hash reference containing item details + if (element && SvROK(*element) && SvTYPE(SvRV(*element)) == SVt_PVHV) + { + HV* hash = (HV*)SvRV(*element); // dereference + + SV** item_id_ptr = hv_fetchs(hash, "item_id", false); + SV** item_charges_ptr = hv_fetchs(hash, "charges", false); + if (item_id_ptr && item_charges_ptr) + { + ServerLootItem_Struct item{}; + item.item_id = static_cast(SvUV(*item_id_ptr)); + item.charges = static_cast(SvIV(*item_charges_ptr)); + bagged_items.emplace_back(item); + } + } + } + + THIS->SummonBaggedItems(bag_item_id, bagged_items); + + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" #endif @@ -6001,6 +6052,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "Sit"), XS_Client_Sit, file, "$"); newXSproto(strcpy(buf, "SlotConvert2"), XS_Client_SlotConvert2, file, "$$"); newXSproto(strcpy(buf, "Stand"), XS_Client_Stand, file, "$"); + newXSproto(strcpy(buf, "SummonBaggedItems"), XS_Client_SummonBaggedItems, file, "$$$"); newXSproto(strcpy(buf, "SummonItem"), XS_Client_SummonItem, file, "$$;$$$$$$$$"); newXSproto(strcpy(buf, "TakeMoneyFromPP"), XS_Client_TakeMoneyFromPP, file, "$$;$"); newXSproto(strcpy(buf, "TGB"), XS_Client_TGB, file, "$");