From b76c9571d2773c5d60eb3c2f4db2c99df75c8b12 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Sun, 18 Sep 2022 17:57:08 -0400 Subject: [PATCH] Core/Loot: Move loot rolls from Group to Loot Port From (https://github.com/TrinityCore/TrinityCore/commit/3ef5079feeedfdafc9d3c1d9f865e96dbc77ecc8) --- Source/Framework/Constants/LootConst.cs | 9 +- Source/Game/Entities/Corpse.cs | 7 + .../Game/Entities/Creature/Creature.Fields.cs | 2 - Source/Game/Entities/Creature/Creature.cs | 15 +- Source/Game/Entities/GameObject/GameObject.cs | 17 +- Source/Game/Entities/Player/Player.Fields.cs | 1 + Source/Game/Entities/Player/Player.Groups.cs | 3 +- Source/Game/Entities/Player/Player.Items.cs | 98 +-- Source/Game/Entities/Player/Player.cs | 9 + Source/Game/Entities/Unit/Unit.Combat.cs | 27 +- Source/Game/Groups/Group.cs | 662 +------------- Source/Game/Handlers/CharacterHandler.cs | 1 - Source/Game/Handlers/GroupHandler.cs | 20 - Source/Game/Handlers/LootHandler.cs | 28 +- Source/Game/Loot/Loot.cs | 813 +++++++++++++++--- Source/Game/Loot/LootItemStorage.cs | 8 +- Source/Game/Mails/MailDraft.cs | 2 +- Source/Game/Networking/Packets/LootPackets.cs | 12 +- Source/Game/Server/WorldSession.cs | 1 - 19 files changed, 803 insertions(+), 932 deletions(-) diff --git a/Source/Framework/Constants/LootConst.cs b/Source/Framework/Constants/LootConst.cs index 5f6713943..82b4b1345 100644 --- a/Source/Framework/Constants/LootConst.cs +++ b/Source/Framework/Constants/LootConst.cs @@ -17,16 +17,14 @@ namespace Framework.Constants { - public enum RollType + public enum RollVote { Pass = 0, Need = 1, Greed = 2, Disenchant = 3, NotEmitedYet = 4, - NotValid = 5, - - MaxTypes = 4, + NotValid = 5 } public enum RollMask @@ -43,8 +41,10 @@ namespace Framework.Constants public enum LootMethod { FreeForAll = 0, + RoundRobin = 1, MasterLoot = 2, GroupLoot = 3, + NeedBeforeGreed = 4, PersonalLoot = 5 } @@ -64,6 +64,7 @@ namespace Framework.Constants Group = 1, Master = 2, Restricted = 3, + RoundRobin = 4, Owner = 5, None = 6 } diff --git a/Source/Game/Entities/Corpse.cs b/Source/Game/Entities/Corpse.cs index 4829fb46d..24f48a4dc 100644 --- a/Source/Game/Entities/Corpse.cs +++ b/Source/Game/Entities/Corpse.cs @@ -92,6 +92,13 @@ namespace Game.Entities return true; } + public override void Update(uint diff) + { + base.Update(diff); + + loot?.Update(); + } + public void SaveToDB() { // prevent DB data inconsistence problems and duplicates diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index 2b03f5d79..5af7ac601 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -91,8 +91,6 @@ namespace Game.Entities List m_vendorItemCounts = new(); public Loot loot; - public uint m_groupLootTimer; // (msecs)timer used for group loot - public ObjectGuid lootingGroupLowGUID; // used to find group which is looting corpse ObjectGuid m_lootRecipient; ObjectGuid m_lootRecipientGroup; } diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index a389a8669..3adb76b27 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -507,20 +507,9 @@ namespace Game.Entities if (IsEngaged()) AIUpdateTick(diff); - if (loot != null && m_groupLootTimer != 0 && !lootingGroupLowGUID.IsEmpty()) - { - if (m_groupLootTimer <= diff) - { - Group group = Global.GroupMgr.GetGroupByGUID(lootingGroupLowGUID); - if (group) - group.EndRoll(loot, GetMap()); + loot?.Update(); - m_groupLootTimer = 0; - lootingGroupLowGUID.Clear(); - } - else m_groupLootTimer -= diff; - } - else if (m_corpseRemoveTime <= GameTime.GetGameTime()) + if (m_corpseRemoveTime <= GameTime.GetGameTime()) { RemoveCorpse(false); Log.outDebug(LogFilter.Unit, "Removing corpse... {0} ", GetEntry()); diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index b7515f9c1..a66c1c3db 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -697,20 +697,7 @@ namespace Game.Entities } break; case GameObjectTypes.Chest: - if (loot != null && m_groupLootTimer != 0) - { - if (m_groupLootTimer <= diff) - { - Group group = Global.GroupMgr.GetGroupByGUID(lootingGroupLowGUID); - if (group) - group.EndRoll(loot, GetMap()); - - m_groupLootTimer = 0; - lootingGroupLowGUID.Clear(); - } - else - m_groupLootTimer -= diff; - } + loot?.Update(); // Non-consumable chest was partially looted and restock time passed, restock all loot now if (GetGoInfo().Chest.consumable == 0 && GameTime.GetGameTime() >= m_restockTime) @@ -3423,8 +3410,6 @@ namespace Game.Entities ObjectGuid m_lootRecipientGroup; LootModes m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable uint m_lootGenerationTime; - public uint m_groupLootTimer; // (msecs)timer used for group loot - public ObjectGuid lootingGroupLowGUID; // used to find group which is looting long m_packedRotation; Quaternion m_localRotation; public Position StationaryPosition { get; set; } diff --git a/Source/Game/Entities/Player/Player.Fields.cs b/Source/Game/Entities/Player/Player.Fields.cs index 5ba8185da..077fb349b 100644 --- a/Source/Game/Entities/Player/Player.Fields.cs +++ b/Source/Game/Entities/Player/Player.Fields.cs @@ -240,6 +240,7 @@ namespace Game.Entities SceneMgr m_sceneMgr; Dictionary m_AELootView = new(); + List m_lootRolls = new(); // loot rolls waiting for answer CUFProfile[] _CUFProfiles = new CUFProfile[PlayerConst.MaxCUFProfiles]; float[] m_powerFraction = new float[(int)PowerType.MaxPerClass]; diff --git a/Source/Game/Entities/Player/Player.Groups.cs b/Source/Game/Entities/Player/Player.Groups.cs index 7082d8947..15d0b0b0d 100644 --- a/Source/Game/Entities/Player/Player.Groups.cs +++ b/Source/Game/Entities/Player/Player.Groups.cs @@ -71,7 +71,8 @@ namespace Game.Entities if (state == LfgState.FinishedDungeon) return PartyResult.PartyLfgBootDungeonComplete; - if (grp.IsRollLootActive()) + Player player = Global.ObjAccessor.FindConnectedPlayer(guidMember); + if (!player.m_lootRolls.Empty()) return PartyResult.PartyLfgBootLootRolls; // @todo Should also be sent when anyone has recently left combat, with an aprox ~5 seconds timer. diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index 0e2126086..bdf5ef77c 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -2949,13 +2949,12 @@ namespace Game.Entities } } - public InventoryResult CanRollForItemInLFG(ItemTemplate proto, WorldObject lootedObject) + public InventoryResult CanRollForItemInLFG(ItemTemplate proto, Map map) { if (!GetGroup() || !GetGroup().IsLFGGroup()) return InventoryResult.Ok; // not in LFG group // check if looted object is inside the lfg dungeon - Map map = lootedObject.GetMap(); if (!Global.LFGMgr.InLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID())) return InventoryResult.Ok; @@ -3946,7 +3945,7 @@ namespace Game.Entities } } - void ApplyItemLootedSpell(Item item, bool apply) + public void ApplyItemLootedSpell(Item item, bool apply) { if (item.GetTemplate().HasFlag(ItemFlags.Legacy)) return; @@ -4104,6 +4103,18 @@ namespace Game.Entities return null; } + public LootRoll GetLootRoll(ObjectGuid lootObjectGuid, byte lootListId) + { + return m_lootRolls.Find(roll => roll.IsLootItem(lootObjectGuid, lootListId)); + } + + public void AddLootRoll(LootRoll roll) { m_lootRolls.Add(roll); } + + public void RemoveLootRoll(LootRoll roll) + { + m_lootRolls.Remove(roll); + } + //Inventory public bool IsInventoryPos(ushort pos) { @@ -5881,33 +5892,13 @@ namespace Game.Entities } } public void AutoStoreLoot(uint loot_id, LootStore store, ItemContext context = 0, bool broadcast = false, bool createdByPlayer = false) { AutoStoreLoot(ItemConst.NullBag, ItemConst.NullSlot, loot_id, store, context, broadcast); } + void AutoStoreLoot(byte bag, byte slot, uint loot_id, LootStore store, ItemContext context = 0, bool broadcast = false, bool createdByPlayer = false) { - Loot loot = new(null, ObjectGuid.Empty, LootType.None, LootMethod.FreeForAll); + Loot loot = new(null, ObjectGuid.Empty, LootType.None, null); loot.FillLoot(loot_id, store, this, true, false, LootModes.Default, context); - uint max_slot = loot.GetMaxSlotInLootFor(this); - for (uint i = 0; i < max_slot; ++i) - { - LootItem lootItem = loot.LootItemInSlot(i, this); - - List dest = new(); - InventoryResult msg = CanStoreNewItem(bag, slot, dest, lootItem.itemid, lootItem.count); - if (msg != InventoryResult.Ok && slot != ItemConst.NullSlot) - msg = CanStoreNewItem(bag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); - if (msg != InventoryResult.Ok && bag != ItemConst.NullBag) - msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); - if (msg != InventoryResult.Ok) - { - SendEquipError(msg, null, null, lootItem.itemid); - continue; - } - - Item pItem = StoreNewItem(dest, lootItem.itemid, true, lootItem.randomBonusListId, null, lootItem.context, lootItem.BonusListIDs); - SendNewItem(pItem, lootItem.count, false, createdByPlayer, broadcast); - ApplyItemLootedSpell(pItem, true); - } - + loot.AutoStore(this, bag, slot, broadcast, createdByPlayer); Unit.ProcSkillsAndAuras(this, null, new ProcFlagsInit(ProcFlags.Looted), new ProcFlagsInit(ProcFlags.None), ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); } @@ -6052,7 +6043,7 @@ namespace Game.Entities // 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.itemIndex); + Global.LootItemStorage.RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item.itemid, item.count, item.LootListId); ApplyItemLootedSpell(newitem, true); } @@ -6092,7 +6083,7 @@ namespace Game.Entities // Now we must make bones lootable, and send player loot bones.SetCorpseDynamicFlag(CorpseDynFlags.Lootable); - bones.loot = new Loot(GetMap(), bones.GetGUID(), LootType.Insignia, looterPlr.GetGroup() != null ? looterPlr.GetGroup().GetLootMethod() : LootMethod.FreeForAll); + bones.loot = new Loot(GetMap(), bones.GetGUID(), LootType.Insignia, looterPlr.GetGroup()); // For AV Achievement Battleground bg = GetBattleground(); @@ -6192,7 +6183,7 @@ namespace Game.Entities Group group = GetGroup(); bool groupRules = (group != null && go.GetGoInfo().type == GameObjectTypes.Chest && go.GetGoInfo().Chest.usegrouplootrules != 0); - loot = new Loot(GetMap(), guid, loot_type, groupRules ? group.GetLootMethod() : LootMethod.FreeForAll); + loot = new Loot(GetMap(), guid, loot_type, groupRules ? group : null); if (go.GetMap().Is25ManRaid()) loot.maxDuplicates = 3; @@ -6224,22 +6215,6 @@ namespace Game.Entities else if (loot_type == LootType.FishingJunk) go.GetFishLootJunk(loot, this); - if (go.GetGoInfo().type == GameObjectTypes.Chest && go.GetGoInfo().Chest.usegrouplootrules != 0) - { - switch (group.GetLootMethod()) - { - case LootMethod.GroupLoot: - // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin - group.GroupLoot(loot, go); - break; - case LootMethod.MasterLoot: - group.MasterLoot(loot, go); - break; - default: - break; - } - } - go.SetLootState(LootState.Activated, this); } @@ -6253,6 +6228,9 @@ namespace Game.Entities case LootMethod.MasterLoot: permission = group.GetMasterLooterGuid() == GetGUID() ? PermissionTypes.Master : PermissionTypes.Restricted; break; + case LootMethod.RoundRobin: + permission = PermissionTypes.RoundRobin; + break; case LootMethod.FreeForAll: permission = PermissionTypes.All; break; @@ -6284,7 +6262,7 @@ namespace Game.Entities if (!item.m_lootGenerated && !Global.LootItemStorage.LoadStoredLoot(item, this)) { item.m_lootGenerated = true; - loot = new Loot(GetMap(), guid, loot_type, LootMethod.FreeForAll); + loot = new Loot(GetMap(), guid, loot_type, null); item.loot = loot; switch (loot_type) @@ -6355,7 +6333,7 @@ namespace Game.Entities { creature.StartPickPocketRefillTimer(); - loot = new Loot(GetMap(), creature.GetGUID(), LootType.Pickpocketing, LootMethod.FreeForAll); + loot = new Loot(GetMap(), creature.GetGUID(), LootType.Pickpocketing, null); creature.loot = loot; uint lootid = creature.GetCreatureTemplate().PickPocketId; if (lootid != 0) @@ -6392,27 +6370,6 @@ namespace Game.Entities return; } - if (loot.loot_type == LootType.None) - { - // for creature, loot is filled when creature is killed. - Group group = creature.GetLootRecipientGroup(); - if (group) - { - switch (loot.GetLootMethod()) - { - case LootMethod.GroupLoot: - // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin - group.GroupLoot(loot, creature); - break; - case LootMethod.MasterLoot: - group.MasterLoot(loot, creature); - break; - default: - break; - } - } - } - if (loot.loot_type == LootType.Skinning) { loot_type = LootType.Skinning; @@ -6443,6 +6400,9 @@ namespace Game.Entities case LootMethod.FreeForAll: permission = PermissionTypes.All; break; + case LootMethod.RoundRobin: + permission = PermissionTypes.RoundRobin; + break; default: permission = PermissionTypes.Group; break; @@ -6475,7 +6435,7 @@ namespace Game.Entities SendPacket(packet); // add 'this' player as one of the players that are looting 'loot' - loot.AddLooter(GetGUID()); + loot.OnLootOpened(GetMap(), GetGUID()); m_AELootView[loot.GetGUID()] = loot; if (loot_type == LootType.Corpse && !guid.IsItem()) diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 293bc981c..742740ed4 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -757,6 +757,7 @@ namespace Game.Entities UnsummonPetTemporaryIfAny(); ClearComboPoints(); GetSession().DoLootReleaseAll(); + m_lootRolls.Clear(); Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); } @@ -3140,7 +3141,15 @@ namespace Game.Entities case LootMethod.MasterLoot: case LootMethod.FreeForAll: return true; + case LootMethod.RoundRobin: + // may only loot if the player is the loot roundrobin player + // or if there are free/quest/conditional item for the player + if (loot.roundRobinPlayer.IsEmpty() || loot.roundRobinPlayer == GetGUID()) + return true; + + return loot.HasItemFor(this); case LootMethod.GroupLoot: + case LootMethod.NeedBeforeGreed: // may only loot if the player is the loot roundrobin player // or item over threshold (so roll(s) can be launched) // or if there are free/quest/conditional item for the player diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index f5bdcfa94..e09ce8bce 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -752,7 +752,6 @@ namespace Game.Entities Player looter = player; var group = player.GetGroup(); - bool hasLooterGuid = false; if (group) { group.BroadcastPacket(partyKillLog, group.GetMemberGroup(player.GetGUID()) != 0); @@ -764,10 +763,7 @@ namespace Game.Entities { looter = Global.ObjAccessor.FindPlayer(group.GetLooterGuid()); if (looter) - { - hasLooterGuid = true; creature.SetLootRecipient(looter); // update creature loot recipient to the allowed looter. - } } } } @@ -777,7 +773,7 @@ namespace Game.Entities // Generate loot before updating looter if (creature) { - creature.loot = new Loot(creature.GetMap(), creature.GetGUID(), LootType.Corpse, group != null ? group.GetLootMethod() : LootMethod.FreeForAll); + creature.loot = new Loot(creature.GetMap(), creature.GetGUID(), LootType.Corpse, group); Loot loot = creature.loot; if (creature.GetMap().Is25ManRaid()) loot.maxDuplicates = 3; @@ -789,24 +785,11 @@ namespace Game.Entities if (creature.GetLootMode() > 0) loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); - if (group) - { - if (hasLooterGuid) - group.SendLooter(creature, looter); - else - group.SendLooter(creature, null); + loot.NotifyLootList(creature.GetMap()); - // Update round robin looter only if the creature had loot - if (!loot.Empty()) - group.UpdateLooterGuid(creature); - } - else - { - LootList lootList = new(); - lootList.Owner = creature.GetGUID(); - lootList.LootObj = creature.loot.GetGUID(); - player.SendMessageToSet(lootList, true); - } + // Update round robin looter only if the creature had loot + if (group != null && !loot.Empty()) + group.UpdateLooterGuid(creature); } player.RewardPlayerAndGroupAtKill(victim, false); diff --git a/Source/Game/Groups/Group.cs b/Source/Game/Groups/Group.cs index e70e1f660..3a32810e6 100644 --- a/Source/Game/Groups/Group.cs +++ b/Source/Game/Groups/Group.cs @@ -532,9 +532,6 @@ namespace Game.Groups } } - if (m_maxEnchantingLevel < player.GetSkillValue(SkillType.Enchanting)) - m_maxEnchantingLevel = player.GetSkillValue(SkillType.Enchanting); - return true; } @@ -603,33 +600,6 @@ namespace Game.Groups DelinkMember(guid); } - // Reevaluate group enchanter if the leaving player had enchanting skill or the player is offline - if (!player || player.GetSkillValue(SkillType.Enchanting) != 0) - ResetMaxEnchantingLevel(); - - // Remove player from loot rolls - foreach (var roll in RollId) - { - if (!roll.playerVote.ContainsKey(guid)) - continue; - - var vote = roll.playerVote[guid]; - if (vote == RollType.Greed || vote == RollType.Disenchant) - --roll.totalGreed; - else if (vote == RollType.Need) - --roll.totalNeed; - else if (vote == RollType.Pass) - --roll.totalPass; - - if (vote != RollType.NotValid) - --roll.totalPlayersRolling; - - roll.playerVote.Remove(guid); - - if (roll.totalPass + roll.totalNeed + roll.totalGreed >= roll.totalPlayersRolling) - CountTheRoll(roll, null); - } - // Update subgroups var slot = _getMemberSlot(guid); if (slot != null) @@ -829,7 +799,7 @@ namespace Game.Groups _homebindIfInstance(player); } - RollId.Clear(); + m_memberSlots.Clear(); RemoveAllInvites(); @@ -862,541 +832,6 @@ namespace Game.Groups Global.GroupMgr.RemoveGroup(this); } - void SendLootStartRollToPlayer(uint countDown, uint mapId, Player p, bool canNeed, Roll r) - { - StartLootRoll startLootRoll = new(); - startLootRoll.LootObj = r.GetTarget().GetGUID(); - startLootRoll.MapID = (int)mapId; - startLootRoll.RollTime = countDown; - startLootRoll.ValidRolls = r.rollTypeMask; - if (!canNeed) - startLootRoll.ValidRolls &= ~RollMask.Need; - startLootRoll.Method = GetLootMethod(); - r.FillPacket(startLootRoll.Item); - - ItemDisenchantLootRecord disenchant = r.GetItemDisenchantLoot(p); - if (disenchant != null) - if (m_maxEnchantingLevel >= disenchant.SkillRequired) - startLootRoll.ValidRolls |= RollMask.Disenchant; - - p.SendPacket(startLootRoll); - } - - void SendLootRoll(ObjectGuid playerGuid, int rollNumber, RollType rollType, Roll roll, bool autoPass = false) - { - LootRollBroadcast lootRoll = new(); - lootRoll.LootObj = roll.GetTarget().GetGUID(); - lootRoll.Player = playerGuid; - lootRoll.Roll = rollNumber; - lootRoll.RollType = rollType; - lootRoll.Autopassed = autoPass; - roll.FillPacket(lootRoll.Item); - - foreach (var pair in roll.playerVote) - { - Player p = Global.ObjAccessor.FindConnectedPlayer(pair.Key); - if (!p || !p.GetSession()) - continue; - - if (pair.Value != RollType.NotValid) - p.SendPacket(lootRoll); - } - } - - void SendLootRollWon(ObjectGuid winnerGuid, int rollNumber, RollType rollType, Roll roll) - { - LootRollWon lootRollWon = new(); - lootRollWon.LootObj = roll.GetTarget().GetGUID(); - lootRollWon.Winner = winnerGuid; - lootRollWon.Roll = rollNumber; - lootRollWon.RollType = rollType; - roll.FillPacket(lootRollWon.Item); - lootRollWon.MainSpec = true; // offspec rolls not implemented - - foreach (var pair in roll.playerVote) - { - Player p = Global.ObjAccessor.FindConnectedPlayer(pair.Key); - if (!p || !p.GetSession()) - continue; - - if (pair.Value != RollType.NotValid) - p.SendPacket(lootRollWon); - } - } - - void SendLootAllPassed(Roll roll) - { - LootAllPassed lootAllPassed = new(); - lootAllPassed.LootObj = roll.GetTarget().GetGUID(); - roll.FillPacket(lootAllPassed.Item); - - foreach (var pair in roll.playerVote) - { - Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); - if (!player || !player.GetSession()) - continue; - - if (pair.Value != RollType.NotValid) - player.SendPacket(lootAllPassed); - } - } - - void SendLootRollsComplete(Roll roll) - { - LootRollsComplete lootRollsComplete = new(); - lootRollsComplete.LootObj = roll.GetTarget().GetGUID(); - lootRollsComplete.LootListID = (byte)(roll.itemSlot + 1); - - foreach (var pair in roll.playerVote) - { - Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); - if (!player || !player.GetSession()) - continue; - - if (pair.Value != RollType.NotValid) - player.SendPacket(lootRollsComplete); - } - } - - // notify group members which player is the allowed looter for the given creature - public void SendLooter(Creature creature, Player groupLooter) - { - Cypher.Assert(creature); - - LootList lootList = new(); - lootList.Owner = creature.GetGUID(); - lootList.LootObj = creature.loot.GetGUID(); - - if (creature.loot.GetLootMethod() == LootMethod.MasterLoot && creature.loot.HasOverThresholdItem()) - lootList.Master = GetMasterLooterGuid(); - - if (groupLooter) - lootList.RoundRobinWinner = groupLooter.GetGUID(); - - BroadcastPacket(lootList, false); - } - - public bool CanRollOnItem(LootItem item, Player player) - { - // Players can't roll on unique items if they already reached the maximum quantity of that item - ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); - if (proto == null) - return false; - - uint itemCount = player.GetItemCount(item.itemid); - if (proto.GetMaxCount() > 0 && itemCount >= proto.GetMaxCount()) - return false; - - if (!item.AllowedForPlayer(player)) - return false; - - return true; - } - - public void GroupLoot(Loot loot, WorldObject lootedObject) - { - byte itemSlot = 0; - for (var i = 0; i < loot.items.Count; ++i, ++itemSlot) - { - LootItem lootItem = loot.items[i]; - if (lootItem.freeforall) - continue; - - ItemTemplate item = Global.ObjectMgr.GetItemTemplate(lootItem.itemid); - if (item == null) - continue; - - //roll for over-threshold item if it's one-player loot - if (item.GetQuality() >= m_lootThreshold) - { - Roll r = new(lootItem); - - for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.Next()) - { - Player playerToRoll = refe.GetSource(); - if (!playerToRoll || playerToRoll.GetSession() == null) - continue; - - if (playerToRoll.IsAtGroupRewardDistance(lootedObject)) - { - r.totalPlayersRolling++; - RollType vote = playerToRoll.GetPassOnGroupLoot() ? RollType.Pass : RollType.NotEmitedYet; - if (!CanRollOnItem(lootItem, playerToRoll)) - { - vote = RollType.Pass; - r.totalPass++; // Can't broadcast the pass now. need to wait until all rolling players are known - } - - r.playerVote[playerToRoll.GetGUID()] = vote; - } - } - - if (r.totalPlayersRolling > 0) - { - r.SetLoot(loot); - r.itemSlot = itemSlot; - - if (item.HasFlag(ItemFlags2.CanOnlyRollGreed)) - r.rollTypeMask &= ~RollMask.Need; - - loot.items[itemSlot].is_blocked = true; - - //Broadcast Pass and Send Rollstart - foreach (var pair in r.playerVote) - { - Player p = Global.ObjAccessor.FindPlayer(pair.Key); - if (!p || p.GetSession() == null) - continue; - - if (pair.Value == RollType.Pass) - SendLootRoll(p.GetGUID(), -1, RollType.Pass, r, true); - else - SendLootStartRollToPlayer(60000, lootedObject.GetMapId(), p, p.CanRollForItemInLFG(item, lootedObject) == InventoryResult.Ok, r); - } - - RollId.Add(r); - Creature creature = lootedObject.ToCreature(); - GameObject go = lootedObject.ToGameObject(); - if (creature) - { - creature.m_groupLootTimer = 60000; - creature.lootingGroupLowGUID = GetGUID(); - } - else if (go) - { - go.m_groupLootTimer = 60000; - go.lootingGroupLowGUID = GetGUID(); - } - } - } - else - lootItem.is_underthreshold = true; - } - - foreach (var i in loot.quest_items) - { - if (!i.follow_loot_rules) - continue; - - ItemTemplate item = Global.ObjectMgr.GetItemTemplate(i.itemid); - Roll r = new(i); - - for (var refe = GetFirstMember(); refe != null; refe = refe.Next()) - { - Player playerToRoll = refe.GetSource(); - if (!playerToRoll || playerToRoll.GetSession() == null) - continue; - - if (playerToRoll.IsAtGroupRewardDistance(lootedObject)) - { - r.totalPlayersRolling++; - RollType vote = RollType.NotEmitedYet; - if (!CanRollOnItem(i, playerToRoll)) - { - vote = RollType.Pass; - ++r.totalPass; - } - r.playerVote[playerToRoll.GetGUID()] = vote; - } - } - - if (r.totalPlayersRolling > 0) - { - r.SetLoot(loot); - r.itemSlot = itemSlot; - - loot.quest_items[itemSlot - loot.items.Count].is_blocked = true; - - //Broadcast Pass and Send Rollstart - foreach (var pair in r.playerVote) - { - Player p = Global.ObjAccessor.FindPlayer(pair.Key); - if (!p || p.GetSession() == null) - continue; - - if (pair.Value == RollType.Pass) - SendLootRoll(p.GetGUID(), -1, RollType.Pass, r, true); - else - SendLootStartRollToPlayer(60000, lootedObject.GetMapId(), p, p.CanRollForItemInLFG(item, lootedObject) == InventoryResult.Ok, r); - } - - RollId.Add(r); - - Creature creature = lootedObject.ToCreature(); - GameObject go = lootedObject.ToGameObject(); - if (creature) - { - creature.m_groupLootTimer = 60000; - creature.lootingGroupLowGUID = GetGUID(); - } - else if (go) - { - go.m_groupLootTimer = 60000; - go.lootingGroupLowGUID = GetGUID(); - } - } - } - } - - public void MasterLoot(Loot loot, WorldObject pLootedObject) - { - MasterLootCandidateList masterLootCandidateList = new(); - masterLootCandidateList.LootObj = loot.GetGUID(); - - for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.Next()) - { - Player looter = refe.GetSource(); - if (!looter.IsInWorld) - continue; - - if (looter.IsAtGroupRewardDistance(pLootedObject)) - masterLootCandidateList.Players.Add(looter.GetGUID()); - } - - masterLootCandidateList.Write(); - - for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.Next()) - { - Player looter = refe.GetSource(); - if (looter.IsAtGroupRewardDistance(pLootedObject)) - looter.SendPacket(masterLootCandidateList); - } - } - - public void CountRollVote(ObjectGuid playerGuid, ObjectGuid lootObjectGuid, byte lootListId, RollType choice) - { - var roll = GetRoll(lootObjectGuid, lootListId); - if (roll == null) - return; - - // this condition means that player joins to the party after roll begins - if (!roll.playerVote.ContainsKey(playerGuid)) - return; - - if (roll.GetLoot() != null) - if (roll.GetLoot().items.Empty()) - return; - - RollType rollType = RollType.MaxTypes; - switch (choice) - { - case RollType.Pass: // Player choose pass - SendLootRoll(playerGuid, -1, RollType.Pass, roll); - ++roll.totalPass; - rollType = RollType.Pass; - break; - case RollType.Need: // player choose Need - SendLootRoll(playerGuid, 0, RollType.Need, roll); - ++roll.totalNeed; - rollType = RollType.Need; - break; - case RollType.Greed: // player choose Greed - SendLootRoll(playerGuid, -7, RollType.Greed, roll); - ++roll.totalGreed; - rollType = RollType.Greed; - break; - case RollType.Disenchant: // player choose Disenchant - SendLootRoll(playerGuid, -8, RollType.Disenchant, roll); - ++roll.totalGreed; - rollType = RollType.Disenchant; - break; - } - - roll.playerVote[playerGuid] = rollType; - - if (roll.totalPass + roll.totalNeed + roll.totalGreed >= roll.totalPlayersRolling) - CountTheRoll(roll, null); - } - - public void EndRoll(Loot pLoot, Map allowedMap) - { - foreach (var roll in RollId.ToList()) - { - if (roll.GetLoot() == pLoot) - { - CountTheRoll(roll, allowedMap); //i don't have to edit player votes, who didn't vote ... he will pass - } - } - } - - void CountTheRoll(Roll roll, Map allowedMap) - { - if (!roll.IsValid()) // is loot already deleted ? - { - RollId.Remove(roll); - return; - } - - //end of the roll - if (roll.totalNeed > 0) - { - if (!roll.playerVote.Empty()) - { - byte maxresul = 0; - ObjectGuid maxguid = ObjectGuid.Empty; - Player player; - - foreach (var pair in roll.playerVote) - { - if (pair.Value != RollType.Need) - continue; - - player = Global.ObjAccessor.FindPlayer(pair.Key); - if (!player || (allowedMap != null && player.GetMap() != allowedMap)) - { - --roll.totalNeed; - continue; - } - - byte randomN = (byte)RandomHelper.IRand(1, 100); - SendLootRoll(pair.Key, randomN, RollType.Need, roll); - if (maxresul < randomN) - { - maxguid = pair.Key; - maxresul = randomN; - } - } - - if (!maxguid.IsEmpty()) - { - SendLootRollWon(maxguid, maxresul, RollType.Need, roll); - player = Global.ObjAccessor.FindConnectedPlayer(maxguid); - - if (player && player.GetSession()) - { - player.UpdateCriteria(CriteriaType.RollNeed, roll.itemid, maxresul); - - List dest = new(); - LootItem item = (roll.itemSlot >= roll.GetLoot().items.Count ? roll.GetLoot().quest_items[roll.itemSlot - roll.GetLoot().items.Count] : roll.GetLoot().items[roll.itemSlot]); - InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, roll.itemid, item.count); - if (msg == InventoryResult.Ok) - { - item.is_looted = true; - roll.GetLoot().NotifyItemRemoved(roll.itemSlot, allowedMap); - roll.GetLoot().unlootedCount--; - player.StoreNewItem(dest, roll.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context, item.BonusListIDs); - } - else - { - item.is_blocked = false; - item.rollWinnerGUID = player.GetGUID(); - player.SendEquipError(msg, null, null, roll.itemid); - } - } - } - else - roll.totalNeed = 0; - } - } - - if (roll.totalNeed == 0 && roll.totalGreed > 0) // if (roll->totalNeed == 0 && ...), not else if, because numbers can be modified above if player is on a different map - { - if (!roll.playerVote.Empty()) - { - byte maxresul = 0; - ObjectGuid maxguid = ObjectGuid.Empty; - Player player; - RollType rollVote = RollType.NotValid; - - foreach (var pair in roll.playerVote) - { - if (pair.Value != RollType.Greed && pair.Value != RollType.Disenchant) - continue; - - player = Global.ObjAccessor.FindPlayer(pair.Key); - if (!player || (allowedMap != null && player.GetMap() != allowedMap)) - { - --roll.totalGreed; - continue; - } - - byte randomN = (byte)RandomHelper.IRand(1, 100); - SendLootRoll(pair.Key, randomN, pair.Value, roll); - if (maxresul < randomN) - { - maxguid = pair.Key; - maxresul = randomN; - rollVote = pair.Value; - } - } - - if (!maxguid.IsEmpty()) - { - SendLootRollWon(maxguid, maxresul, rollVote, roll); - player = Global.ObjAccessor.FindConnectedPlayer(maxguid); - - if (player && player.GetSession() != null) - { - player.UpdateCriteria(CriteriaType.RollGreed, roll.itemid, maxresul); - - LootItem item = roll.itemSlot >= roll.GetLoot().items.Count ? roll.GetLoot().quest_items[roll.itemSlot - roll.GetLoot().items.Count] : roll.GetLoot().items[roll.itemSlot]; - - if (rollVote == RollType.Greed) - { - List dest = new(); - InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, roll.itemid, item.count); - if (msg == InventoryResult.Ok) - { - item.is_looted = true; - roll.GetLoot().NotifyItemRemoved(roll.itemSlot, allowedMap); - roll.GetLoot().unlootedCount--; - player.StoreNewItem(dest, roll.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context, item.BonusListIDs); - } - else - { - item.is_blocked = false; - item.rollWinnerGUID = player.GetGUID(); - player.SendEquipError(msg, null, null, roll.itemid); - } - } - else if (rollVote == RollType.Disenchant) - { - item.is_looted = true; - roll.GetLoot().NotifyItemRemoved(roll.itemSlot, allowedMap); - roll.GetLoot().unlootedCount--; - player.UpdateCriteria(CriteriaType.CastSpell, 13262); // Disenchant - - ItemDisenchantLootRecord disenchant = roll.GetItemDisenchantLoot(player); - - List dest = new(); - InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, roll.itemid, item.count); - if (msg == InventoryResult.Ok) - player.AutoStoreLoot(disenchant.Id, LootStorage.Disenchant, ItemContext.None, true); - else // If the player's inventory is full, send the disenchant result in a mail. - { - Loot loot = new(allowedMap, roll.GetLoot().GetOwnerGUID(), LootType.Disenchanting, roll.GetLoot().GetLootMethod()); - loot.FillLoot(disenchant.Id, LootStorage.Disenchant, player, true); - - uint max_slot = loot.GetMaxSlotInLootFor(player); - for (uint i = 0; i < max_slot; ++i) - { - LootItem lootItem = loot.LootItemInSlot(i, player); - player.SendEquipError(msg, null, null, lootItem.itemid); - player.SendItemRetrievalMail(lootItem.itemid, lootItem.count, lootItem.context); - } - } - } - } - } - else - roll.totalGreed = 0; - } - } - - if (roll.totalNeed == 0 && roll.totalGreed == 0) // if, not else, because numbers can be modified above if player is on a different map - { - SendLootAllPassed(roll); - - // remove is_blocked so that the item is lootable by all players - LootItem item = roll.itemSlot >= roll.GetLoot().items.Count ? roll.GetLoot().quest_items[roll.itemSlot - roll.GetLoot().items.Count] : roll.GetLoot().items[roll.itemSlot]; - item.is_blocked = false; - } - - SendLootRollsComplete(roll); - - RollId.Remove(roll); - } - public void SetTargetIcon(byte symbol, ObjectGuid target, ObjectGuid changedBy, sbyte partyIndex) { if (symbol >= MapConst.TargetIconsCount) @@ -2238,18 +1673,6 @@ namespace Game.Groups } } - public void ResetMaxEnchantingLevel() - { - m_maxEnchantingLevel = 0; - Player member; - foreach (var memberSlot in m_memberSlots) - { - member = Global.ObjAccessor.FindPlayer(memberSlot.guid); - if (member && m_maxEnchantingLevel < member.GetSkillValue(SkillType.Enchanting)) - m_maxEnchantingLevel = member.GetSkillValue(SkillType.Enchanting); - } - } - public void SetLootMethod(LootMethod method) { m_lootMethod = method; @@ -2466,8 +1889,6 @@ namespace Game.Groups return GetMembersCount() > 0; } - public bool IsRollLootActive() { return !RollId.Empty(); } - public ObjectGuid GetLeaderGUID() { return m_leaderGuid; @@ -2623,15 +2044,6 @@ namespace Game.Groups SendUpdate(); } - Roll GetRoll(ObjectGuid lootObjectGuid, byte lootListId) - { - foreach (var roll in RollId) - if (roll.IsValid() && roll.GetTarget().GetGUID() == lootObjectGuid && roll.itemSlot == lootListId) - return roll; - - return null; - } - public void LinkMember(GroupReference pRef) { m_memberMgr.InsertFirst(pRef); @@ -2761,11 +2173,9 @@ namespace Game.Groups ItemQuality m_lootThreshold; ObjectGuid m_looterGuid; ObjectGuid m_masterLooterGuid; - List RollId = new(); Dictionary> m_boundInstances = new(); byte[] m_subGroupsCounts; ObjectGuid m_guid; - uint m_maxEnchantingLevel; uint m_dbStoreId; bool _isLeaderOffline; TimeTracker _leaderOfflineTimer = new(); @@ -2784,76 +2194,6 @@ namespace Game.Groups } } - public class Roll : LootValidatorRef - { - public Roll(LootItem li) - { - itemid = li.itemid; - itemRandomBonusListId = li.randomBonusListId; - itemCount = li.count; - rollTypeMask = RollMask.AllNoDisenchant; - } - - public void SetLoot(Loot pLoot) - { - Link(pLoot, this); - } - - public Loot GetLoot() - { - return GetTarget(); - } - - public override void TargetObjectBuildLink() - { - // called from link() - GetTarget().AddLootValidatorRef(this); - } - - public void FillPacket(LootItemData lootItem) - { - lootItem.UIType = (totalPlayersRolling > totalNeed + totalGreed + totalPass) ? LootSlotType.RollOngoing : LootSlotType.AllowLoot; - lootItem.Quantity = itemCount; - lootItem.LootListID = (byte)(itemSlot + 1); - - LootItem lootItemInSlot = GetTarget().GetItemInSlot(itemSlot); - if (lootItemInSlot != null) - { - lootItem.CanTradeToTapList = lootItemInSlot.allowedGUIDs.Count > 1; - lootItem.Loot = new ItemInstance(lootItemInSlot); - } - } - - public ItemDisenchantLootRecord GetItemDisenchantLoot(Player player) - { - LootItem lootItemInSlot = GetTarget().GetItemInSlot(itemSlot); - if (lootItemInSlot != null) - { - ItemInstance itemInstance = new(lootItemInSlot); - BonusData bonusData = new(itemInstance); - if (!bonusData.CanDisenchant) - return null; - - ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemid); - uint itemLevel = Item.GetItemLevel(itemTemplate, bonusData, player.GetLevel(), 0, 0, 0, 0, false, 0); - return Item.GetDisenchantLoot(itemTemplate, (uint)bonusData.Quality, itemLevel); - } - - return null; - } - - public uint itemid; - public uint itemRandomBonusListId; - public byte itemCount; - public Dictionary playerVote = new(); - public byte totalPlayersRolling; - public byte totalNeed; - public byte totalGreed; - public byte totalPass; - public byte itemSlot; - public RollMask rollTypeMask; - } - public class MemberSlot { public ObjectGuid guid; diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index 869760502..e4564279a 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -869,7 +869,6 @@ namespace Game if (group) { group.SendUpdate(); - group.ResetMaxEnchantingLevel(); if (group.GetLeaderGUID() == pCurrChar.GetGUID()) group.StopLeaderOfflineTimer(); } diff --git a/Source/Game/Handlers/GroupHandler.cs b/Source/Game/Handlers/GroupHandler.cs index 173f75ad7..b0cb7a946 100644 --- a/Source/Game/Handlers/GroupHandler.cs +++ b/Source/Game/Handlers/GroupHandler.cs @@ -385,26 +385,6 @@ namespace Game group.SendUpdate(); } - [WorldPacketHandler(ClientOpcodes.LootRoll)] - void HandleLootRoll(LootRoll packet) - { - Group group = GetPlayer().GetGroup(); - if (!group) - return; - - group.CountRollVote(GetPlayer().GetGUID(), packet.LootObj, (byte)(packet.LootListID - 1), packet.RollType); - - switch (packet.RollType) - { - case RollType.Need: - GetPlayer().UpdateCriteria(CriteriaType.RollAnyNeed, 1); - break; - case RollType.Greed: - GetPlayer().UpdateCriteria(CriteriaType.RollAnyGreed, 1); - break; - } - } - [WorldPacketHandler(ClientOpcodes.MinimapPing)] void HandleMinimapPing(MinimapPingClient packet) { diff --git a/Source/Game/Handlers/LootHandler.cs b/Source/Game/Handlers/LootHandler.cs index 47b27dffc..503717d04 100644 --- a/Source/Game/Handlers/LootHandler.cs +++ b/Source/Game/Handlers/LootHandler.cs @@ -362,22 +362,14 @@ namespace Game if (player.GetGUID() == loot.roundRobinPlayer) { loot.roundRobinPlayer.Clear(); - - Group group = player.GetGroup(); - if (group) - { - if (group.GetLootMethod() != LootMethod.MasterLoot) - group.SendLooter(creature, null); - } - // force dynflag update to update looter and lootable info - creature.m_values.ModifyValue(creature.m_objectData).ModifyValue(creature.m_objectData.DynamicFlags); - creature.ForceUpdateFieldChange(); + loot.NotifyLootList(creature.GetMap()); } + + // force dynflag update to update looter and lootable info + creature.m_values.ModifyValue(creature.m_objectData).ModifyValue(creature.m_objectData.DynamicFlags); + creature.ForceUpdateFieldChange(); } } - - //Player is not looking at loot list, he doesn't need to see updates on the loot list - loot.RemoveLooter(player.GetGUID()); } public void DoLootReleaseAll() @@ -466,6 +458,16 @@ namespace Game } } + [WorldPacketHandler(ClientOpcodes.LootRoll)] + void HandleLootRoll(LootRollPacket packet) + { + LootRoll lootRoll = GetPlayer().GetLootRoll(packet.LootObj, (byte)(packet.LootListID - 1)); + if (lootRoll == null) + return; + + lootRoll.PlayerVote(GetPlayer(), packet.RollType); + } + [WorldPacketHandler(ClientOpcodes.SetLootSpecialization)] void HandleSetLootSpecialization(SetLootSpecialization packet) { diff --git a/Source/Game/Loot/Loot.cs b/Source/Game/Loot/Loot.cs index f987c218a..c5d1d41f0 100644 --- a/Source/Game/Loot/Loot.cs +++ b/Source/Game/Loot/Loot.cs @@ -16,14 +16,15 @@ */ using Framework.Constants; -using Framework.Dynamic; using Game.Conditions; +using Game.DataStorage; using Game.Entities; using Game.Groups; +using Game.Maps; using Game.Networking.Packets; using System; using System.Collections.Generic; -using Game.Maps; +using System.Linq; namespace Game.Loots { @@ -37,7 +38,7 @@ namespace Game.Loots ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid); freeforall = proto != null && proto.HasFlag(ItemFlags.MultiDrop); - follow_loot_rules = proto != null && proto.FlagsCu.HasAnyFlag(ItemFlagsCustom.FollowLootRules); + follow_loot_rules = !li.needs_quest || (proto != null && proto.FlagsCu.HasAnyFlag(ItemFlagsCustom.FollowLootRules)); needs_quest = li.needs_quest; @@ -110,7 +111,7 @@ namespace Game.Loots public List GetAllowedLooters() { return allowedGUIDs; } public uint itemid; - public uint itemIndex; + public uint LootListId; public uint randomBonusListId; public List BonusListIDs = new(); public ItemContext context; @@ -145,35 +146,450 @@ namespace Game.Loots } } - public class LootValidatorRef : Reference + public class PlayerRollVote { - public override void TargetObjectDestroyLink() + public RollVote Vote; + public byte RollNumber; + + public PlayerRollVote() { - - } - - public override void SourceObjectDestroyLink() - { - + Vote = RollVote.NotValid; + RollNumber = 0; } } - public class LootValidatorRefManager : RefManager + public class LootRoll { - public new LootValidatorRef GetFirst() { return (LootValidatorRef)base.GetFirst(); } - public new LootValidatorRef GetLast() { return (LootValidatorRef)base.GetLast(); } + static TimeSpan LOOT_ROLL_TIMEOUT = TimeSpan.FromMinutes(1); + + Map m_map; + Dictionary m_rollVoteMap = new(); + bool m_isStarted; + LootItem m_lootItem; + Loot m_loot; + uint m_lootListId; + RollMask m_voteMask; + DateTime m_endTime = DateTime.MinValue; + + ~LootRoll() + { + if (m_isStarted) + SendAllPassed(); + + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + if (roll.Vote != RollVote.NotEmitedYet) + continue; + + Player player = Global.ObjAccessor.GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player.RemoveLootRoll(this); + } + } + + // Send the roll for the whole group + void SendStartRoll() + { + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(m_lootItem.itemid); + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + if (roll.Vote != RollVote.NotEmitedYet) + continue; + + Player player = Global.ObjAccessor.GetPlayer(m_map, playerGuid); + if (player == null) + continue; + + StartLootRoll startLootRoll = new(); + startLootRoll.LootObj = m_loot.GetGUID(); + startLootRoll.MapID = (int)m_map.GetId(); + startLootRoll.RollTime = (uint)LOOT_ROLL_TIMEOUT.TotalMilliseconds; + 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) + startLootRoll.ValidRolls &= ~RollMask.Need; + + FillPacket(startLootRoll.Item); + startLootRoll.Item.UIType = LootSlotType.RollOngoing; + + player.SendPacket(startLootRoll); + } + + // Handle auto pass option + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + if (roll.Vote != RollVote.Pass) + continue; + + SendRoll(playerGuid, -1, RollVote.Pass, null); + } + } + + // Send all passed message + void SendAllPassed() + { + LootAllPassed lootAllPassed = new(); + lootAllPassed.LootObj = m_loot.GetGUID(); + FillPacket(lootAllPassed.Item); + lootAllPassed.Item.UIType = LootSlotType.AllowLoot; + lootAllPassed.Write(); + + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + if (roll.Vote != RollVote.NotValid) + continue; + + Player player = Global.ObjAccessor.GetPlayer(m_map, playerGuid); + if (player == null) + continue; + + player.SendPacket(lootAllPassed); + } + } + + // Send roll of targetGuid to the whole group (included targuetGuid) + void SendRoll(ObjectGuid targetGuid, int rollNumber, RollVote rollType, ObjectGuid? rollWinner) + { + LootRollBroadcast lootRoll = new(); + lootRoll.LootObj = m_loot.GetGUID(); + lootRoll.Player = targetGuid; + lootRoll.Roll = rollNumber; + lootRoll.RollType = rollType; + lootRoll.Autopassed = false; + FillPacket(lootRoll.Item); + lootRoll.Item.UIType = LootSlotType.RollOngoing; + lootRoll.Write(); + + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + if (roll.Vote == RollVote.NotValid) + continue; + + if (playerGuid == rollWinner) + continue; + + Player player = Global.ObjAccessor.GetPlayer(m_map, playerGuid); + if (player == null) + continue; + + player.SendPacket(lootRoll); + } + + if (rollWinner.HasValue) + { + Player player = Global.ObjAccessor.GetPlayer(m_map, rollWinner.Value); + if (player != null) + { + lootRoll.Item.UIType = LootSlotType.AllowLoot; + lootRoll.Clear(); + player.SendPacket(lootRoll); + } + } + } + + // Send roll 'value' of the whole group and the winner to the whole group + void SendLootRollWon(ObjectGuid targetGuid, int rollNumber, RollVote rollType) + { + // Send roll values + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + switch (roll.Vote) + { + case RollVote.Pass: + break; + case RollVote.NotEmitedYet: + case RollVote.NotValid: + SendRoll(playerGuid, 0, RollVote.Pass, targetGuid); + break; + default: + SendRoll(playerGuid, roll.RollNumber, roll.Vote, targetGuid); + break; + } + } + + LootRollWon lootRollWon = new(); + lootRollWon.LootObj = m_loot.GetGUID(); + lootRollWon.Winner = targetGuid; + lootRollWon.Roll = rollNumber; + lootRollWon.RollType = rollType; + FillPacket(lootRollWon.Item); + lootRollWon.Item.UIType = LootSlotType.Locked; + lootRollWon.MainSpec = true; // offspec rolls not implemented + lootRollWon.Write(); + + foreach (var (playerGuid, roll) in m_rollVoteMap) + { + if (roll.Vote == RollVote.NotValid) + continue; + + if (playerGuid == targetGuid) + continue; + + Player player1 = Global.ObjAccessor.GetPlayer(m_map, playerGuid); + if (player1 == null) + continue; + + player1.SendPacket(lootRollWon); + } + + Player player = Global.ObjAccessor.GetPlayer(m_map, targetGuid); + if (player != null) + { + lootRollWon.Item.UIType = LootSlotType.AllowLoot; + lootRollWon.Clear(); + player.SendPacket(lootRollWon); + } + } + + void FillPacket(LootItemData lootItem) + { + lootItem.Quantity = m_lootItem.count; + lootItem.LootListID = (byte)(m_lootListId + 1); + lootItem.CanTradeToTapList = m_lootItem.allowedGUIDs.Count > 1; + lootItem.Loot = new(m_lootItem); + } + + // Try to start the group roll for the specified item (it may fail for quest item or any condition + // If this method return false the roll have to be removed from the container to avoid any problem + public bool TryToStart(Map map, Loot loot, uint lootListId, ushort enchantingSkill) + { + if (!m_isStarted) + { + if (lootListId >= loot.items.Count) + return false; + + m_map = map; + + // initialize the data needed for the roll + m_lootItem = loot.items[(int)lootListId]; + + m_loot = loot; + m_lootListId = lootListId; + m_lootItem.is_blocked = true; // block the item while rolling + + uint playerCount = 0; + foreach (ObjectGuid allowedLooter in m_lootItem.GetAllowedLooters()) + { + Player plr = Global.ObjAccessor.GetPlayer(m_map, allowedLooter); + if (!plr || !m_lootItem.AllowedForPlayer(plr)) // check if player meet the condition to be able to roll this item + { + m_rollVoteMap[allowedLooter].Vote = RollVote.NotValid; + continue; + } + // initialize player vote map + m_rollVoteMap[allowedLooter].Vote = plr.GetPassOnGroupLoot() ? RollVote.Pass : RollVote.NotEmitedYet; + if (!plr.GetPassOnGroupLoot()) + plr.AddLootRoll(this); + + ++playerCount; + } + + // initialize item prototype and check enchant possibilities for this group + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(m_lootItem.itemid); + m_voteMask = RollMask.AllMask; + if (itemTemplate.HasFlag(ItemFlags2.CanOnlyRollGreed)) + m_voteMask = m_voteMask & ~RollMask.Need; + var disenchant = GetItemDisenchantLoot(); + if (disenchant == null || disenchant.SkillRequired > enchantingSkill) + m_voteMask = m_voteMask & ~RollMask.Disenchant; + + if (playerCount > 1) // check if more than one player can loot this item + { + // start the roll + SendStartRoll(); + m_endTime = GameTime.Now() + LOOT_ROLL_TIMEOUT; + m_isStarted = true; + return true; + } + // no need to start roll if one or less player can loot this item so place it under threshold + m_lootItem.is_underthreshold = true; + m_lootItem.is_blocked = false; + } + return false; + } + + // Add vote from playerGuid + public bool PlayerVote(Player player, RollVote vote) + { + ObjectGuid playerGuid = player.GetGUID(); + if (!m_rollVoteMap.TryGetValue(playerGuid, out PlayerRollVote voter)) + return false; + + voter.Vote = vote; + + if (vote != RollVote.Pass && vote != RollVote.NotValid) + voter.RollNumber = (byte)RandomHelper.URand(1, 100); + + switch (vote) + { + case RollVote.Pass: // Player choose pass + { + SendRoll(playerGuid, -1, RollVote.Pass, null); + break; + } + case RollVote.Need: // player choose Need + { + SendRoll(playerGuid, 0, RollVote.Need, null); + player.UpdateCriteria(CriteriaType.RollAnyNeed, 1); + break; + } + case RollVote.Greed: // player choose Greed + { + SendRoll(playerGuid, -1, RollVote.Greed, null); + player.UpdateCriteria(CriteriaType.RollAnyGreed, 1); + break; + } + case RollVote.Disenchant: // player choose Disenchant + { + SendRoll(playerGuid, -1, RollVote.Disenchant, null); + player.UpdateCriteria(CriteriaType.RollAnyGreed, 1); + break; + } + default: // Roll removed case + return false; + } + return true; + } + + // check if we can found a winner for this roll or if timer is expired + public bool UpdateRoll() + { + KeyValuePair winner = default; + + if (AllPlayerVoted(ref winner) || m_endTime <= GameTime.Now()) + { + Finish(winner); + return true; + } + return false; + } + + public bool IsLootItem(ObjectGuid lootObject, uint lootListId) + { + return m_loot.GetGUID() == lootObject && m_lootListId == lootListId; + } + + /** + * \brief Check if all player have voted and return true in that case. Also return current winner. + * \param winnerItr > will be different than m_rollCoteMap.end() if winner exist. (Someone voted greed or need) + * \returns true if all players voted + **/ + bool AllPlayerVoted(ref KeyValuePair winnerPair) + { + uint notVoted = 0; + bool isSomeoneNeed = false; + + winnerPair = default; + foreach (var pair in m_rollVoteMap) + { + switch (pair.Value.Vote) + { + case RollVote.Need: + if (!isSomeoneNeed || winnerPair.Value == null || pair.Value.RollNumber > winnerPair.Value.RollNumber) + { + isSomeoneNeed = true; // first passage will force to set winner because need is prioritized + winnerPair = pair; + } + break; + case RollVote.Greed: + case RollVote.Disenchant: + if (!isSomeoneNeed) // if at least one need is detected then winner can't be a greed + { + if (winnerPair.Value == null || pair.Value.RollNumber > winnerPair.Value.RollNumber) + winnerPair = pair; + } + break; + // Explicitly passing excludes a player from winning loot, so no action required. + case RollVote.Pass: + break; + case RollVote.NotEmitedYet: + ++notVoted; + break; + default: + break; + } + } + + return notVoted == 0; + } + + ItemDisenchantLootRecord GetItemDisenchantLoot() + { + ItemInstance itemInstance = new(m_lootItem); + + BonusData bonusData = new(itemInstance); + if (!bonusData.CanDisenchant) + return null; + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(m_lootItem.itemid); + uint itemLevel = Item.GetItemLevel(itemTemplate, bonusData, 1, 0, 0, 0, 0, false, 0); + return Item.GetDisenchantLoot(itemTemplate, (uint)bonusData.Quality, itemLevel); + } + + // terminate the roll + void Finish(KeyValuePair winnerPair) + { + m_lootItem.is_blocked = false; + if (winnerPair.Value == null) + { + SendAllPassed(); + } + else + { + m_lootItem.rollWinnerGUID = winnerPair.Key; + + SendLootRollWon(winnerPair.Key, winnerPair.Value.RollNumber, winnerPair.Value.Vote); + + Player player = Global.ObjAccessor.FindConnectedPlayer(winnerPair.Key); + if (player != null) + { + if (winnerPair.Value.Vote == RollVote.Need) + player.UpdateCriteria(CriteriaType.RollNeed, m_lootItem.itemid, winnerPair.Value.RollNumber); + else if (winnerPair.Value.Vote == RollVote.Disenchant) + player.UpdateCriteria(CriteriaType.CastSpell, 13262); + else + player.UpdateCriteria(CriteriaType.RollGreed, m_lootItem.itemid, winnerPair.Value.RollNumber); + + if (winnerPair.Value.Vote == RollVote.Disenchant) + { + var disenchant = GetItemDisenchantLoot(); + Loot loot = new(m_map, m_loot.GetOwnerGUID(), LootType.Disenchanting, null); + loot.FillLoot(disenchant.Id, LootStorage.Disenchant, player, true, false, LootModes.Default, ItemContext.None); + if (!loot.AutoStore(player, ItemConst.NullBag, ItemConst.NullSlot, true)) + { + uint maxSlot = loot.GetMaxSlotInLootFor(player); + for (uint i = 0; i < maxSlot; ++i) + { + LootItem disenchantLoot = loot.LootItemInSlot(i, player); + if (disenchantLoot != null) + player.SendItemRetrievalMail(disenchantLoot.itemid, disenchantLoot.count, disenchantLoot.context); + } + } + else + m_loot.NotifyItemRemoved((byte)m_lootItem.LootListId, m_map); + } + else + player.StoreLootItem(m_loot.GetOwnerGUID(), (byte)m_lootListId, m_loot); + } + } + m_isStarted = false; + } } public class Loot { - public Loot(Map map, ObjectGuid owner, LootType type, LootMethod lootMethod) + public Loot(Map map, ObjectGuid owner, LootType type, Group group) { loot_type = type; maxDuplicates = 1; _guid = map ? ObjectGuid.Create(HighGuid.LootObject, map.GetId(), 0, map.GenerateLowGuid(HighGuid.LootObject)) : ObjectGuid.Empty; _owner = owner; _itemContext = ItemContext.None; - _lootMethod = lootMethod; + _lootMethod = group != null ? group.GetLootMethod() : LootMethod.FreeForAll; + _lootMaster = group != null ? group.GetMasterLooterGuid() : ObjectGuid.Empty; } // Inserts the item into the loot (called by LootTemplate processors) @@ -194,7 +610,7 @@ namespace Game.Loots LootItem generatedLoot = new(item); generatedLoot.context = _itemContext; generatedLoot.count = (byte)Math.Min(count, proto.GetMaxStackSize()); - generatedLoot.itemIndex = (uint)lootItems.Count; + generatedLoot.LootListId = (uint)lootItems.Count; if (_itemContext != 0) { List bonusListIDs = Global.DB2Mgr.GetDefaultItemBonusTree(generatedLoot.itemid, _itemContext); @@ -230,6 +646,63 @@ namespace Game.Loots } } + public bool AutoStore(Player player, byte bag, byte slot, bool broadcast, bool createdByPlayer = false) + { + bool allLooted = true; + uint max_slot = GetMaxSlotInLootFor(player); + for (uint i = 0; i < max_slot; ++i) + { + NotNormalLootItem qitem = null; + NotNormalLootItem ffaitem = null; + NotNormalLootItem conditem = null; + + LootItem lootItem = LootItemInSlot(i, player, out qitem, out ffaitem, out conditem); + if (lootItem == null || lootItem.is_looted) + continue; + + if (!lootItem.AllowedForPlayer(player)) + continue; + + if (qitem == null && lootItem.is_blocked) + continue; + + // dont allow protected item to be looted by someone else + 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) + { + player.SendEquipError(msg, null, null, lootItem.itemid); + allLooted = false; + continue; + } + + if (qitem != null) + qitem.is_looted = true; + else if (ffaitem != null) + ffaitem.is_looted = true; + else if (conditem != null) + conditem.is_looted = true; + + if (!lootItem.freeforall) + lootItem.is_looted = true; + + --unlootedCount; + + Item pItem = player.StoreNewItem(dest, lootItem.itemid, true, lootItem.randomBonusListId, null, lootItem.context, lootItem.BonusListIDs); + player.SendNewItem(pItem, lootItem.count, false, createdByPlayer, broadcast); + player.ApplyItemLootedSpell(pItem, true); + } + + return allLooted; + } + public LootItem GetItemInSlot(uint lootSlot) { if (lootSlot < items.Count) @@ -271,28 +744,69 @@ namespace Game.Loots { Player player = refe.GetSource(); if (player) // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter - if (player.IsInMap(lootOwner)) - FillNotNormalLootFor(player, player.IsAtGroupRewardDistance(lootOwner)); + if (player.IsAtGroupRewardDistance(lootOwner)) + FillNotNormalLootFor(player); } - for (byte i = 0; i < items.Count; ++i) + void processLootItem(LootItem item) { - ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(items[i].itemid); + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); if (proto != null) + { if (proto.GetQuality() < group.GetLootThreshold()) - items[i].is_underthreshold = true; + item.is_underthreshold = true; + else + { + switch (_lootMethod) + { + case LootMethod.MasterLoot: + case LootMethod.GroupLoot: + case LootMethod.NeedBeforeGreed: + { + item.is_blocked = true; + break; + } + default: + break; + } + } + } + }; + + foreach (LootItem item in items) + { + if (item.freeforall) + continue; + + processLootItem(item); + } + + foreach (LootItem item in quest_items) + { + if (!item.follow_loot_rules) + continue; + + processLootItem(item); } } // ... for personal loot else - FillNotNormalLootFor(lootOwner, true); + FillNotNormalLootFor(lootOwner); return true; } - void FillNotNormalLootFor(Player player, bool presentAtLooting) + public void Update() + { + foreach (var pair in _rolls.ToList()) + if (pair.Value.UpdateRoll()) + _rolls.Remove(pair.Key); + } + + void FillNotNormalLootFor(Player player) { ObjectGuid plguid = player.GetGUID(); + _allowedLooters.Add(plguid); var questItemList = PlayerQuestItems.LookupByKey(plguid); if (questItemList.Empty()) @@ -304,7 +818,7 @@ namespace Game.Loots questItemList = PlayerNonQuestNonFFAConditionalItems.LookupByKey(plguid); if (questItemList.Empty()) - FillNonQuestNonFFAConditionalLoot(player, presentAtLooting); + FillNonQuestNonFFAConditionalLoot(player); } List FillFFALoot(Player player) @@ -341,6 +855,8 @@ namespace Game.Loots if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player.GetGroup() && ((GetLootMethod() == LootMethod.MasterLoot && player.GetGroup().GetMasterLooterGuid() == player.GetGUID()) || GetLootMethod() != LootMethod.MasterLoot)))) { + item.AddAllowedLooter(player); + ql.Add(new NotNormalLootItem(i)); // quest items get blocked when they first appear in a @@ -349,7 +865,7 @@ namespace Game.Loots // increase once if one looter only, looter-times if free for all if (item.freeforall || !item.is_blocked) ++unlootedCount; - if (!player.GetGroup() || GetLootMethod() != LootMethod.GroupLoot) + if (!player.GetGroup() || GetLootMethod() != LootMethod.GroupLoot && GetLootMethod() != LootMethod.RoundRobin) item.is_blocked = true; if (items.Count + ql.Count == SharedConst.MaxNRLootItems) @@ -363,7 +879,7 @@ namespace Game.Loots return ql; } - List FillNonQuestNonFFAConditionalLoot(Player player, bool presentAtLooting) + List FillNonQuestNonFFAConditionalLoot(Player player) { List ql = new(); @@ -372,8 +888,7 @@ namespace Game.Loots LootItem item = items[i]; if (!item.is_looted && !item.freeforall && item.AllowedForPlayer(player)) { - if (presentAtLooting) - item.AddAllowedLooter(player); + item.AddAllowedLooter(player); if (!item.conditions.Empty()) { ql.Add(new NotNormalLootItem(i)); @@ -447,6 +962,65 @@ namespace Game.Loots } } + public void OnLootOpened(Map map, ObjectGuid looter) + { + AddLooter(looter); + if (!_wasOpened) + { + _wasOpened = true; + + if (_lootMethod == LootMethod.GroupLoot || _lootMethod == LootMethod.NeedBeforeGreed) + { + ushort maxEnchantingSkill = 0; + foreach (ObjectGuid allowedLooterGuid in _allowedLooters) + { + Player allowedLooter = Global.ObjAccessor.GetPlayer(map, allowedLooterGuid); + if (allowedLooter != null) + maxEnchantingSkill = Math.Max(maxEnchantingSkill, allowedLooter.GetSkillValue(SkillType.Enchanting)); + } + + uint lootListId = 0; + for (; lootListId < items.Count; ++lootListId) + { + LootItem item = items[(int)lootListId]; + if (!item.is_blocked) + continue; + + LootRoll lootRoll = new(); + var inserted = _rolls.TryAdd(lootListId, lootRoll); + if (!lootRoll.TryToStart(map, this, lootListId, maxEnchantingSkill)) + _rolls.Remove(lootListId); + } + + for (; lootListId - items.Count < quest_items.Count; ++lootListId) + { + LootItem item = quest_items[(int)lootListId - items.Count]; + if (!item.is_blocked) + continue; + + LootRoll lootRoll = new(); + var inserted = _rolls.TryAdd(lootListId, lootRoll); + if (!lootRoll.TryToStart(map, this, lootListId, maxEnchantingSkill)) + _rolls.Remove(lootListId); + } + } + else if (_lootMethod == LootMethod.MasterLoot) + { + if (looter == _lootMaster) + { + Player lootMaster = Global.ObjAccessor.GetPlayer(map, looter); + if (lootMaster != null) + { + MasterLootCandidateList masterLootCandidateList = new(); + masterLootCandidateList.LootObj = GetGUID(); + masterLootCandidateList.Players = _allowedLooters; + lootMaster.SendPacket(masterLootCandidateList); + } + } + } + } + } + public void GenerateMoneyLoot(uint minAmount, uint maxAmount) { if (maxAmount > 0) @@ -619,82 +1193,102 @@ namespace Game.Loots case PermissionTypes.Group: case PermissionTypes.Master: case PermissionTypes.Restricted: + { + // if you are not the round-robin group looter, you can only see + // blocked rolled items and quest items, and !ffa items + for (byte i = 0; i < items.Count; ++i) { - // if you are not the round-robin group looter, you can only see - // blocked rolled items and quest items, and !ffa items - for (byte i = 0; i < items.Count; ++i) + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) { - if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) - { - LootSlotType slot_type; + LootSlotType slot_type; - if (items[i].is_blocked) // for ML & restricted is_blocked = !is_underthreshold + if (items[i].is_blocked) // for ML & restricted is_blocked = !is_underthreshold + { + switch (permission) { - switch (permission) + case PermissionTypes.Group: + slot_type = LootSlotType.RollOngoing; + break; + case PermissionTypes.Master: { - case PermissionTypes.Group: - slot_type = LootSlotType.RollOngoing; - break; - case PermissionTypes.Master: - { - if (viewer.GetGroup() && viewer.GetGroup().GetMasterLooterGuid() == viewer.GetGUID()) - slot_type = LootSlotType.Master; - else - slot_type = LootSlotType.Locked; - break; - } - case PermissionTypes.Restricted: + if (viewer.GetGroup() && viewer.GetGroup().GetMasterLooterGuid() == viewer.GetGUID()) + slot_type = LootSlotType.Master; + else slot_type = LootSlotType.Locked; - break; - default: - continue; + break; } - } - else if (roundRobinPlayer.IsEmpty() || viewer.GetGUID() == roundRobinPlayer || !items[i].is_underthreshold) - { - // no round robin owner or he has released the loot - // or it IS the round robin group owner - // => item is lootable - slot_type = LootSlotType.AllowLoot; - } - else if (!items[i].rollWinnerGUID.IsEmpty()) - { - if (items[i].rollWinnerGUID == viewer.GetGUID()) - slot_type = LootSlotType.Owner; - else + case PermissionTypes.Restricted: + slot_type = LootSlotType.Locked; + break; + default: continue; } - else - // item shall not be displayed. - continue; - - LootItemData lootItem = new(); - lootItem.LootListID = (byte)(i + 1); - lootItem.UIType = slot_type; - lootItem.Quantity = items[i].count; - lootItem.Loot = new ItemInstance(items[i]); - packet.Items.Add(lootItem); } + else if (roundRobinPlayer.IsEmpty() || viewer.GetGUID() == roundRobinPlayer || !items[i].is_underthreshold) + { + // no round robin owner or he has released the loot + // or it IS the round robin group owner + // => item is lootable + slot_type = LootSlotType.AllowLoot; + } + else if (!items[i].rollWinnerGUID.IsEmpty()) + { + if (items[i].rollWinnerGUID == viewer.GetGUID()) + slot_type = LootSlotType.Owner; + else + continue; + } + else + // item shall not be displayed. + continue; + + LootItemData lootItem = new(); + lootItem.LootListID = (byte)(i + 1); + lootItem.UIType = slot_type; + lootItem.Quantity = items[i].count; + lootItem.Loot = new ItemInstance(items[i]); + packet.Items.Add(lootItem); } - break; } + break; + } + case PermissionTypes.RoundRobin: + { + for (var i = 0; i < items.Count; ++i) + { + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) + { + if (!roundRobinPlayer.IsEmpty() && viewer.GetGUID() != roundRobinPlayer) + // item shall not be displayed. + continue; + + LootItemData lootItem = new(); + lootItem.LootListID = (byte)(i + 1); + lootItem.UIType = LootSlotType.AllowLoot; + lootItem.Quantity = items[i].count; + lootItem.Loot = new(items[i]); + packet.Items.Add(lootItem); + } + } + break; + } case PermissionTypes.All: case PermissionTypes.Owner: + { + for (byte i = 0; i < items.Count; ++i) { - for (byte i = 0; i < items.Count; ++i) + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) { - if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) - { - LootItemData lootItem = new(); - lootItem.LootListID = (byte)(i + 1); - lootItem.UIType = (permission == PermissionTypes.Owner ? LootSlotType.Owner : LootSlotType.AllowLoot); - lootItem.Quantity = items[i].count; - lootItem.Loot = new ItemInstance(items[i]); - packet.Items.Add(lootItem); - } + LootItemData lootItem = new(); + lootItem.LootListID = (byte)(i + 1); + lootItem.UIType = (permission == PermissionTypes.Owner ? LootSlotType.Owner : LootSlotType.AllowLoot); + lootItem.Quantity = items[i].count; + lootItem.Loot = new ItemInstance(items[i]); + packet.Items.Add(lootItem); } - break; } + break; + } default: return; } @@ -724,6 +1318,7 @@ namespace Game.Loots lootItem.UIType = item.is_blocked ? LootSlotType.Locked : LootSlotType.AllowLoot; break; case PermissionTypes.Group: + case PermissionTypes.RoundRobin: if (!item.is_blocked) lootItem.UIType = LootSlotType.AllowLoot; else @@ -783,6 +1378,7 @@ namespace Game.Loots lootItem.UIType = item.is_blocked ? LootSlotType.Locked : LootSlotType.AllowLoot; break; case PermissionTypes.Group: + case PermissionTypes.RoundRobin: if (!item.is_blocked) lootItem.UIType = LootSlotType.AllowLoot; else @@ -802,11 +1398,6 @@ namespace Game.Loots } } - public void AddLootValidatorRef(LootValidatorRef pLootValidatorRef) - { - i_LootValidatorRefManager.InsertFirst(pLootValidatorRef); - } - public void Clear() { PlayerQuestItems.Clear(); @@ -828,8 +1419,31 @@ namespace Game.Loots gold = 0; unlootedCount = 0; roundRobinPlayer = ObjectGuid.Empty; - i_LootValidatorRefManager.ClearReferences(); _itemContext = 0; + _rolls.Clear(); + } + + public void NotifyLootList(Map map) + { + LootList lootList = new(); + + lootList.Owner = GetOwnerGUID(); + lootList.LootObj = GetGUID(); + + if (GetLootMethod() == LootMethod.MasterLoot && HasOverThresholdItem()) + lootList.Master = GetLootMasterGUID(); + + if (!roundRobinPlayer.IsEmpty()) + lootList.RoundRobinWinner = roundRobinPlayer; + + lootList.Write(); + + foreach (ObjectGuid allowedLooterGuid in _allowedLooters) + { + Player allowedLooter = Global.ObjAccessor.GetPlayer(map, allowedLooterGuid); + if (allowedLooter != null) + allowedLooter.SendPacket(lootList); + } } public bool Empty() { return items.Empty() && gold == 0; } @@ -841,7 +1455,9 @@ namespace Game.Loots public ObjectGuid GetGUID() { return _guid; } public ObjectGuid GetOwnerGUID() { return _owner; } public LootMethod GetLootMethod() { return _lootMethod; } - + + public ObjectGuid GetLootMasterGUID() { return _lootMaster; } + public MultiMap GetPlayerQuestItems() { return PlayerQuestItems; } public MultiMap GetPlayerFFAItems() { return PlayerFFAItems; } public MultiMap GetPlayerNonQuestNonFFAConditionalItems() { return PlayerNonQuestNonFFAConditionalItems; } @@ -859,14 +1475,15 @@ namespace Game.Loots MultiMap PlayerFFAItems = new(); MultiMap PlayerNonQuestNonFFAConditionalItems = new(); - // All rolls are registered here. They need to know, when the loot is not valid anymore - LootValidatorRefManager i_LootValidatorRefManager = new(); - // Loot GUID ObjectGuid _guid; ObjectGuid _owner; // The WorldObject that holds this loot ItemContext _itemContext; LootMethod _lootMethod; + Dictionary _rolls = new(); // used if an item is under rolling + ObjectGuid _lootMaster; + List _allowedLooters = new(); + bool _wasOpened; // true if at least one player received the loot content } public class AELootResult diff --git a/Source/Game/Loot/LootItemStorage.cs b/Source/Game/Loot/LootItemStorage.cs index 19d78310c..82e1689ce 100644 --- a/Source/Game/Loot/LootItemStorage.cs +++ b/Source/Game/Loot/LootItemStorage.cs @@ -50,7 +50,7 @@ namespace Game.Loots LootItem lootItem = new(); lootItem.itemid = result.Read(1); lootItem.count = result.Read(2); - lootItem.itemIndex = result.Read(3); + lootItem.LootListId = result.Read(3); lootItem.follow_loot_rules = result.Read(4); lootItem.freeforall = result.Read(5); lootItem.is_blocked = result.Read(6); @@ -114,7 +114,7 @@ namespace Game.Loots LootItem li = new(); li.itemid = id; li.count = (byte)storedItem.Count; - li.itemIndex = storedItem.ItemIndex; + li.LootListId = storedItem.ItemIndex; li.follow_loot_rules = storedItem.FollowRules; li.freeforall = storedItem.FFA; li.is_blocked = storedItem.Blocked; @@ -244,7 +244,7 @@ namespace Game.Loots stmt.AddValue(0, _containerId); stmt.AddValue(1, lootItem.itemid); stmt.AddValue(2, lootItem.count); - stmt.AddValue(3, lootItem.itemIndex); + stmt.AddValue(3, lootItem.LootListId); stmt.AddValue(4, lootItem.follow_loot_rules); stmt.AddValue(5, lootItem.freeforall); stmt.AddValue(6, lootItem.is_blocked); @@ -325,7 +325,7 @@ namespace Game.Loots { ItemId = lootItem.itemid; Count = lootItem.count; - ItemIndex = lootItem.itemIndex; + ItemIndex = lootItem.LootListId; FollowRules = lootItem.follow_loot_rules; FFA = lootItem.freeforall; Blocked = lootItem.is_blocked; diff --git a/Source/Game/Mails/MailDraft.cs b/Source/Game/Mails/MailDraft.cs index 9f19135ce..adc354f0e 100644 --- a/Source/Game/Mails/MailDraft.cs +++ b/Source/Game/Mails/MailDraft.cs @@ -60,7 +60,7 @@ namespace Game.Mails if (m_mailTemplateId == 123) m_money = 1000000; - Loot mailLoot = new(null, ObjectGuid.Empty, LootType.None, LootMethod.FreeForAll); + Loot mailLoot = new(null, ObjectGuid.Empty, LootType.None, null); // can be empty mailLoot.FillLoot(m_mailTemplateId, LootStorage.Mail, receiver, true, true, LootModes.Default, ItemContext.None); diff --git a/Source/Game/Networking/Packets/LootPackets.cs b/Source/Game/Networking/Packets/LootPackets.cs index 6970dcc08..7db9c5182 100644 --- a/Source/Game/Networking/Packets/LootPackets.cs +++ b/Source/Game/Networking/Packets/LootPackets.cs @@ -188,20 +188,20 @@ namespace Game.Networking.Packets public ObjectGuid LootObj; } - class LootRoll : ClientPacket + class LootRollPacket : ClientPacket { - public LootRoll(WorldPacket packet) : base(packet) { } + public LootRollPacket(WorldPacket packet) : base(packet) { } public override void Read() { LootObj = _worldPacket.ReadPackedGuid(); LootListID = _worldPacket.ReadUInt8(); - RollType = (RollType)_worldPacket.ReadUInt8(); + RollType = (RollVote)_worldPacket.ReadUInt8(); } public ObjectGuid LootObj; public byte LootListID; - public RollType RollType; + public RollVote RollType; } class LootReleaseResponse : ServerPacket @@ -303,7 +303,7 @@ namespace Game.Networking.Packets public ObjectGuid LootObj; public ObjectGuid Player; public int Roll; // Roll value can be negative, it means that it is an "offspec" roll but only during roll selection broadcast (not when sending the result) - public RollType RollType; + public RollVote RollType; public LootItemData Item = new(); public bool Autopassed; // Triggers message |HlootHistory:%d|h[Loot]|h: You automatically passed on: %s because you cannot loot that item. } @@ -326,7 +326,7 @@ namespace Game.Networking.Packets public ObjectGuid LootObj; public ObjectGuid Winner; public int Roll; - public RollType RollType; + public RollVote RollType; public LootItemData Item = new(); public bool MainSpec; } diff --git a/Source/Game/Server/WorldSession.cs b/Source/Game/Server/WorldSession.cs index fbdab6934..ce83c7d47 100644 --- a/Source/Game/Server/WorldSession.cs +++ b/Source/Game/Server/WorldSession.cs @@ -193,7 +193,6 @@ namespace Game if (group != null) { group.SendUpdate(); - group.ResetMaxEnchantingLevel(); if (group.GetLeaderGUID() == _player.GetGUID()) group.StartLeaderOfflineTimer(); }