From 4c3074d462046f103240d4ae5560343613c07214 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Wed, 9 Oct 2024 00:18:14 -0400 Subject: [PATCH] Core/Loot: Implemented currency loot Port From (https://github.com/TrinityCore/TrinityCore/commit/3e28ee080a1cf3c7cd332a8d1e0808505b4ea9d4) --- Source/Framework/Constants/ItemConst.cs | 1 + Source/Framework/Constants/Language.cs | 2 +- .../Database/Databases/CharacterDatabase.cs | 6 +- Source/Game/Chat/Commands/ModifyCommands.cs | 15 +- Source/Game/Chat/Commands/NPCCommands.cs | 101 ++++++- Source/Game/Entities/Player/Player.Items.cs | 97 ++++--- Source/Game/Entities/Player/Player.Quest.cs | 33 +++ Source/Game/Handlers/LootHandler.cs | 8 +- Source/Game/Loot/Loot.cs | 251 ++++++++++++------ Source/Game/Loot/LootItemStorage.cs | 65 ++--- Source/Game/Loot/LootManager.cs | 129 ++++++--- 11 files changed, 494 insertions(+), 214 deletions(-) diff --git a/Source/Framework/Constants/ItemConst.cs b/Source/Framework/Constants/ItemConst.cs index f8dc7260a..decff51da 100644 --- a/Source/Framework/Constants/ItemConst.cs +++ b/Source/Framework/Constants/ItemConst.cs @@ -1454,5 +1454,6 @@ namespace Framework.Constants { Item = 0, Reference = 1, + Currency = 2, } } diff --git a/Source/Framework/Constants/Language.cs b/Source/Framework/Constants/Language.cs index f33ad2c58..3b387ee83 100644 --- a/Source/Framework/Constants/Language.cs +++ b/Source/Framework/Constants/Language.cs @@ -370,7 +370,7 @@ namespace Framework.Constants CommandNpcShowlootLabel2 = 293, CommandNpcShowlootSublabel = 294, CommandNpcShowlootEntry2 = 295, - // 296 Free + CommandNpcShowLootCurrency = 296, // End CommandWanderDistance = 297, diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs index 6c3cef083..6d5a3f5ab 100644 --- a/Source/Framework/Database/Databases/CharacterDatabase.cs +++ b/Source/Framework/Database/Databases/CharacterDatabase.cs @@ -663,10 +663,10 @@ namespace Framework.Database PrepareStatement(CharStatements.DEL_CHAR_CUF_PROFILES, "DELETE FROM character_cuf_profiles WHERE guid = ?"); // Items that hold loot or money - PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items"); + PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items"); PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?"); - PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ? AND item_count = ? AND item_index = ?"); - PrepareStatement(CharStatements.INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_type = ? AND item_id = ? AND item_count = ? AND item_index = ?"); + PrepareStatement(CharStatements.INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); PrepareStatement(CharStatements.SEL_ITEMCONTAINER_MONEY, "SELECT container_id, money FROM item_loot_money"); PrepareStatement(CharStatements.DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?"); PrepareStatement(CharStatements.INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)"); diff --git a/Source/Game/Chat/Commands/ModifyCommands.cs b/Source/Game/Chat/Commands/ModifyCommands.cs index a8704f67c..aea3ed90c 100644 --- a/Source/Game/Chat/Commands/ModifyCommands.cs +++ b/Source/Game/Chat/Commands/ModifyCommands.cs @@ -611,11 +611,8 @@ namespace Game.Chat } [Command("currency", RBACPermissions.CommandModifyCurrency)] - static bool HandleModifyCurrencyCommand(CommandHandler handler, StringArguments args) + static bool HandleModifyCurrencyCommand(CommandHandler handler, CurrencyTypesRecord currency, int amount) { - if (args.Empty()) - return false; - Player target = handler.GetSelectedPlayerOrSelf(); if (target == null) { @@ -624,15 +621,7 @@ namespace Game.Chat return false; } - uint currencyId = args.NextUInt32(); - if (!CliDB.CurrencyTypesStorage.ContainsKey(currencyId)) - return false; - - uint amount = args.NextUInt32(); - if (amount == 0) - return false; - - target.ModifyCurrency(currencyId, (int)amount, CurrencyGainSource.Cheat, CurrencyDestroyReason.Cheat); + target.ModifyCurrency(currency.Id, amount, CurrencyGainSource.Cheat, CurrencyDestroyReason.Cheat); return true; } diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index fe31d6cde..de3aa164f 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -316,23 +316,23 @@ namespace Game.Chat return false; } + if (!creatureTarget.IsDead()) + { + handler.SendSysMessage(CypherStrings.CommandNotDeadOrNoLoot, creatureTarget.GetName()); + return false; + } + Loot loot = creatureTarget._loot; - if (creatureTarget.IsDead() || loot == null || loot.IsLooted()) + if ((loot == null || loot.IsLooted()) && creatureTarget.m_personalLoot.Count(p => p.Value.IsLooted()) == 0) { handler.SendSysMessage(CypherStrings.CommandNotDeadOrNoLoot, creatureTarget.GetName()); return false; } handler.SendSysMessage(CypherStrings.CommandNpcShowlootHeader, creatureTarget.GetName(), creatureTarget.GetEntry()); - handler.SendSysMessage(CypherStrings.CommandNpcShowlootMoney, loot.gold / MoneyConstants.Gold, (loot.gold % MoneyConstants.Gold) / MoneyConstants.Silver, loot.gold % MoneyConstants.Silver); - if (all.Equals("all", StringComparison.OrdinalIgnoreCase)) // nonzero from strcmp <. not equal - { - handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count); - foreach (LootItem item in loot.items) - if (!item.is_looted) - _ShowLootEntry(handler, item.itemid, item.count); - } + if (loot != null) + _ShowLootContents(handler, all == "all", loot); else { handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count); @@ -340,10 +340,11 @@ namespace Game.Chat if (!item.is_looted && !item.freeforall && item.conditions.IsEmpty()) _ShowLootEntry(handler, item.itemid, item.count); - if (!loot.GetPlayerFFAItems().Empty()) + foreach (var (lootOwner, personalLoot) in creatureTarget.m_personalLoot) { - handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel2, "FFA items per allowed player"); - _IterateNotNormalLootMap(handler, loot.GetPlayerFFAItems(), loot.items); + var character = Global.CharacterCacheStorage.GetCharacterCacheByGuid(lootOwner); + handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel2, $"Personal loot for {(character != null ? character.Name : "")}"); + _ShowLootContents(handler, all == "all", personalLoot); } } @@ -515,7 +516,7 @@ namespace Game.Chat return true; } - static void _ShowLootEntry(CommandHandler handler, uint itemId, byte itemCount, bool alternateString = false) + static void _ShowLootEntry(CommandHandler handler, uint itemId, uint itemCount, bool alternateString = false) { string name = "Unknown item"; @@ -526,6 +527,18 @@ namespace Game.Chat handler.SendSysMessage(alternateString ? CypherStrings.CommandNpcShowlootEntry2 : CypherStrings.CommandNpcShowlootEntry, itemCount, ItemConst.ItemQualityColors[(int)(itemTemplate != null ? itemTemplate.GetQuality() : ItemQuality.Poor)], itemId, name, itemId); } + + static void _ShowLootCurrencyEntry(CommandHandler handler, uint currencyId, uint count, bool alternateString = false) + { + CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(currencyId); + string name = "Unknown currency"; + if (currency != null) + name = currency.Name; + + handler.SendSysMessage(CypherStrings.CommandNpcShowLootCurrency, alternateString ? 6 : 3 /*number of bytes from following string*/, "\u2500\u2500", + count, ItemConst.ItemQualityColors[currency != null ? currency.Quality : (int)ItemQuality.Poor], currencyId, count, name, currencyId); + } + static void _IterateNotNormalLootMap(CommandHandler handler, MultiMap map, List items) { foreach (var key in map.Keys) @@ -542,7 +555,67 @@ namespace Game.Chat { LootItem item = items[it.LootListId]; if (!it.is_looted && !item.is_looted) - _ShowLootEntry(handler, item.itemid, item.count, true); + { + switch (item.type) + { + case LootItemType.Item: + _ShowLootEntry(handler, item.itemid, item.count, true); + break; + case LootItemType.Currency: + _ShowLootCurrencyEntry(handler, item.itemid, item.count, true); + break; + } + } + } + } + } + + static void _ShowLootContents(CommandHandler handler, bool all, Loot loot) + { + handler.SendSysMessage(CypherStrings.CommandNpcShowlootMoney, loot.gold / MoneyConstants.Gold, (loot.gold % MoneyConstants.Gold) / MoneyConstants.Silver, loot.gold % MoneyConstants.Silver); + + if (!all) + { + handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count); + foreach (LootItem item in loot.items) + { + if (!item.is_looted) + { + switch (item.type) + { + case LootItemType.Item: + _ShowLootEntry(handler, item.itemid, item.count); + break; + case LootItemType.Currency: + _ShowLootCurrencyEntry(handler, item.itemid, item.count); + break; + } + } + } + } + else + { + handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel, "Standard items", loot.items.Count); + foreach (LootItem item in loot.items) + { + if (!item.is_looted && !item.freeforall && item.conditions.IsEmpty()) + { + switch (item.type) + { + case LootItemType.Item: + _ShowLootEntry(handler, item.itemid, item.count); + break; + case LootItemType.Currency: + _ShowLootCurrencyEntry(handler, item.itemid, item.count); + break; + } + } + } + + if (!loot.GetPlayerFFAItems().Empty()) + { + handler.SendSysMessage(CypherStrings.CommandNpcShowlootLabel2, "FFA items per allowed player"); + _IterateNotNormalLootMap(handler, loot.GetPlayerFFAItems(), loot.items); } } } diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index fa30b908d..d98141400 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -6066,55 +6066,64 @@ namespace Game.Entities return; } - List dest = new(); - InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); - if (msg == InventoryResult.Ok) + switch (item.type) { - Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context); - if (ffaItem != null) - { - //freeforall case, notify only one player of the removal - ffaItem.is_looted = true; - SendNotifyLootItemRemoved(loot.GetGUID(), loot.GetOwnerGUID(), lootSlot); - } - else //not freeforall, notify everyone - loot.NotifyItemRemoved(lootSlot, GetMap()); + case LootItemType.Item: + List dest = new(); + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, null, null, item.itemid); + return; + } - //if only one person is supposed to loot the item, then set it to looted - if (!item.freeforall) - item.is_looted = true; + Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context); + if (newitem != null && (newitem.GetQuality() > ItemQuality.Epic || (newitem.GetQuality() == ItemQuality.Epic && newitem.GetItemLevel(this) >= GuildConst.MinNewsItemLevel))) + { + Guild guild = GetGuild(); + if (guild != null) + guild.AddGuildNews(GuildNews.ItemLooted, GetGUID(), 0, item.itemid); + } - --loot.unlootedCount; + // if aeLooting then we must delay sending out item so that it appears properly stacked in chat + if (aeResult == null || newitem == null) + { + SendNewItem(newitem, item.count, false, false, true, loot.GetDungeonEncounterId()); + UpdateCriteria(CriteriaType.LootItem, item.itemid, item.count); + UpdateCriteria(CriteriaType.GetLootByType, item.itemid, item.count, (uint)SharedConst.GetLootTypeForClient(loot.loot_type)); + UpdateCriteria(CriteriaType.LootAnyItem, item.itemid, item.count); + } + else + aeResult.Add(newitem, (byte)item.count, SharedConst.GetLootTypeForClient(loot.loot_type), loot.GetDungeonEncounterId()); - if (newitem != null && (newitem.GetQuality() > ItemQuality.Epic || (newitem.GetQuality() == ItemQuality.Epic && newitem.GetItemLevel(this) >= GuildConst.MinNewsItemLevel))) - { - Guild guild = GetGuild(); - if (guild != null) - guild.AddGuildNews(GuildNews.ItemLooted, GetGUID(), 0, item.itemid); - } - - // if aeLooting then we must delay sending out item so that it appears properly stacked in chat - if (aeResult == null || newitem == null) - { - SendNewItem(newitem, item.count, false, false, true, loot.GetDungeonEncounterId()); - UpdateCriteria(CriteriaType.LootItem, item.itemid, item.count); - UpdateCriteria(CriteriaType.GetLootByType, item.itemid, item.count, (uint)SharedConst.GetLootTypeForClient(loot.loot_type)); - UpdateCriteria(CriteriaType.LootAnyItem, item.itemid, item.count); - } - else - aeResult.Add(newitem, item.count, SharedConst.GetLootTypeForClient(loot.loot_type), loot.GetDungeonEncounterId()); - - // LootItem is being removed (looted) from the container, delete it from the DB. - if (loot.loot_type == LootType.Item) - Global.LootItemStorage.RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item.itemid, item.count, item.LootListId); - - if (newitem != null) - ApplyItemLootedSpell(newitem, true); - else - ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(item.itemid)); + if (newitem != null) + ApplyItemLootedSpell(newitem, true); + else + ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(item.itemid)); + break; + case LootItemType.Currency: + ModifyCurrency(item.itemid, (int)item.count, CurrencyGainSource.Loot); + break; } - else - SendEquipError(msg, null, null, item.itemid); + + if (ffaItem != null) + { + //freeforall case, notify only one player of the removal + ffaItem.is_looted = true; + SendNotifyLootItemRemoved(loot.GetGUID(), loot.GetOwnerGUID(), lootSlot); + } + else //not freeforall, notify everyone + loot.NotifyItemRemoved(lootSlot, GetMap()); + + //if only one person is supposed to loot the item, then set it to looted + if (!item.freeforall) + item.is_looted = true; + + --loot.unlootedCount; + + // LootItem is being removed (looted) from the container, delete it from the DB. + if (loot.loot_type == LootType.Item) + Global.LootItemStorage.RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item.type, item.itemid, item.count, item.LootListId); } public Dictionary GetAELootView() { return m_AELootView; } diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 0a71567b7..22bf10bc9 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -2873,6 +2873,39 @@ namespace Game.Entities return null; } + public bool HasQuestForCurrency(uint currencyId) + { + bool isCompletableObjective(QuestObjectiveStatusData objectiveStatus) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(objectiveStatus.QuestStatusPair.QuestID); + QuestObjective objective = Global.ObjectMgr.GetQuestObjective(objectiveStatus.ObjectiveId); + if (qInfo == null || objective == null || !IsQuestObjectiveCompletable(objectiveStatus.QuestStatusPair.Status.Slot, qInfo, objective)) + return false; + + // hide quest if player is in raid-group and quest is no raid quest + if (GetGroup() != null && GetGroup().IsRaidGroup() && !qInfo.IsAllowedInRaid(GetMap().GetDifficultyID())) + if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later + return false; + + if (!IsQuestObjectiveComplete(objectiveStatus.QuestStatusPair.Status.Slot, qInfo, objective)) + return true; + + return false; + }; + + bool hasObjectiveTypeForCurrency(QuestObjectiveType type) => m_questObjectiveStatus.LookupByKey((type, currencyId)).Any(isCompletableObjective); + if (hasObjectiveTypeForCurrency(QuestObjectiveType.Currency)) + return true; + + if (hasObjectiveTypeForCurrency(QuestObjectiveType.HaveCurrency)) + return true; + + if (hasObjectiveTypeForCurrency(QuestObjectiveType.ObtainCurrency)) + return true; + + return false; + } + public int GetQuestObjectiveData(QuestObjective objective) { ushort slot = FindQuestSlot(objective.QuestID); diff --git a/Source/Game/Handlers/LootHandler.cs b/Source/Game/Handlers/LootHandler.cs index f9c431032..8308eae31 100644 --- a/Source/Game/Handlers/LootHandler.cs +++ b/Source/Game/Handlers/LootHandler.cs @@ -427,11 +427,17 @@ namespace Game if (req.LootListID >= loot.items.Count) { + _player.SendLootError(req.Object, loot.GetOwnerGUID(), LootError.MasterOther); Log.outDebug(LogFilter.Loot, $"MasterLootItem: Player {GetPlayer().GetName()} might be using a hack! (slot {req.LootListID}, size {loot.items.Count})"); return; } LootItem item = loot.items[req.LootListID]; + if (item.type != LootItemType.Item) + { + _player.SendLootError(req.Object, loot.GetOwnerGUID(), LootError.MasterOther); + return; + } List dest = new(); InventoryResult msg = target.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); @@ -452,7 +458,7 @@ namespace Game // now move item from loot to target inventory Item newitem = target.StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context, item.BonusListIDs); if (newitem != null) - aeResult.Add(newitem, item.count, loot.loot_type, loot.GetDungeonEncounterId()); + aeResult.Add(newitem, (byte)item.count, loot.loot_type, loot.GetDungeonEncounterId()); else target.ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(item.itemid)); diff --git a/Source/Game/Loot/Loot.cs b/Source/Game/Loot/Loot.cs index cbcd64309..a851c2e8b 100644 --- a/Source/Game/Loot/Loot.cs +++ b/Source/Game/Loot/Loot.cs @@ -16,19 +16,47 @@ namespace Game.Loots { public class LootItem { + public uint itemid; + public uint LootListId; + public uint randomBonusListId; + public List BonusListIDs = new(); + public ItemContext context; + public ConditionsReference conditions; // additional loot condition + public List allowedGUIDs = new(); + public ObjectGuid rollWinnerGUID; // Stores the guid of person who won loot, if his bags are full only he can see the item in loot list! + public uint count; + public LootItemType type; + public bool is_looted; + public bool is_blocked; + public bool freeforall; // free for all + public bool is_underthreshold; + public bool is_counted; + public bool needs_quest; // quest drop + public bool follow_loot_rules; + public LootItem() { } public LootItem(LootStoreItem li) { itemid = li.itemid; conditions = li.conditions; - - ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid); - freeforall = proto != null && proto.HasFlag(ItemFlags.MultiDrop); - follow_loot_rules = !li.needs_quest || (proto != null && proto.FlagsCu.HasAnyFlag(ItemFlagsCustom.FollowLootRules)); - needs_quest = li.needs_quest; - randomBonusListId = ItemEnchantmentManager.GenerateItemRandomBonusListId(itemid); + switch (li.type) + { + case LootStoreItemType.Item: + randomBonusListId = ItemEnchantmentManager.GenerateItemRandomBonusListId(itemid); + type = LootItemType.Item; + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid); + freeforall = proto != null && proto.HasFlag(ItemFlags.MultiDrop); + follow_loot_rules = !li.needs_quest || (proto != null && proto.HasFlag(ItemFlagsCustom.FollowLootRules)); + break; + case LootStoreItemType.Currency: + type = LootItemType.Currency; + freeforall = true; + break; + default: + break; + } } /// @@ -39,10 +67,35 @@ namespace Game.Loots /// public bool AllowedForPlayer(Player player, Loot loot) { - return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions); + switch (type) + { + case LootItemType.Item: + return ItemAllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions); + case LootItemType.Currency: + return CurrencyAllowedForPlayer(player, itemid, needs_quest, conditions); + default: + break; + } + return false; } - public static bool AllowedForPlayer(Player player, Loot loot, uint itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, ConditionsReference conditions) + public static bool AllowedForPlayer(Player player, LootStoreItem lootStoreItem, bool strictUsabilityCheck) + { + switch (lootStoreItem.type) + { + case LootStoreItemType.Item: + return ItemAllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest, + !lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), + strictUsabilityCheck, lootStoreItem.conditions); + case LootStoreItemType.Currency: + return CurrencyAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.needs_quest, lootStoreItem.conditions); + default: + break; + } + return false; + } + + public static bool ItemAllowedForPlayer(Player player, Loot loot, uint itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, ConditionsReference conditions) { // DB conditions check if (!conditions.Meets(player)) @@ -96,6 +149,30 @@ namespace Game.Loots return true; } + public static bool CurrencyAllowedForPlayer(Player player, uint currencyId, bool needs_quest, ConditionsReference conditions) + { + // DB conditions check + if (!conditions.Meets(player)) + return false; + + CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(currencyId); + if (currency == null) + return false; + + // not show loot for not own team + if (currency.HasFlag(CurrencyTypesFlags.IsHordeOnly) && player.GetTeam() != Team.Horde) + return false; + + if (currency.HasFlag(CurrencyTypesFlags.IsAllianceOnly) && player.GetTeam() != Team.Alliance) + return false; + + // check quest requirements + if (needs_quest && !player.HasQuestForCurrency(currencyId)) + return false; + + return true; + } + public void AddAllowedLooter(Player player) { allowedGUIDs.Add(player.GetGUID()); @@ -174,23 +251,6 @@ namespace Game.Loots } public List GetAllowedLooters() { return allowedGUIDs; } - - public uint itemid; - public uint LootListId; - public uint randomBonusListId; - public List BonusListIDs = new(); - public ItemContext context; - public ConditionsReference conditions; // additional loot condition - public List allowedGUIDs = new(); - public ObjectGuid rollWinnerGUID; // Stores the guid of person who won loot, if his bags are full only he can see the item in loot list! - public byte count; - public bool is_looted; - public bool is_blocked; - public bool freeforall; // free for all - public bool is_underthreshold; - public bool is_counted; - public bool needs_quest; // quest drop - public bool follow_loot_rules; } public class NotNormalLootItem @@ -630,7 +690,7 @@ namespace Game.Loots for (uint i = 0; i < loot.items.Count; ++i) { LootItem disenchantLoot = loot.LootItemInSlot(i, player); - if (disenchantLoot != null) + if (disenchantLoot != null && disenchantLoot.type == LootItemType.Item) player.SendItemRetrievalMail(disenchantLoot.itemid, disenchantLoot.count, disenchantLoot.context); } } @@ -660,23 +720,40 @@ namespace Game.Loots // Inserts the item into the loot (called by LootTemplate processors) public void AddItem(LootStoreItem item) { - ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); - if (proto == null) - return; - - uint count = RandomHelper.URand(item.mincount, item.maxcount); - uint stacks = (uint)(count / proto.GetMaxStackSize() + (Convert.ToBoolean(count % proto.GetMaxStackSize()) ? 1 : 0)); - - for (uint i = 0; i < stacks && items.Count < SharedConst.MaxNRLootItems; ++i) + switch (item.type) { - LootItem generatedLoot = new(item); - generatedLoot.context = _itemContext; - generatedLoot.count = (byte)Math.Min(count, proto.GetMaxStackSize()); - generatedLoot.LootListId = (uint)items.Count; - generatedLoot.BonusListIDs = ItemBonusMgr.GetBonusListsForItem(generatedLoot.itemid, new(_itemContext)); + case LootStoreItemType.Item: + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); + if (proto == null) + return; - items.Add(generatedLoot); - count -= proto.GetMaxStackSize(); + uint count = RandomHelper.URand(item.mincount, item.maxcount); + uint stacks = (uint)(count / proto.GetMaxStackSize() + (Convert.ToBoolean(count % proto.GetMaxStackSize()) ? 1 : 0)); + + for (uint i = 0; i < stacks && items.Count < SharedConst.MaxNRLootItems; ++i) + { + LootItem generatedLoot = new(item); + generatedLoot.context = _itemContext; + generatedLoot.count = (byte)Math.Min(count, proto.GetMaxStackSize()); + generatedLoot.LootListId = (uint)items.Count; + generatedLoot.BonusListIDs = ItemBonusMgr.GetBonusListsForItem(generatedLoot.itemid, new(_itemContext)); + + items.Add(generatedLoot); + count -= proto.GetMaxStackSize(); + } + break; + } + case LootStoreItemType.Currency: + { + LootItem generatedLoot = new(item); + generatedLoot.count = RandomHelper.URand(item.mincount, item.maxcount); + generatedLoot.LootListId = (uint)items.Count; + items.Add(generatedLoot); + break; + } + default: + break; } } @@ -699,19 +776,36 @@ namespace Game.Loots if (!lootItem.rollWinnerGUID.IsEmpty() && lootItem.rollWinnerGUID != GetGUID()) continue; - List dest = new(); - InventoryResult msg = player.CanStoreNewItem(bag, slot, dest, lootItem.itemid, lootItem.count); - if (msg != InventoryResult.Ok && slot != ItemConst.NullSlot) - msg = player.CanStoreNewItem(bag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); - if (msg != InventoryResult.Ok && bag != ItemConst.NullBag) - msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); - if (msg != InventoryResult.Ok) + switch (lootItem.type) { - player.SendEquipError(msg, null, null, lootItem.itemid); - allLooted = false; - continue; - } + case LootItemType.Item: + List dest = new(); + InventoryResult msg = player.CanStoreNewItem(bag, slot, dest, lootItem.itemid, lootItem.count); + if (msg != InventoryResult.Ok && slot != ItemConst.NullSlot) + msg = player.CanStoreNewItem(bag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); + if (msg != InventoryResult.Ok && bag != ItemConst.NullBag) + msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); + if (msg != InventoryResult.Ok) + { + player.SendEquipError(msg, null, null, lootItem.itemid); + allLooted = false; + continue; + } + Item pItem = player.StoreNewItem(dest, lootItem.itemid, true, lootItem.randomBonusListId, null, lootItem.context, lootItem.BonusListIDs); + if (pItem != null) + { + player.SendNewItem(pItem, lootItem.count, false, createdByPlayer, broadcast, GetDungeonEncounterId()); + player.ApplyItemLootedSpell(pItem, true); + } + else + player.ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(lootItem.itemid)); + + break; + case LootItemType.Currency: + player.ModifyCurrency(lootItem.itemid, (int)lootItem.count, CurrencyGainSource.Loot); + break; + } if (ffaitem != null) ffaitem.is_looted = true; @@ -719,15 +813,6 @@ namespace Game.Loots lootItem.is_looted = true; --unlootedCount; - - Item pItem = player.StoreNewItem(dest, lootItem.itemid, true, lootItem.randomBonusListId, null, lootItem.context, lootItem.BonusListIDs); - if (pItem != null) - { - player.SendNewItem(pItem, lootItem.count, false, createdByPlayer, broadcast, GetDungeonEncounterId()); - player.ApplyItemLootedSpell(pItem, true); - } - else - player.ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(lootItem.itemid)); } return allLooted; @@ -783,7 +868,7 @@ namespace Game.Loots foreach (LootItem item in items) { - if (!item.follow_loot_rules || item.freeforall) + if (!item.follow_loot_rules || item.freeforall || item.type != LootItemType.Item) continue; ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); @@ -1014,12 +1099,8 @@ namespace Game.Loots return true; var ffaItems = GetPlayerFFAItems().LookupByKey(player.GetGUID()); - if (ffaItems != null) - { - bool hasFfaItem = ffaItems.Any(ffaItem => !ffaItem.is_looted); - if (hasFfaItem) - return true; - } + if (ffaItems != null && ffaItems.Any(ffaItem => !ffaItem.is_looted)) + return true; return false; } @@ -1046,12 +1127,34 @@ namespace Game.Loots if (!uiType.HasValue) continue; - LootItemData lootItem = new(); - lootItem.LootListID = (byte)item.LootListId; - lootItem.UIType = uiType.Value; - lootItem.Quantity = item.count; - lootItem.Loot = new(item); - packet.Items.Add(lootItem); + switch (item.type) + { + case LootItemType.Item: + { + LootItemData lootItem = new(); + lootItem.LootListID = (byte)item.LootListId; + lootItem.UIType = uiType.Value; + lootItem.Quantity = item.count; + lootItem.Loot = new(item); + packet.Items.Add(lootItem); + break; + } + case LootItemType.Currency: + { + LootCurrency lootCurrency = new(); + lootCurrency.CurrencyID = item.itemid; + lootCurrency.Quantity = item.count; + lootCurrency.LootListID = (byte)item.LootListId; + lootCurrency.UIType = (byte)uiType.Value; + + // fake visible quantity for SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT - handled in Player::ModifyCurrency + lootCurrency.Quantity = (uint)((float)lootCurrency.Quantity * viewer.GetTotalAuraMultiplierByMiscValue(AuraType.ModCurrencyCategoryGainPct, CliDB.CurrencyTypesStorage.LookupByKey(item.itemid).CategoryID)); + packet.Currencies.Add(lootCurrency); + break; + } + default: + break; + } } } diff --git a/Source/Game/Loot/LootItemStorage.cs b/Source/Game/Loot/LootItemStorage.cs index 3e42e0021..4a3548efe 100644 --- a/Source/Game/Loot/LootItemStorage.cs +++ b/Source/Game/Loot/LootItemStorage.cs @@ -35,18 +35,19 @@ namespace Game.Loots StoredLootContainer storedContainer = _lootItemStorage[key]; LootItem lootItem = new(); - lootItem.itemid = result.Read(1); - lootItem.count = result.Read(2); - lootItem.LootListId = result.Read(3); - lootItem.follow_loot_rules = result.Read(4); - lootItem.freeforall = result.Read(5); - lootItem.is_blocked = result.Read(6); - lootItem.is_counted = result.Read(7); - lootItem.is_underthreshold = result.Read(8); - lootItem.needs_quest = result.Read(9); - lootItem.randomBonusListId = result.Read(10); - lootItem.context = (ItemContext)result.Read(11); - StringArray bonusLists = new(result.Read(12), ' '); + lootItem.type = (LootItemType)result.Read(1); + lootItem.itemid = result.Read(2); + lootItem.count = result.Read(3); + lootItem.LootListId = result.Read(4); + lootItem.follow_loot_rules = result.Read(5); + lootItem.freeforall = result.Read(6); + lootItem.is_blocked = result.Read(7); + lootItem.is_counted = result.Read(8); + lootItem.is_underthreshold = result.Read(9); + lootItem.needs_quest = result.Read(10); + lootItem.randomBonusListId = result.Read(11); + lootItem.context = (ItemContext)result.Read(12); + StringArray bonusLists = new(result.Read(13), ' '); foreach (string str in bonusLists) lootItem.BonusListIDs.Add(uint.Parse(str)); @@ -177,12 +178,12 @@ namespace Game.Loots DB.Characters.CommitTransaction(trans); } - public void RemoveStoredLootItemForContainer(ulong containerId, uint itemId, uint count, uint itemIndex) + public void RemoveStoredLootItemForContainer(ulong containerId, LootItemType type, uint itemId, uint count, uint itemIndex) { if (!_lootItemStorage.ContainsKey(containerId)) return; - _lootItemStorage[containerId].RemoveItem(itemId, count, itemIndex); + _lootItemStorage[containerId].RemoveItem(type, itemId, count, itemIndex); } public void AddNewStoredLoot(ulong containerId, Loot loot, Player player) @@ -248,25 +249,26 @@ namespace Game.Loots PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_ITEMS); - // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix + // container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix stmt.AddValue(0, _containerId); - stmt.AddValue(1, lootItem.itemid); - stmt.AddValue(2, lootItem.count); - stmt.AddValue(3, lootItem.LootListId); - stmt.AddValue(4, lootItem.follow_loot_rules); - stmt.AddValue(5, lootItem.freeforall); - stmt.AddValue(6, lootItem.is_blocked); - stmt.AddValue(7, lootItem.is_counted); - stmt.AddValue(8, lootItem.is_underthreshold); - stmt.AddValue(9, lootItem.needs_quest); - stmt.AddValue(10, lootItem.randomBonusListId); - stmt.AddValue(11, (uint)lootItem.context); + stmt.AddValue(1, (sbyte)lootItem.type); + stmt.AddValue(2, lootItem.itemid); + stmt.AddValue(3, lootItem.count); + stmt.AddValue(4, lootItem.LootListId); + stmt.AddValue(5, lootItem.follow_loot_rules); + stmt.AddValue(6, lootItem.freeforall); + stmt.AddValue(7, lootItem.is_blocked); + stmt.AddValue(8, lootItem.is_counted); + stmt.AddValue(9, lootItem.is_underthreshold); + stmt.AddValue(10, lootItem.needs_quest); + stmt.AddValue(11, lootItem.randomBonusListId); + stmt.AddValue(12, (uint)lootItem.context); StringBuilder bonusListIDs = new(); foreach (int bonusListID in lootItem.BonusListIDs) bonusListIDs.Append(bonusListID + ' '); - stmt.AddValue(12, bonusListIDs.ToString()); + stmt.AddValue(13, bonusListIDs.ToString()); trans.Append(stmt); } @@ -295,7 +297,7 @@ namespace Game.Loots DB.Characters.Execute(stmt); } - public void RemoveItem(uint itemId, uint count, uint itemIndex) + public void RemoveItem(LootItemType type, uint itemId, uint count, uint itemIndex) { var bounds = _lootItems.LookupByKey(itemId); foreach (var itr in bounds) @@ -310,9 +312,10 @@ namespace Game.Loots // Deletes a single item associated with an openable item from the DB PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM); stmt.AddValue(0, _containerId); - stmt.AddValue(1, itemId); - stmt.AddValue(2, count); - stmt.AddValue(3, itemIndex); + stmt.AddValue(1, (sbyte)type); + stmt.AddValue(2, itemId); + stmt.AddValue(3, count); + stmt.AddValue(4, itemIndex); DB.Characters.Execute(stmt); } diff --git a/Source/Game/Loot/LootManager.cs b/Source/Game/Loot/LootManager.cs index 16c125260..93fe0331b 100644 --- a/Source/Game/Loot/LootManager.cs +++ b/Source/Game/Loot/LootManager.cs @@ -512,6 +512,14 @@ namespace Game.Loots } case LootStoreItemType.Reference: return RandomHelper.randChance(chance * (rate ? WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferenced) : 1.0f)); + case LootStoreItemType.Currency: + { + CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(itemid); + + float qualityModifier = currency != null && rate && QualityToRate[currency.Quality] != WorldCfg.Max ? WorldConfig.GetFloatValue(QualityToRate[currency.Quality]) : 1.0f; + + return RandomHelper.randChance(chance * qualityModifier); + } default: break; } @@ -523,7 +531,7 @@ namespace Game.Loots { if (mincount == 0) { - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: wrong mincount ({3}) - skipped", store.GetName(), entry, itemid, mincount); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: wrong mincount ({mincount}) - skipped"); return false; } @@ -533,38 +541,64 @@ namespace Game.Loots ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid); if (proto == null) { - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: item does not exist - skipped", store.GetName(), entry, itemid); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: item does not exist - skipped"); return false; } if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only { - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: equal-chanced grouped entry, but group not defined - skipped", store.GetName(), entry, itemid); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: equal-chanced grouped entry, but group not defined - skipped"); return false; } if (chance != 0 && chance < 0.000001f) // loot with low chance { - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: low chance ({3}) - skipped", - store.GetName(), entry, itemid, chance); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: low chance ({chance}) - skipped"); return false; } if (maxcount < mincount) // wrong max count { - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: max count ({3}) less that min count ({4}) - skipped", store.GetName(), entry, itemid, maxcount, mincount); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: max count ({maxcount}) less that min count ({mincount}) - skipped"); return false; } break; case LootStoreItemType.Reference: if (needs_quest) - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: quest chance will be treated as non-quest chance", store.GetName(), entry, itemid); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: quest chance will be treated as non-quest chance"); else if (chance == 0) // no chance for the reference { - Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: zero chance is specified for a reference, skipped", store.GetName(), entry, itemid); + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: zero chance is specified for a reference, skipped"); return false; } break; + case LootStoreItemType.Currency: + { + if (!CliDB.CurrencyTypesStorage.HasRecord(itemid)) + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: currency does not exist - skipped"); + return false; + } + + if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: equal-chanced grouped entry, but group not defined - skipped"); + return false; + } + + if (chance != 0 && chance < 0.0001f) // loot with low chance + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: low chance ({chance}) - skipped"); + return false; + } + + if (maxcount < mincount) // wrong max count + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: MaxCount ({maxcount}) less that MinCount ({mincount}) - skipped"); + return false; + } + break; + } default: Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} Item {itemid}: invalid ItemType {type}, skipped"); return false; @@ -757,12 +791,10 @@ namespace Game.Loots switch (item.type) { case LootStoreItemType.Item: + case LootStoreItemType.Currency: // Plain entries (not a reference, not grouped) // Chance is already checked, just add - if (personalLooter == null - || LootItem.AllowedForPlayer(personalLooter, null, item.itemid, item.needs_quest, - !item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), - true, item.conditions)) + if (personalLooter == null || LootItem.AllowedForPlayer(personalLooter, item, true)) loot.AddItem(item); break; case LootStoreItemType.Reference: @@ -813,13 +845,7 @@ namespace Game.Loots { // Plain entries (not a reference, not grouped) // Chance is already checked, just add - var lootersForItem = getLootersForItem(looter => - { - return LootItem.AllowedForPlayer(looter, null, item.itemid, item.needs_quest, - !item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), - true, item.conditions); - }); - + var lootersForItem = getLootersForItem(looter => LootItem.AllowedForPlayer(looter, item, true)); if (!lootersForItem.Empty()) { Player chosenLooter = lootersForItem.SelectRandom(); @@ -860,6 +886,16 @@ namespace Game.Loots } break; } + case LootStoreItemType.Currency: + { + // Plain entries (not a reference, not grouped) + // Chance is already checked, just add + var lootersForItem = getLootersForItem(looter => LootItem.AllowedForPlayer(looter, item, true)); + + foreach (Player looter in lootersForItem) + personalLoot[looter].AddItem(item); + break; + } default: break; } @@ -901,9 +937,7 @@ namespace Game.Loots switch (lootStoreItem.type) { case LootStoreItemType.Item: - if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest, - !lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), - strictUsabilityCheck, lootStoreItem.conditions)) + if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck)) return true; // active quest drop found break; case LootStoreItemType.Reference: @@ -931,6 +965,22 @@ namespace Game.Loots // Copies the conditions list from a template item to a LootItem foreach (var item in Entries) { + switch (item.type) + { + case LootStoreItemType.Item: + if (li.type != LootItemType.Item) + continue; + break; + case LootStoreItemType.Reference: + continue; + case LootStoreItemType.Currency: + if (li.type != LootItemType.Currency) + continue; + break; + default: + break; + } + if (item.itemid != li.itemid) continue; @@ -957,6 +1007,7 @@ namespace Game.Loots switch (item.type) { case LootStoreItemType.Item: + case LootStoreItemType.Currency: if (item.needs_quest) return true; // quest drop found break; @@ -1009,6 +1060,10 @@ namespace Game.Loots if (Referenced.HasQuestDropForPlayer(store, player, item.groupid)) return true; break; + case LootStoreItemType.Currency: + if (player.HasQuestForCurrency(item.itemid)) + return true; // active quest drop found + break; default: break; } @@ -1124,10 +1179,24 @@ namespace Game.Loots public bool HasQuestDropForPlayer(Player player) { - if (ExplicitlyChanced.Any(item => player.HasQuestForItem(item.itemid))) + var hasQuestForLootItem = (LootStoreItem item) => + { + switch (item.type) + { + case LootStoreItemType.Item: + return player.HasQuestForItem(item.itemid); + case LootStoreItemType.Currency: + return player.HasQuestForCurrency(item.itemid); + default: + break; + } + return false; + }; + + if (ExplicitlyChanced.Any(hasQuestForLootItem)) return true; - if (EqualChanced.Any(item => player.HasQuestForItem(item.itemid))) + if (EqualChanced.Any(hasQuestForLootItem)) return true; return false; @@ -1239,15 +1308,11 @@ namespace Game.Loots public bool HasDropForPlayer(Player player, bool strictUsabilityCheck) { foreach (LootStoreItem lootStoreItem in ExplicitlyChanced) - if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest, - !lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), - strictUsabilityCheck, lootStoreItem.conditions)) + if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck)) return true; foreach (LootStoreItem lootStoreItem in EqualChanced) - if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest, - !lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), - strictUsabilityCheck, lootStoreItem.conditions)) + if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck)) return true; return false; @@ -1268,9 +1333,7 @@ namespace Game.Loots if ((item.lootmode & _lootMode) == 0) return true; - if (_personalLooter != null && !LootItem.AllowedForPlayer(_personalLooter, null, item.itemid, item.needs_quest, - !item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), - true, item.conditions)) + if (_personalLooter != null && !LootItem.AllowedForPlayer(_personalLooter, item, true)) return true; return false;