Core/Loot: Move loot rolls from Group to Loot
Port From (https://github.com/TrinityCore/TrinityCore/commit/3ef5079feeedfdafc9d3c1d9f865e96dbc77ecc8)
This commit is contained in:
+715
-98
@@ -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<ObjectGuid> GetAllowedLooters() { return allowedGUIDs; }
|
||||
|
||||
public uint itemid;
|
||||
public uint itemIndex;
|
||||
public uint LootListId;
|
||||
public uint randomBonusListId;
|
||||
public List<uint> BonusListIDs = new();
|
||||
public ItemContext context;
|
||||
@@ -145,35 +146,450 @@ namespace Game.Loots
|
||||
}
|
||||
}
|
||||
|
||||
public class LootValidatorRef : Reference<Loot, LootValidatorRef>
|
||||
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<Loot, LootValidatorRef>
|
||||
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<ObjectGuid, PlayerRollVote> 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<ObjectGuid, PlayerRollVote> 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<ObjectGuid, PlayerRollVote> 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<ObjectGuid, PlayerRollVote> 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<uint> 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<ItemPosCount> 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<NotNormalLootItem> 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<NotNormalLootItem> FillNonQuestNonFFAConditionalLoot(Player player, bool presentAtLooting)
|
||||
List<NotNormalLootItem> FillNonQuestNonFFAConditionalLoot(Player player)
|
||||
{
|
||||
List<NotNormalLootItem> 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<ObjectGuid, NotNormalLootItem> GetPlayerQuestItems() { return PlayerQuestItems; }
|
||||
public MultiMap<ObjectGuid, NotNormalLootItem> GetPlayerFFAItems() { return PlayerFFAItems; }
|
||||
public MultiMap<ObjectGuid, NotNormalLootItem> GetPlayerNonQuestNonFFAConditionalItems() { return PlayerNonQuestNonFFAConditionalItems; }
|
||||
@@ -859,14 +1475,15 @@ namespace Game.Loots
|
||||
MultiMap<ObjectGuid, NotNormalLootItem> PlayerFFAItems = new();
|
||||
MultiMap<ObjectGuid, NotNormalLootItem> 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<uint, LootRoll> _rolls = new(); // used if an item is under rolling
|
||||
ObjectGuid _lootMaster;
|
||||
List<ObjectGuid> _allowedLooters = new();
|
||||
bool _wasOpened; // true if at least one player received the loot content
|
||||
}
|
||||
|
||||
public class AELootResult
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Game.Loots
|
||||
LootItem lootItem = new();
|
||||
lootItem.itemid = result.Read<uint>(1);
|
||||
lootItem.count = result.Read<byte>(2);
|
||||
lootItem.itemIndex = result.Read<uint>(3);
|
||||
lootItem.LootListId = result.Read<uint>(3);
|
||||
lootItem.follow_loot_rules = result.Read<bool>(4);
|
||||
lootItem.freeforall = result.Read<bool>(5);
|
||||
lootItem.is_blocked = result.Read<bool>(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;
|
||||
|
||||
Reference in New Issue
Block a user