Core/Loot: Implemented dungeon encounter personal loot

Port From (https://github.com/TrinityCore/TrinityCore/commit/010e6f7f49744b16e3ecececb7d9605f8b8db4d5)
This commit is contained in:
hondacrx
2022-10-26 01:36:15 -04:00
parent 32960df381
commit 744e9c1c19
9 changed files with 314 additions and 115 deletions
+19 -16
View File
@@ -52,6 +52,11 @@ namespace Game.Loots
/// <param name="loot"></param>
/// <returns></returns>
public bool AllowedForPlayer(Player player, Loot loot)
{
return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
}
public static bool AllowedForPlayer(Player player, Loot loot, uint itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, List<Condition> conditions)
{
// DB conditions check
if (!Global.ConditionMgr.IsObjectMeetToConditions(player, conditions))
@@ -69,7 +74,7 @@ namespace Game.Loots
return false;
// Master looter can see all items even if the character can't loot them
if (loot.GetLootMethod() == LootMethod.MasterLoot && follow_loot_rules && player.GetGroup() != null && player.GetGroup().GetMasterLooterGuid() == player.GetGUID())
if (loot != null && loot.GetLootMethod() == LootMethod.MasterLoot && follow_loot_rules && loot.GetLootMasterGUID() == player.GetGUID())
return true;
// Don't allow loot for players without profession or those who already know the recipe
@@ -88,24 +93,20 @@ namespace Game.Loots
}
}
// Don't allow to loot soulbound recipes that the player has already learned
if (pProto.GetClass() == ItemClass.Recipe && pProto.GetBonding() == ItemBondingType.OnAcquire)
{
foreach (var itemEffect in pProto.Effects)
{
if (itemEffect.TriggerType != ItemSpelltriggerType.OnLearn)
continue;
if (player.HasSpell((uint)itemEffect.SpellID))
return false;
}
}
// check quest requirements
if (!pProto.FlagsCu.HasAnyFlag(ItemFlagsCustom.IgnoreQuestStatus)
&& ((needs_quest || (pProto.GetStartQuest() != 0 && player.GetQuestStatus(pProto.GetStartQuest()) != QuestStatus.None)) && !player.HasQuestForItem(itemid)))
return false;
if (strictUsabilityCheck)
{
if ((pProto.IsWeapon() || pProto.IsArmor()) && !pProto.IsUsableByLootSpecialization(player, true))
return false;
if (player.CanRollNeedForItem(pProto, null, false) != InventoryResult.Ok)
return false;
}
return true;
}
@@ -286,7 +287,7 @@ namespace Game.Loots
startLootRoll.Method = m_loot.GetLootMethod();
startLootRoll.ValidRolls = m_voteMask;
// In NEED_BEFORE_GREED need disabled for non-usable item for player
if (m_loot.GetLootMethod() == LootMethod.NeedBeforeGreed && player.CanRollForItemInLFG(itemTemplate, m_map) != InventoryResult.Ok)
if (m_loot.GetLootMethod() == LootMethod.NeedBeforeGreed && player.CanRollNeedForItem(itemTemplate, m_map, true) != InventoryResult.Ok)
startLootRoll.ValidRolls &= ~RollMask.Need;
FillPacket(startLootRoll.Item);
@@ -825,7 +826,7 @@ namespace Game.Loots
_rolls.Remove(pair.Key);
}
void FillNotNormalLootFor(Player player)
public void FillNotNormalLootFor(Player player)
{
ObjectGuid plguid = player.GetGUID();
_allowedLooters.Add(plguid);
@@ -1079,6 +1080,8 @@ namespace Game.Loots
public ObjectGuid GetGUID() { return _guid; }
public ObjectGuid GetOwnerGUID() { return _owner; }
public ItemContext GetItemContext() { return _itemContext; }
public void SetItemContext(ItemContext context) { _itemContext = context; }
public LootMethod GetLootMethod() { return _lootMethod; }
public ObjectGuid GetLootMasterGUID() { return _lootMaster; }
+211 -16
View File
@@ -65,6 +65,42 @@ namespace Game.Loots
LoadLootTemplates_Reference();
}
public static Dictionary<ObjectGuid, Loot> GenerateDungeonEncounterPersonalLoot(uint dungeonEncounterId, uint lootId, LootStore store,
LootType type, WorldObject lootOwner, uint minMoney, uint maxMoney, ushort lootMode, ItemContext context, List<Player> tappers)
{
Dictionary<Player, Loot> tempLoot = new();
foreach (Player tapper in tappers)
{
if (tapper.IsLockedToDungeonEncounter(dungeonEncounterId))
continue;
Loot loot = new(lootOwner.GetMap(), lootOwner.GetGUID(), type, null);
loot.SetItemContext(context);
loot.SetDungeonEncounterId(dungeonEncounterId);
loot.GenerateMoneyLoot(minMoney, maxMoney);
tempLoot[tapper] = loot;
}
LootTemplate tab = store.GetLootFor(lootId);
if (tab != null)
tab.ProcessPersonalLoot(tempLoot, store.IsRatesAllowed(), lootMode);
Dictionary<ObjectGuid, Loot> personalLoot = new();
foreach (var (looter, loot) in tempLoot)
{
loot.FillNotNormalLootFor(looter);
if (loot.IsLooted())
continue;
personalLoot[looter.GetGUID()] = loot;
}
return personalLoot;
}
public static void LoadLootTemplates_Creature()
{
Log.outInfo(LogFilter.ServerLoading, "Loading creature loot templates...");
@@ -719,7 +755,7 @@ namespace Game.Loots
Entries.Add(item);
}
public void Process(Loot loot, bool rate, ushort lootMode, byte groupId)
public void Process(Loot loot, bool rate, ushort lootMode, byte groupId, Player personalLooter = null)
{
if (groupId != 0) // Group reference uses own processing of the group
{
@@ -729,7 +765,7 @@ namespace Game.Loots
if (Groups[groupId - 1] == null)
return;
Groups[groupId - 1].Process(loot, lootMode);
Groups[groupId - 1].Process(loot, lootMode, personalLooter);
return;
}
@@ -750,19 +786,156 @@ namespace Game.Loots
uint maxcount = (uint)(item.maxcount * WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferencedAmount));
for (uint loop = 0; loop < maxcount; ++loop) // Ref multiplicator
Referenced.Process(loot, rate, lootMode, item.groupid);
Referenced.Process(loot, rate, lootMode, item.groupid, personalLooter);
}
else
{
// Plain entries (not a reference, not grouped)
// Chance is already checked, just add
if (personalLooter == null
|| LootItem.AllowedForPlayer(personalLooter, null, item.itemid, item.needs_quest,
!item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
true, item.conditions))
loot.AddItem(item);
}
else // Plain entries (not a reference, not grouped)
loot.AddItem(item); // Chance is already checked, just add
}
// Now processing groups
foreach (var group in Groups.Values)
{
if (group != null)
group.Process(loot, lootMode);
group.Process(loot, lootMode, personalLooter);
}
}
public void ProcessPersonalLoot(Dictionary<Player, Loot> personalLoot, bool rate, ushort lootMode)
{
List<Player> getLootersForItem(Func<Player, bool> predicate)
{
List<Player> lootersForItem = new();
foreach (var (looter, loot) in personalLoot)
{
if (predicate(looter))
lootersForItem.Add(looter);
}
return lootersForItem;
}
// Rolling non-grouped items
foreach (LootStoreItem item in Entries)
{
if ((item.lootmode & lootMode) == 0) // Do not add if mode mismatch
continue;
if (!item.Roll(rate))
continue; // Bad luck for the entry
if (item.reference > 0) // References processing
{
LootTemplate referenced = LootStorage.Reference.GetLootFor(item.reference);
if (referenced == null)
continue; // Error message already printed at loading stage
uint maxcount = (uint)((float)item.maxcount * WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferencedAmount));
List<Player> gotLoot = new();
for (uint loop = 0; loop < maxcount; ++loop) // Ref multiplicator
{
var lootersForItem = getLootersForItem(looter => referenced.HasDropForPlayer(looter, item.groupid, true));
// nobody can loot this, skip it
if (lootersForItem.Empty())
break;
var newEnd = lootersForItem.RemoveAll(looter => gotLoot.Contains(looter));
if (lootersForItem.Count == newEnd)
{
// if we run out of looters this means that there are more items dropped than players
// start a new cycle adding one item to everyone
gotLoot.Clear();
}
else
lootersForItem.RemoveRange(newEnd, lootersForItem.Count - newEnd);
Player chosenLooter = lootersForItem.SelectRandom();
referenced.Process(personalLoot[chosenLooter], rate, lootMode, item.groupid, chosenLooter);
gotLoot.Add(chosenLooter);
}
}
else
{
// Plain entries (not a reference, not grouped)
// Chance is already checked, just add
var lootersForItem = getLootersForItem(looter =>
{
return LootItem.AllowedForPlayer(looter, null, item.itemid, item.needs_quest,
!item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
true, item.conditions);
});
if (!lootersForItem.Empty())
{
Player chosenLooter = lootersForItem.SelectRandom();
personalLoot[chosenLooter].AddItem(item);
}
}
}
// Now processing groups
foreach (LootGroup group in Groups.Values)
{
if (group != null)
{
var lootersForGroup = getLootersForItem(looter => group.HasDropForPlayer(looter, true));
if (!lootersForGroup.Empty())
{
Player chosenLooter = lootersForGroup.SelectRandom();
group.Process(personalLoot[chosenLooter], lootMode);
}
}
}
}
// True if template includes at least 1 drop for the player
bool HasDropForPlayer(Player player, byte groupId, bool strictUsabilityCheck)
{
if (groupId != 0) // Group reference
{
if (groupId > Groups.Count)
return false; // Error message already printed at loading stage
if (Groups[groupId - 1] == null)
return false;
return Groups[groupId - 1].HasDropForPlayer(player, strictUsabilityCheck);
}
// Checking non-grouped entries
foreach (LootStoreItem lootStoreItem in Entries)
{
if (lootStoreItem.reference > 0) // References processing
{
LootTemplate referenced = LootStorage.Reference.GetLootFor(lootStoreItem.reference);
if (referenced == null)
continue; // Error message already printed at loading stage
if (referenced.HasDropForPlayer(player, lootStoreItem.groupid, strictUsabilityCheck))
return true;
}
else if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions))
return true; // active quest drop found
}
// Now checking groups
foreach (LootGroup group in Groups.Values)
if (group != null && group.HasDropForPlayer(player, strictUsabilityCheck))
return true;
return false;
}
public void CopyConditions(List<Condition> conditions)
{
foreach (var i in Entries)
@@ -859,7 +1032,7 @@ namespace Game.Loots
{
// Checking group chances
foreach (var group in Groups)
group.Value.Verify(lootstore, id, (byte)(group.Key + 1));
group.Value.Verify(lootstore, id, (byte)(group.Key + 1));
// @todo References validity checks
}
@@ -983,9 +1156,9 @@ namespace Game.Loots
return false;
}
public void Process(Loot loot, ushort lootMode)
public void Process(Loot loot, ushort lootMode, Player personalLooter = null)
{
LootStoreItem item = Roll(loot, lootMode);
LootStoreItem item = Roll(lootMode, personalLooter);
if (item != null)
loot.AddItem(item);
}
@@ -1057,10 +1230,10 @@ namespace Game.Loots
LootStoreItemList ExplicitlyChanced = new(); // Entries with chances defined in DB
LootStoreItemList EqualChanced = new(); // Zero chances - every entry takes the same chance
LootStoreItem Roll(Loot loot, ushort lootMode)
LootStoreItem Roll(ushort lootMode, Player personalLooter = null)
{
LootStoreItemList possibleLoot = ExplicitlyChanced;
possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check);
possibleLoot.RemoveAll(new LootGroupInvalidSelector(lootMode, personalLooter).Check);
if (!possibleLoot.Empty()) // First explicitly chanced entries are checked
{
@@ -1078,32 +1251,54 @@ namespace Game.Loots
}
possibleLoot = EqualChanced;
possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check);
possibleLoot.RemoveAll(new LootGroupInvalidSelector(lootMode, personalLooter).Check);
if (!possibleLoot.Empty()) // If nothing selected yet - an item is taken from equal-chanced part
return possibleLoot.SelectRandom();
return null; // Empty drop from the group
}
public bool HasDropForPlayer(Player player, bool strictUsabilityCheck)
{
foreach (LootStoreItem lootStoreItem in ExplicitlyChanced)
if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions))
return true;
foreach (LootStoreItem lootStoreItem in EqualChanced)
if (LootItem.AllowedForPlayer(player, null, lootStoreItem.itemid, lootStoreItem.needs_quest,
!lootStoreItem.needs_quest || Global.ObjectMgr.GetItemTemplate(lootStoreItem.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
strictUsabilityCheck, lootStoreItem.conditions))
return true;
return false;
}
}
}
public struct LootGroupInvalidSelector
{
public LootGroupInvalidSelector(Loot loot, ushort lootMode)
public LootGroupInvalidSelector(ushort lootMode, Player personalLooter)
{
_loot = loot;
_lootMode = lootMode;
_personalLooter = personalLooter;
}
public bool Check(LootStoreItem item)
{
if (!Convert.ToBoolean(item.lootmode & _lootMode))
if ((item.lootmode & _lootMode) == 0)
return true;
if (_personalLooter && !LootItem.AllowedForPlayer(_personalLooter, null, item.itemid, item.needs_quest,
!item.needs_quest || Global.ObjectMgr.GetItemTemplate(item.itemid).HasFlag(ItemFlagsCustom.FollowLootRules),
true, item.conditions))
return true;
return false;
}
Loot _loot;
ushort _lootMode;
Player _personalLooter;
}
}