From de84ed13695f04e90feae49ba2a74106f3da8add Mon Sep 17 00:00:00 2001 From: iThorgrim <125808072+iThorgrim@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:30:12 +0200 Subject: [PATCH] Feat(Loot): Add Loot Objet and Methods and GetLoot method for Creature (#287) --- src/LuaEngine/LuaEngine.h | 1 + src/LuaEngine/LuaFunctions.cpp | 43 ++ src/LuaEngine/methods/CreatureMethods.h | 11 + src/LuaEngine/methods/LootMethods.h | 523 ++++++++++++++++++++++++ 4 files changed, 578 insertions(+) create mode 100644 src/LuaEngine/methods/LootMethods.h diff --git a/src/LuaEngine/LuaEngine.h b/src/LuaEngine/LuaEngine.h index 999fb03..fdc8d88 100644 --- a/src/LuaEngine/LuaEngine.h +++ b/src/LuaEngine/LuaEngine.h @@ -23,6 +23,7 @@ #include "HttpManager.h" #include "EventEmitter.h" #include "TicketMgr.h" +#include "LootMgr.h" #include #include diff --git a/src/LuaEngine/LuaFunctions.cpp b/src/LuaEngine/LuaFunctions.cpp index 4405f37..0f432ae 100644 --- a/src/LuaEngine/LuaFunctions.cpp +++ b/src/LuaEngine/LuaFunctions.cpp @@ -42,6 +42,7 @@ extern "C" #include "RollMethods.h" #include "TicketMethods.h" #include "SpellInfoMethods.h" +#include "LootMethods.h" // DBCStores includes #include "GemPropertiesEntryMethods.h" @@ -837,6 +838,7 @@ ElunaRegister CreatureMethods[] = { "GetDBTableGUIDLow", &LuaCreature::GetDBTableGUIDLow }, { "GetCreatureFamily", &LuaCreature::GetCreatureFamily }, { "GetReactState", &LuaCreature::GetReactState }, + { "GetLoot", &LuaCreature::GetLoot }, // Setters { "SetRegeneratingHealth", &LuaCreature::SetRegeneratingHealth }, @@ -1598,6 +1600,47 @@ ElunaRegister SpellEntryMethods[] = { NULL, NULL } }; +ElunaRegister LootMethods[] = +{ + // Get + { "GetMoney", &LuaLoot::GetMoney }, + { "GetItems", &LuaLoot::GetItems }, + { "GetUnlootedCount", &LuaLoot::GetUnlootedCount }, + { "GetLootType", &LuaLoot::GetLootType }, + { "GetRoundRobinPlayer", &LuaLoot::GetRoundRobinPlayer }, + { "GetLootOwner", &LuaLoot::GetLootOwner }, + { "GetContainer", &LuaLoot::GetContainer }, + { "GetSourceWorldObject", &LuaLoot::GetSourceWorldObject }, + { "GetItemCount", &LuaLoot::GetItemCount }, + { "GetMaxSlotForPlayer", &LuaLoot::GetMaxSlotForPlayer }, + + // Set + { "AddItem", &LuaLoot::AddItem }, + { "RemoveItem", &LuaLoot::RemoveItem }, + { "SetMoney", &LuaLoot::SetMoney }, + { "SetUnlootedCount", &LuaLoot::SetUnlootedCount }, + { "UpdateItemIndex", &LuaLoot::UpdateItemIndex }, + { "SetItemLooted", &LuaLoot::SetItemLooted }, + { "SetLootType", &LuaLoot::SetLootType }, + { "SetRoundRobinPlayer", &LuaLoot::SetRoundRobinPlayer }, + { "SetLootOwner", &LuaLoot::SetLootOwner }, + { "SetContainer", &LuaLoot::SetContainer }, + { "SetSourceWorldObject", &LuaLoot::SetSourceWorldObject }, + { "Clear", &LuaLoot::Clear }, + { "AddLooter", &LuaLoot::AddLooter }, + { "RemoveLooter", &LuaLoot::RemoveLooter }, + + // Boolean + { "HasItem", &LuaLoot::HasItem }, + { "HasQuestItems", &LuaLoot::HasQuestItems }, + { "HasItemForAll", &LuaLoot::HasItemForAll }, + { "HasOverThresholdItem", &LuaLoot::HasOverThresholdItem }, + { "IsLooted", &LuaLoot::IsLooted }, + { "IsEmpty", &LuaLoot::IsEmpty }, + + { NULL, NULL } +}; + // fix compile error about accessing vehicle destructor template<> int ElunaTemplate::CollectGarbage(lua_State* L) { diff --git a/src/LuaEngine/methods/CreatureMethods.h b/src/LuaEngine/methods/CreatureMethods.h index 52f5f1f..b7c1def 100644 --- a/src/LuaEngine/methods/CreatureMethods.h +++ b/src/LuaEngine/methods/CreatureMethods.h @@ -1345,5 +1345,16 @@ namespace LuaCreature return 1; } + + /** + * Returns the [Creature]'s loot. + * + * @return [Loot] loot : the loot object + */ + int GetLoot(lua_State*L, Creature* creature) + { + Eluna::Push(L, &creature->loot); + return 1; + } }; #endif diff --git a/src/LuaEngine/methods/LootMethods.h b/src/LuaEngine/methods/LootMethods.h new file mode 100644 index 0000000..3d2e548 --- /dev/null +++ b/src/LuaEngine/methods/LootMethods.h @@ -0,0 +1,523 @@ +/* +* Copyright (C) 2010 - 2025 Eluna Lua Engine +* This program is free software licensed under GPL version 3 +* Please see the included DOCS/LICENSE.md for more information +*/ + +#ifndef LOOTMETHODS_H +#define LOOTMETHODS_H + +/*** + * Represents loot that can be obtained from various sources like creatures, gameobjects, or items. + * + * Contains information about items that can be looted, their quantities, money, and loot state. + * + * Inherits all methods from: none + */ +namespace LuaLoot +{ + /** + * Returns `true` if all loot has been taken from this [Loot], returns `false` otherwise. + * + * @return bool isLooted + */ + int IsLooted(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->isLooted()); + return 1; + } + + /** + * Adds an item to the [Loot] with the specified parameters. + * + * If an item with the same ID already exists and its count is less than 255, the count will be increased instead of adding a new entry. + * + * @param uint32 itemId : the ID of the item to add + * @param uint8 minCount : minimum count of the item + * @param uint8 maxCount : maximum count of the item + * @param float chance : chance for the item to drop (0-100) + * @param uint16 lootMode : loot mode for the item + * @param bool needsQuest = false : if `true`, the item requires a quest to be looted + */ + int AddItem(lua_State* L, Loot* loot) + { + uint32 itemid = Eluna::CHECKVAL(L, 2); + uint8 min_count = Eluna::CHECKVAL(L, 3); + uint8 max_count = Eluna::CHECKVAL(L, 4); + float chance = Eluna::CHECKVAL(L, 5); + uint16 loot_mode = Eluna::CHECKVAL(L, 6); + bool needs_quest = Eluna::CHECKVAL(L, 7, false); + + for (LootItem &lootitem : loot->items) + { + if (lootitem.itemid == itemid && lootitem.count < 255) + { + lootitem.count += min_count; + return 0; + } + } + + LootStoreItem newLootStoreItem(itemid, 0, chance, needs_quest, loot_mode, 0, min_count, max_count); + loot->AddItem(newLootStoreItem); + + return 0; + } + + /** + * Returns `true` if the [Loot] contains the specified item, and returns `false` otherwise. + * + * @param uint32 itemId = 0 : the ID of the item to check for. If 0, checks if any item exists + * @param uint32 count = 0 : specific count to check for. If 0, ignores count + * @return bool hasItem + */ + int HasItem(lua_State* L, Loot* loot) + { + uint32 itemid = Eluna::CHECKVAL(L, 2, false); + uint32 count = Eluna::CHECKVAL(L, 3, false); + bool has_item = false; + + if (itemid) + { + for (const LootItem &lootitem : loot->items) + { + if (lootitem.itemid == itemid && (count == 0 || lootitem.count == count)) + { + has_item = true; + break; + } + } + } + else + { + for (const LootItem &lootitem : loot->items) + { + if (lootitem.itemid != 0) + { + has_item = true; + break; + } + } + } + + Eluna::Push(L, has_item); + return 1; + } + + /** + * Removes the specified item from the [Loot]. + * + * If count is specified, removes only that amount. Otherwise removes all items with the ID. + * + * @param uint32 itemId : the ID of the item to remove + * @param bool isCountSpecified = false : if `true`, only removes the specified count + * @param uint32 count = 0 : amount to remove when isCountSpecified is true + */ + int RemoveItem(lua_State* L, Loot* loot) + { + uint32 itemid = Eluna::CHECKVAL(L, 2); + bool isCountSpecified = Eluna::CHECKVAL(L, 3, false); + uint32 count = isCountSpecified ? Eluna::CHECKVAL(L, 4) : 0; + + for (auto it = loot->items.begin(); it != loot->items.end();) + { + if (it->itemid == itemid) + { + if (isCountSpecified) + { + if (it->count > count) + { + it->count -= count; + break; + } + else + { + count -= it->count; + } + } + it = loot->items.erase(it); + } + else + { + ++it; + } + } + return 0; + } + + /** + * Returns the amount of money in this [Loot]. + * + * @return uint32 money : the amount of money in copper + */ + int GetMoney(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->gold); + return 1; + } + + /** + * Sets the amount of money in this [Loot]. + * + * @param uint32 money : the amount of money to set in copper + */ + int SetMoney(lua_State* L, Loot* loot) + { + uint32 gold = Eluna::CHECKVAL(L, 2); + + loot->gold = gold; + return 0; + } + + /** + * Generates a random amount of money for this [Loot] within the specified range. + * + * @param uint32 minGold : minimum amount of money in copper + * @param uint32 maxGold : maximum amount of money in copper + */ + int GenerateMoney(lua_State* L, Loot* loot) + { + uint32 min_gold = Eluna::CHECKVAL(L, 2); + uint32 max_gold = Eluna::CHECKVAL(L, 3); + + loot->generateMoneyLoot(min_gold, max_gold); + return 0; + } + + /** + * Clears all items and money from this [Loot]. + */ + int Clear(lua_State* /*L*/, Loot* loot) + { + loot->clear(); + return 0; + } + + /** + * Sets the number of unlooted items in this [Loot]. + * + * @param uint32 count : the number of unlooted items + */ + int SetUnlootedCount(lua_State* L, Loot* loot) + { + uint32 count = Eluna::CHECKVAL(L, 2); + + loot->unlootedCount = count; + return 0; + } + + /** + * Returns the number of unlooted items in this [Loot]. + * + * @return uint32 unlootedCount + */ + int GetUnlootedCount(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->unlootedCount); + return 1; + } + + /** + * Returns a table containing all items in this [Loot]. + * + * Each item is represented as a table with the following fields: + * - id: item ID + * - index: item index in the loot list + * - count: quantity of the item + * - needs_quest: whether the item requires a quest + * - is_looted: whether the item has been looted + * + * @return table items : array of item tables + */ + int GetItems(lua_State* L, Loot* loot) + { + lua_createtable(L, loot->items.size(), 0); + int tbl = lua_gettop(L); + + for (unsigned int i = 0; i < loot->items.size(); i++) + { + lua_newtable(L); + + Eluna::Push(L, loot->items[i].itemid); + lua_setfield(L, -2, "id"); + + Eluna::Push(L, loot->items[i].itemIndex); + lua_setfield(L, -2, "index"); + + Eluna::Push(L, loot->items[i].count); + lua_setfield(L, -2, "count"); + + Eluna::Push(L, loot->items[i].needs_quest); + lua_setfield(L, -2, "needs_quest"); + + Eluna::Push(L, loot->items[i].is_looted); + lua_setfield(L, -2, "is_looted"); + + lua_rawseti(L, tbl, i + 1); + } + + lua_settop(L, tbl); + return 1; + } + + /** + * Updates the index of all items in this [Loot] to match their position in the list. + * + * This should be called after removing items to ensure indices are sequential. + */ + int UpdateItemIndex(lua_State* /*L*/, Loot* loot) + { + for (unsigned int i = 0; i < loot->items.size(); i++) + loot->items[i].itemIndex = i; + + return 0; + } + + /** + * Sets the looted status of a specific item in this [Loot]. + * + * @param uint32 itemId : the ID of the item + * @param uint32 count : specific count to match. If 0, ignores count + * @param bool looted = true : `true` to mark as looted, `false` to mark as unlooted + */ + int SetItemLooted(lua_State* L, Loot* loot) + { + uint32 itemid = Eluna::CHECKVAL(L, 2); + uint32 count = Eluna::CHECKVAL(L, 3); + bool looted = Eluna::CHECKVAL(L, 4, true); + + for (auto &lootItem : loot->items) + { + if (lootItem.itemid == itemid && (count == 0 || lootItem.count == count)) + { + lootItem.is_looted = looted; + break; + } + } + return 0; + } + + /** + * Returns `true` if the [Loot] is completely empty (no items and no money), returns `false` otherwise. + * + * @return bool isEmpty + */ + int IsEmpty(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->empty()); + return 1; + } + + /** + * Returns the [Loot] type. + * + * @return [LootType] lootType + */ + int GetLootType(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->loot_type); + return 1; + } + + /** + * Sets the [Loot] type. + * + *
+     * enum LootType
+     * {
+     *     LOOT_NONE                           = 0,
+     *     LOOT_CORPSE                         = 1,
+     *     LOOT_PICKPOCKETING                  = 2,
+     *     LOOT_FISHING                        = 3,
+     *     LOOT_DISENCHANTING                  = 4,
+     *     LOOT_SKINNING                       = 6,
+     *     LOOT_PROSPECTING                    = 7,
+     *     LOOT_MILLING                        = 8,
+     *     LOOT_FISHINGHOLE                    = 20,
+     *     LOOT_INSIGNIA                       = 21,
+     *     LOOT_FISHING_JUNK                   = 22
+     * };
+     * 
+ * + * @param [LootType] lootType : the loot type to set + */ + int SetLootType(lua_State* L, Loot* loot) + { + uint32 lootType = Eluna::CHECKVAL(L, 2); + loot->loot_type = static_cast(lootType); + return 0; + } + + /** + * Returns the [Player] GUID that owns this loot for round robin distribution. + * + * @return ObjectGUID roundRobinPlayer : the player GUID + */ + int GetRoundRobinPlayer(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->roundRobinPlayer); + return 1; + } + + /** + * Sets the [Player] GUID for round robin loot distribution. + * + * @param ObjectGuid playerGUID : the player GUID + */ + int SetRoundRobinPlayer(lua_State* L, Loot* loot) + { + ObjectGuid guid = Eluna::CHECKVAL(L, 2); + loot->roundRobinPlayer = guid; + return 0; + } + + /** + * Returns the [Player] GUID that owns this loot. + * + * @return ObjectGuid lootOwner : the player GUID + */ + int GetLootOwner(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->lootOwnerGUID); + return 1; + } + + /** + * Sets the [Player] GUID that owns this loot. + * + * @param ObjectGuid playerGUID : the player GUID + */ + int SetLootOwner(lua_State* L, Loot* loot) + { + ObjectGuid guid = Eluna::CHECKVAL(L, 2); + loot->lootOwnerGUID = guid; + return 0; + } + + /** + * Returns the container GUID that holds this loot. + * + * @return ObjectGuid containerGUID : the container GUID + */ + int GetContainer(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->containerGUID); + return 1; + } + + /** + * Sets the container GUID that holds this loot. + * + * @param ObjectGuid containerGUID : the container GUID + */ + int SetContainer(lua_State* L, Loot* loot) + { + ObjectGuid guid = Eluna::CHECKVAL(L, 2); + loot->containerGUID = guid; + return 0; + } + + /** + * Returns the source [WorldObject] GUID for this loot. + * + * @return ObjectGuid sourceGUID : the source [WorldObject] GUID + */ + int GetSourceWorldObject(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->sourceWorldObjectGUID); + return 1; + } + + /** + * Sets the source [WorldObject] GUID for this loot. + * + * @param ObjectGuid sourceGUID : the source [WorldObject] GUID + */ + int SetSourceWorldObject(lua_State* L, Loot* loot) + { + ObjectGuid guid = Eluna::CHECKVAL(L, 2); + loot->sourceWorldObjectGUID = guid; + return 0; + } + + /** + * Returns `true` if the [Loot] contains quest items and returns `false` otherwise. + * + * @return bool hasQuestItems + */ + int HasQuestItems(lua_State* L, Loot* loot) + { + Eluna::Push(L, !loot->quest_items.empty()); + return 1; + } + + /** + * Returns `true` if the [Loot] has items available for all players and returns `false` otherwise. + * + * @return bool hasItemForAll + */ + int HasItemForAll(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->hasItemForAll()); + return 1; + } + + /** + * Returns `true` if the [Loot] has items that are over the group loot threshold and returns `false` otherwise. + * + * @return bool hasOverThresholdItem + */ + int HasOverThresholdItem(lua_State* L, Loot* loot) + { + Eluna::Push(L, loot->hasOverThresholdItem()); + return 1; + } + + /** + * Returns the total number of items (regular + quest items) in this [Loot]. + * + * @return uint32 itemCount + */ + int GetItemCount(lua_State* L, Loot* loot) + { + Eluna::Push(L, static_cast(loot->items.size() + loot->quest_items.size())); + return 1; + } + + /** + * Returns the maximum loot slot index available for the specified [Player]. + * + * @param [Player] player : the player to check slots for + * @return uint32 maxSlot + */ + int GetMaxSlotForPlayer(lua_State* L, Loot* loot) + { + Player* player = Eluna::CHECKOBJ(L, 2); + Eluna::Push(L, loot->GetMaxSlotInLootFor(player)); + return 1; + } + + /** + * Adds a [Player] to the list of players currently looting this [Loot]. + * + * @param [Player] player : the player to add as a looter + */ + int AddLooter(lua_State* L, Loot* loot) + { + Player* player = Eluna::CHECKOBJ(L, 2); + loot->AddLooter(player->GetGUID()); + return 0; + } + + /** + * Removes a [Player] from the list of players currently looting this [Loot]. + * + * @param [Player] player : the player to remove from looters + */ + int RemoveLooter(lua_State* L, Loot* loot) + { + Player* player = Eluna::CHECKOBJ(L, 2); + loot->RemoveLooter(player->GetGUID()); + return 0; + } +}; +#endif // LOOTMETHODS_H