From 744e9c1c1957978b90e3eafc8856b990136d4aa1 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Wed, 26 Oct 2022 01:36:15 -0400 Subject: [PATCH] Core/Loot: Implemented dungeon encounter personal loot Port From (https://github.com/TrinityCore/TrinityCore/commit/010e6f7f49744b16e3ecececb7d9605f8b8db4d5) --- Source/Game/Entities/GameObject/GameObject.cs | 37 ++- Source/Game/Entities/Item/ItemTemplate.cs | 2 + Source/Game/Entities/Player/Player.Items.cs | 50 +--- Source/Game/Entities/Player/Player.cs | 3 +- Source/Game/Entities/Unit/Unit.Combat.cs | 62 ++--- Source/Game/Groups/Group.cs | 7 +- Source/Game/Handlers/GroupHandler.cs | 6 +- Source/Game/Loot/Loot.cs | 35 +-- Source/Game/Loot/LootManager.cs | 227 ++++++++++++++++-- 9 files changed, 314 insertions(+), 115 deletions(-) diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index 1a82dd6f6..3449fa224 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -1749,17 +1749,34 @@ namespace Game.Entities { if (info.Chest.chestPersonalLoot != 0) { - Loot personalLoot = new(GetMap(), GetGUID(), LootType.Chest, null); - m_personalLoot[player.GetGUID()] = personalLoot; - - personalLoot.SetDungeonEncounterId(info.Chest.DungeonEncounter); - personalLoot.FillLoot(info.Chest.chestPersonalLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext()); - - if (GetLootMode() > 0) + GameObjectTemplateAddon addon = GetTemplateAddon(); + if (info.Chest.DungeonEncounter != 0) { - GameObjectTemplateAddon addon = GetTemplateAddon(); - if (addon != null) - personalLoot.GenerateMoneyLoot(addon.Mingold, addon.Maxgold); + List tappers = new(); + foreach (ObjectGuid tapperGuid in GetTapList()) + { + Player tapper = Global.ObjAccessor.GetPlayer(this, tapperGuid); + if (tapper != null) + tappers.Add(tapper); + } + + if (tappers.Empty()) + tappers.Add(player); + + m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(info.Chest.DungeonEncounter, info.Chest.chestPersonalLoot, + LootStorage.Gameobject, LootType.Chest, this, addon != null ? addon.Mingold : 0, addon != null ? addon.Maxgold : 0, + (ushort)GetLootMode(), GetMap().GetDifficultyLootItemContext(), tappers); + } + else + { + Loot loot = new(GetMap(), GetGUID(), LootType.Chest, null); + m_personalLoot[player.GetGUID()] = loot; + + loot.SetDungeonEncounterId(info.Chest.DungeonEncounter); + loot.FillLoot(info.Chest.chestPersonalLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext()); + + if (GetLootMode() > 0 && addon != null) + loot.GenerateMoneyLoot(addon.Mingold, addon.Maxgold); } } } diff --git a/Source/Game/Entities/Item/ItemTemplate.cs b/Source/Game/Entities/Item/ItemTemplate.cs index 69c47921f..2e5f8d389 100644 --- a/Source/Game/Entities/Item/ItemTemplate.cs +++ b/Source/Game/Entities/Item/ItemTemplate.cs @@ -335,6 +335,8 @@ namespace Game.Entities public bool IsWeapon() { return GetClass() == ItemClass.Weapon; } + public bool IsArmor() { return GetClass() == ItemClass.Armor; } + public bool IsRangedWeapon() { return IsWeapon() && (GetSubClass() == (uint)ItemSubClassWeapon.Bow || diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index 4f359602c..c0496dafe 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -2959,14 +2959,17 @@ namespace Game.Entities } } - public InventoryResult CanRollForItemInLFG(ItemTemplate proto, Map map) + public InventoryResult CanRollNeedForItem(ItemTemplate proto, Map map, bool restrictOnlyLfg) { - if (!GetGroup() || !GetGroup().IsLFGGroup()) - return InventoryResult.Ok; // not in LFG group + if (restrictOnlyLfg) + { + if (!GetGroup() || !GetGroup().IsLFGGroup()) + return InventoryResult.Ok; // not in LFG group - // check if looted object is inside the lfg dungeon - if (!Global.LFGMgr.InLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID())) - return InventoryResult.Ok; + // check if looted object is inside the lfg dungeon + if (!Global.LFGMgr.InLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID())) + return InventoryResult.Ok; + } if (proto == null) return InventoryResult.ItemNotFound; @@ -2986,41 +2989,14 @@ namespace Game.Entities return InventoryResult.CantEquipSkill; } - Class _class = GetClass(); if (proto.GetClass() == ItemClass.Weapon && GetSkillValue(proto.GetSkill()) == 0) return InventoryResult.ProficiencyNeeded; - if (proto.GetClass() == ItemClass.Armor && proto.GetSubClass() > (uint)ItemSubClassArmor.Miscellaneous - && proto.GetSubClass() < (uint)ItemSubClassArmor.Cosmetic && proto.GetInventoryType() != InventoryType.Cloak) + if (proto.GetClass() == ItemClass.Armor && proto.GetInventoryType() != InventoryType.Cloak) { - if (_class == Class.Warrior || _class == Class.Paladin || _class == Class.Deathknight) - { - if (GetLevel() < 40) - { - if (proto.GetSubClass() != (uint)ItemSubClassArmor.Mail) - return InventoryResult.ClientLockedOut; - } - else if (proto.GetSubClass() != (uint)ItemSubClassArmor.Plate) - return InventoryResult.ClientLockedOut; - } - else if (_class == Class.Hunter || _class == Class.Shaman) - { - if (GetLevel() < 40) - { - if (proto.GetSubClass() != (uint)ItemSubClassArmor.Leather) - return InventoryResult.ClientLockedOut; - } - else if (proto.GetSubClass() != (uint)ItemSubClassArmor.Mail) - return InventoryResult.ClientLockedOut; - } - - if (_class == Class.Rogue || _class == Class.Druid) - if (proto.GetSubClass() != (uint)ItemSubClassArmor.Leather) - return InventoryResult.ClientLockedOut; - - if (_class == Class.Mage || _class == Class.Priest || _class == Class.Warlock) - if (proto.GetSubClass() != (uint)ItemSubClassArmor.Cloth) - return InventoryResult.ClientLockedOut; + ChrClassesRecord classesEntry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); + if ((classesEntry.ArmorTypeMask & 1 << (int)proto.GetSubClass()) == 0) + return InventoryResult.ClientLockedOut; } return InventoryResult.Ok; diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 88a82521d..8f07bd7ad 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -3141,8 +3141,7 @@ namespace Game.Entities switch (loot.GetLootMethod()) { - case LootMethod.PersonalLoot:// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot) - return false; + case LootMethod.PersonalLoot: case LootMethod.FreeForAll: return true; case LootMethod.RoundRobin: diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index a3acb1b68..45a3a7dab 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -704,9 +704,9 @@ namespace Game.Entities Creature creature = victim.ToCreature(); - bool isRewardAllowed = true; + bool isRewardAllowed = attacker != victim; if (creature != null) - isRewardAllowed = !creature.GetTapList().Empty(); + isRewardAllowed = isRewardAllowed && !creature.GetTapList().Empty(); List tappers = new(); if (isRewardAllowed && creature) @@ -727,11 +727,6 @@ namespace Game.Entities // call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop) if (isRewardAllowed) { - PartyKillLog partyKillLog = new(); - partyKillLog.Player = player.GetGUID(); - partyKillLog.Victim = victim.GetGUID(); - partyKillLog.Write(); - HashSet groups = new(); foreach (Player tapper in tappers) { @@ -740,6 +735,11 @@ namespace Game.Entities { if (groups.Add(tapperGroup)) { + PartyKillLog partyKillLog = new(); + partyKillLog.Player = player && tapperGroup.IsMember(player.GetGUID()) ? player.GetGUID() : tapper.GetGUID(); + partyKillLog.Victim = victim.GetGUID(); + partyKillLog.Write(); + tapperGroup.BroadcastPacket(partyKillLog, tapperGroup.GetMemberGroup(tapper.GetGUID()) != 0); if (creature) @@ -747,7 +747,12 @@ namespace Game.Entities } } else + { + PartyKillLog partyKillLog = new(); + partyKillLog.Player = tapper.GetGUID(); + partyKillLog.Victim = victim.GetGUID(); tapper.SendPacket(partyKillLog); + } } // Generate loot before updating looter @@ -763,30 +768,33 @@ namespace Game.Entities Group group = !groups.Empty() ? groups.First() : null; Player looter = group ? Global.ObjAccessor.GetPlayer(creature, group.GetLooterGuid()) : tappers[0]; - Loot loot = new(creature.GetMap(), creature.GetGUID(), LootType.Corpse, dungeonEncounter != null ? group : null); - if (dungeonEncounter != null) - loot.SetDungeonEncounterId(dungeonEncounter.Id); - - uint lootid = creature.GetCreatureTemplate().LootId; - if (lootid != 0) - loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); - - if (creature.GetLootMode() > 0) - loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); - - if (group) - loot.NotifyLootList(creature.GetMap()); - - if (dungeonEncounter != null || groups.Empty()) - creature._loot = loot; // TODO: personal boss loot + { + creature.m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(dungeonEncounter.Id, creature.GetCreatureTemplate().LootId, + LootStorage.Creature, LootType.Corpse, creature, creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold, + (ushort)creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext(), tappers); + } else + { + Loot loot = new(creature.GetMap(), creature.GetGUID(), LootType.Corpse, dungeonEncounter != null ? group : null); + + uint lootid = creature.GetCreatureTemplate().LootId; + if (lootid != 0) + loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); + + if (creature.GetLootMode() > 0) + loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); + + if (group) + loot.NotifyLootList(creature.GetMap()); + creature.m_personalLoot[looter.GetGUID()] = loot; // trash mob loot is personal, generated with round robin rules - // Update round robin looter only if the creature had loot - if (!loot.IsLooted()) - foreach (Group tapperGroup in groups) - tapperGroup.UpdateLooterGuid(creature); + // Update round robin looter only if the creature had loot + if (!loot.IsLooted()) + foreach (Group tapperGroup in groups) + tapperGroup.UpdateLooterGuid(creature); + } } else { diff --git a/Source/Game/Groups/Group.cs b/Source/Game/Groups/Group.cs index f5db44c19..7ce87594c 100644 --- a/Source/Game/Groups/Group.cs +++ b/Source/Game/Groups/Group.cs @@ -40,7 +40,7 @@ namespace Game.Groups m_dungeonDifficulty = Difficulty.Normal; m_raidDifficulty = Difficulty.NormalRaid; m_legacyRaidDifficulty = Difficulty.Raid10N; - m_lootMethod = LootMethod.FreeForAll; + m_lootMethod = LootMethod.PersonalLoot; m_lootThreshold = ItemQuality.Uncommon; } @@ -120,9 +120,6 @@ namespace Game.Groups if (m_groupFlags.HasAnyFlag(GroupFlags.Raid)) _initRaidSubGroupsCounter(); - if (!IsLFGGroup()) - m_lootMethod = LootMethod.GroupLoot; - m_lootThreshold = ItemQuality.Uncommon; m_looterGuid = leaderGuid; @@ -246,7 +243,7 @@ namespace Game.Groups { m_groupFlags = (m_groupFlags | GroupFlags.Lfg | GroupFlags.LfgRestricted); m_groupCategory = GroupCategory.Instance; - m_lootMethod = LootMethod.GroupLoot; + m_lootMethod = LootMethod.PersonalLoot; if (!IsBGGroup() && !IsBFGroup()) { PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_TYPE); diff --git a/Source/Game/Handlers/GroupHandler.cs b/Source/Game/Handlers/GroupHandler.cs index b0cb7a946..d9c9c8efd 100644 --- a/Source/Game/Handlers/GroupHandler.cs +++ b/Source/Game/Handlers/GroupHandler.cs @@ -350,11 +350,12 @@ namespace Game [WorldPacketHandler(ClientOpcodes.SetLootMethod)] void HandleSetLootMethod(SetLootMethod packet) { + // not allowed to change + /* Group group = GetPlayer().GetGroup(); if (!group) - return; + return; - /** error handling **/ if (!group.IsLeader(GetPlayer().GetGUID())) return; @@ -383,6 +384,7 @@ namespace Game group.SetMasterLooterGuid(packet.LootMasterGUID); group.SetLootThreshold(packet.LootThreshold); group.SendUpdate(); + */ } [WorldPacketHandler(ClientOpcodes.MinimapPing)] diff --git a/Source/Game/Loot/Loot.cs b/Source/Game/Loot/Loot.cs index 4d373eae7..4d7190f13 100644 --- a/Source/Game/Loot/Loot.cs +++ b/Source/Game/Loot/Loot.cs @@ -52,6 +52,11 @@ namespace Game.Loots /// /// public bool AllowedForPlayer(Player player, Loot loot) + { + return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions); + } + + public static bool AllowedForPlayer(Player player, Loot loot, uint itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, List conditions) { // DB conditions check if (!Global.ConditionMgr.IsObjectMeetToConditions(player, conditions)) @@ -69,7 +74,7 @@ namespace Game.Loots return false; // Master looter can see all items even if the character can't loot them - if (loot.GetLootMethod() == LootMethod.MasterLoot && follow_loot_rules && player.GetGroup() != null && player.GetGroup().GetMasterLooterGuid() == player.GetGUID()) + if (loot != null && loot.GetLootMethod() == LootMethod.MasterLoot && follow_loot_rules && loot.GetLootMasterGUID() == player.GetGUID()) return true; // Don't allow loot for players without profession or those who already know the recipe @@ -88,24 +93,20 @@ namespace Game.Loots } } - // Don't allow to loot soulbound recipes that the player has already learned - if (pProto.GetClass() == ItemClass.Recipe && pProto.GetBonding() == ItemBondingType.OnAcquire) - { - foreach (var itemEffect in pProto.Effects) - { - if (itemEffect.TriggerType != ItemSpelltriggerType.OnLearn) - continue; - - if (player.HasSpell((uint)itemEffect.SpellID)) - return false; - } - } - // check quest requirements if (!pProto.FlagsCu.HasAnyFlag(ItemFlagsCustom.IgnoreQuestStatus) && ((needs_quest || (pProto.GetStartQuest() != 0 && player.GetQuestStatus(pProto.GetStartQuest()) != QuestStatus.None)) && !player.HasQuestForItem(itemid))) return false; + if (strictUsabilityCheck) + { + if ((pProto.IsWeapon() || pProto.IsArmor()) && !pProto.IsUsableByLootSpecialization(player, true)) + return false; + + if (player.CanRollNeedForItem(pProto, null, false) != InventoryResult.Ok) + return false; + } + return true; } @@ -286,7 +287,7 @@ namespace Game.Loots startLootRoll.Method = m_loot.GetLootMethod(); startLootRoll.ValidRolls = m_voteMask; // In NEED_BEFORE_GREED need disabled for non-usable item for player - if (m_loot.GetLootMethod() == LootMethod.NeedBeforeGreed && player.CanRollForItemInLFG(itemTemplate, m_map) != InventoryResult.Ok) + if (m_loot.GetLootMethod() == LootMethod.NeedBeforeGreed && player.CanRollNeedForItem(itemTemplate, m_map, true) != InventoryResult.Ok) startLootRoll.ValidRolls &= ~RollMask.Need; FillPacket(startLootRoll.Item); @@ -825,7 +826,7 @@ namespace Game.Loots _rolls.Remove(pair.Key); } - void FillNotNormalLootFor(Player player) + public void FillNotNormalLootFor(Player player) { ObjectGuid plguid = player.GetGUID(); _allowedLooters.Add(plguid); @@ -1079,6 +1080,8 @@ namespace Game.Loots public ObjectGuid GetGUID() { return _guid; } public ObjectGuid GetOwnerGUID() { return _owner; } + public ItemContext GetItemContext() { return _itemContext; } + public void SetItemContext(ItemContext context) { _itemContext = context; } public LootMethod GetLootMethod() { return _lootMethod; } public ObjectGuid GetLootMasterGUID() { return _lootMaster; } diff --git a/Source/Game/Loot/LootManager.cs b/Source/Game/Loot/LootManager.cs index 28cf86abd..a68f6b2fb 100644 --- a/Source/Game/Loot/LootManager.cs +++ b/Source/Game/Loot/LootManager.cs @@ -65,6 +65,42 @@ namespace Game.Loots LoadLootTemplates_Reference(); } + public static Dictionary GenerateDungeonEncounterPersonalLoot(uint dungeonEncounterId, uint lootId, LootStore store, + LootType type, WorldObject lootOwner, uint minMoney, uint maxMoney, ushort lootMode, ItemContext context, List tappers) + { + Dictionary tempLoot = new(); + + foreach (Player tapper in tappers) + { + if (tapper.IsLockedToDungeonEncounter(dungeonEncounterId)) + continue; + + Loot loot = new(lootOwner.GetMap(), lootOwner.GetGUID(), type, null); + loot.SetItemContext(context); + loot.SetDungeonEncounterId(dungeonEncounterId); + loot.GenerateMoneyLoot(minMoney, maxMoney); + + tempLoot[tapper] = loot; + } + + LootTemplate tab = store.GetLootFor(lootId); + if (tab != null) + tab.ProcessPersonalLoot(tempLoot, store.IsRatesAllowed(), lootMode); + + Dictionary personalLoot = new(); + foreach (var (looter, loot) in tempLoot) + { + loot.FillNotNormalLootFor(looter); + + if (loot.IsLooted()) + continue; + + personalLoot[looter.GetGUID()] = loot; + } + + return personalLoot; + } + public static void LoadLootTemplates_Creature() { Log.outInfo(LogFilter.ServerLoading, "Loading creature loot templates..."); @@ -719,7 +755,7 @@ namespace Game.Loots Entries.Add(item); } - public void Process(Loot loot, bool rate, ushort lootMode, byte groupId) + public void Process(Loot loot, bool rate, ushort lootMode, byte groupId, Player personalLooter = null) { if (groupId != 0) // Group reference uses own processing of the group { @@ -729,7 +765,7 @@ namespace Game.Loots if (Groups[groupId - 1] == null) return; - Groups[groupId - 1].Process(loot, lootMode); + Groups[groupId - 1].Process(loot, lootMode, personalLooter); return; } @@ -750,19 +786,156 @@ namespace Game.Loots uint maxcount = (uint)(item.maxcount * WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferencedAmount)); for (uint loop = 0; loop < maxcount; ++loop) // Ref multiplicator - Referenced.Process(loot, rate, lootMode, item.groupid); + Referenced.Process(loot, rate, lootMode, item.groupid, personalLooter); + } + else + { + // 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)) + loot.AddItem(item); } - else // Plain entries (not a reference, not grouped) - loot.AddItem(item); // Chance is already checked, just add } // Now processing groups foreach (var group in Groups.Values) { if (group != null) - group.Process(loot, lootMode); + group.Process(loot, lootMode, personalLooter); } } + + public void ProcessPersonalLoot(Dictionary personalLoot, bool rate, ushort lootMode) + { + List getLootersForItem(Func predicate) + { + List lootersForItem = new(); + foreach (var (looter, loot) in personalLoot) + { + if (predicate(looter)) + lootersForItem.Add(looter); + } + return lootersForItem; + } + + // Rolling non-grouped items + foreach (LootStoreItem item in Entries) + { + if ((item.lootmode & lootMode) == 0) // Do not add if mode mismatch + continue; + + if (!item.Roll(rate)) + continue; // Bad luck for the entry + + if (item.reference > 0) // References processing + { + LootTemplate referenced = LootStorage.Reference.GetLootFor(item.reference); + if (referenced == null) + continue; // Error message already printed at loading stage + + uint maxcount = (uint)((float)item.maxcount * WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferencedAmount)); + List gotLoot = new(); + for (uint loop = 0; loop < maxcount; ++loop) // Ref multiplicator + { + var lootersForItem = getLootersForItem(looter => referenced.HasDropForPlayer(looter, item.groupid, true)); + + // nobody can loot this, skip it + if (lootersForItem.Empty()) + break; + + var newEnd = lootersForItem.RemoveAll(looter => gotLoot.Contains(looter)); + + if (lootersForItem.Count == newEnd) + { + // if we run out of looters this means that there are more items dropped than players + // start a new cycle adding one item to everyone + gotLoot.Clear(); + } + else + lootersForItem.RemoveRange(newEnd, lootersForItem.Count - newEnd); + + Player chosenLooter = lootersForItem.SelectRandom(); + referenced.Process(personalLoot[chosenLooter], rate, lootMode, item.groupid, chosenLooter); + gotLoot.Add(chosenLooter); + } + } + else + { + // 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); + }); + + if (!lootersForItem.Empty()) + { + Player chosenLooter = lootersForItem.SelectRandom(); + personalLoot[chosenLooter].AddItem(item); + } + } + } + + // Now processing groups + foreach (LootGroup group in Groups.Values) + { + if (group != null) + { + var lootersForGroup = getLootersForItem(looter => group.HasDropForPlayer(looter, true)); + + if (!lootersForGroup.Empty()) + { + Player chosenLooter = lootersForGroup.SelectRandom(); + group.Process(personalLoot[chosenLooter], lootMode); + } + } + } + } + + // True if template includes at least 1 drop for the player + bool HasDropForPlayer(Player player, byte groupId, bool strictUsabilityCheck) + { + if (groupId != 0) // Group reference + { + if (groupId > Groups.Count) + return false; // Error message already printed at loading stage + + if (Groups[groupId - 1] == null) + return false; + + return Groups[groupId - 1].HasDropForPlayer(player, strictUsabilityCheck); + } + + // Checking non-grouped entries + foreach (LootStoreItem lootStoreItem in Entries) + { + if (lootStoreItem.reference > 0) // References processing + { + LootTemplate referenced = LootStorage.Reference.GetLootFor(lootStoreItem.reference); + if (referenced == null) + continue; // Error message already printed at loading stage + if (referenced.HasDropForPlayer(player, lootStoreItem.groupid, strictUsabilityCheck)) + return true; + } + else if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest, + !lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), + strictUsabilityCheck, lootStoreItem.conditions)) + return true; // active quest drop found + } + + // Now checking groups + foreach (LootGroup group in Groups.Values) + if (group != null && group.HasDropForPlayer(player, strictUsabilityCheck)) + return true; + + return false; + } + public void CopyConditions(List conditions) { foreach (var i in Entries) @@ -859,7 +1032,7 @@ namespace Game.Loots { // Checking group chances foreach (var group in Groups) - group.Value.Verify(lootstore, id, (byte)(group.Key + 1)); + group.Value.Verify(lootstore, id, (byte)(group.Key + 1)); // @todo References validity checks } @@ -983,9 +1156,9 @@ namespace Game.Loots return false; } - public void Process(Loot loot, ushort lootMode) + public void Process(Loot loot, ushort lootMode, Player personalLooter = null) { - LootStoreItem item = Roll(loot, lootMode); + LootStoreItem item = Roll(lootMode, personalLooter); if (item != null) loot.AddItem(item); } @@ -1057,10 +1230,10 @@ namespace Game.Loots LootStoreItemList ExplicitlyChanced = new(); // Entries with chances defined in DB LootStoreItemList EqualChanced = new(); // Zero chances - every entry takes the same chance - LootStoreItem Roll(Loot loot, ushort lootMode) + LootStoreItem Roll(ushort lootMode, Player personalLooter = null) { LootStoreItemList possibleLoot = ExplicitlyChanced; - possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check); + possibleLoot.RemoveAll(new LootGroupInvalidSelector(lootMode, personalLooter).Check); if (!possibleLoot.Empty()) // First explicitly chanced entries are checked { @@ -1078,32 +1251,54 @@ namespace Game.Loots } possibleLoot = EqualChanced; - possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check); + possibleLoot.RemoveAll(new LootGroupInvalidSelector(lootMode, personalLooter).Check); if (!possibleLoot.Empty()) // If nothing selected yet - an item is taken from equal-chanced part return possibleLoot.SelectRandom(); return null; // Empty drop from the group } + + 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)) + 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)) + return true; + + return false; + } } } public struct LootGroupInvalidSelector { - public LootGroupInvalidSelector(Loot loot, ushort lootMode) + public LootGroupInvalidSelector(ushort lootMode, Player personalLooter) { - _loot = loot; _lootMode = lootMode; + _personalLooter = personalLooter; } public bool Check(LootStoreItem item) { - if (!Convert.ToBoolean(item.lootmode & _lootMode)) + if ((item.lootmode & _lootMode) == 0) + return true; + + if (_personalLooter && !LootItem.AllowedForPlayer(_personalLooter, null, item.itemid, item.needs_quest, + !item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules), + true, item.conditions)) return true; return false; } - Loot _loot; ushort _lootMode; + Player _personalLooter; } }