Core/Loot: Implemented dungeon encounter personal loot
Port From (https://github.com/TrinityCore/TrinityCore/commit/010e6f7f49744b16e3ecececb7d9605f8b8db4d5)
This commit is contained in:
@@ -1749,17 +1749,34 @@ namespace Game.Entities
|
||||
{
|
||||
if (info.Chest.chestPersonalLoot != 0)
|
||||
{
|
||||
Loot personalLoot = new(GetMap(), GetGUID(), LootType.Chest, null);
|
||||
m_personalLoot[player.GetGUID()] = personalLoot;
|
||||
|
||||
personalLoot.SetDungeonEncounterId(info.Chest.DungeonEncounter);
|
||||
personalLoot.FillLoot(info.Chest.chestPersonalLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext());
|
||||
|
||||
if (GetLootMode() > 0)
|
||||
GameObjectTemplateAddon addon = GetTemplateAddon();
|
||||
if (info.Chest.DungeonEncounter != 0)
|
||||
{
|
||||
GameObjectTemplateAddon addon = GetTemplateAddon();
|
||||
if (addon != null)
|
||||
personalLoot.GenerateMoneyLoot(addon.Mingold, addon.Maxgold);
|
||||
List<Player> tappers = new();
|
||||
foreach (ObjectGuid tapperGuid in GetTapList())
|
||||
{
|
||||
Player tapper = Global.ObjAccessor.GetPlayer(this, tapperGuid);
|
||||
if (tapper != null)
|
||||
tappers.Add(tapper);
|
||||
}
|
||||
|
||||
if (tappers.Empty())
|
||||
tappers.Add(player);
|
||||
|
||||
m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(info.Chest.DungeonEncounter, info.Chest.chestPersonalLoot,
|
||||
LootStorage.Gameobject, LootType.Chest, this, addon != null ? addon.Mingold : 0, addon != null ? addon.Maxgold : 0,
|
||||
(ushort)GetLootMode(), GetMap().GetDifficultyLootItemContext(), tappers);
|
||||
}
|
||||
else
|
||||
{
|
||||
Loot loot = new(GetMap(), GetGUID(), LootType.Chest, null);
|
||||
m_personalLoot[player.GetGUID()] = loot;
|
||||
|
||||
loot.SetDungeonEncounterId(info.Chest.DungeonEncounter);
|
||||
loot.FillLoot(info.Chest.chestPersonalLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext());
|
||||
|
||||
if (GetLootMode() > 0 && addon != null)
|
||||
loot.GenerateMoneyLoot(addon.Mingold, addon.Maxgold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,8 @@ namespace Game.Entities
|
||||
|
||||
public bool IsWeapon() { return GetClass() == ItemClass.Weapon; }
|
||||
|
||||
public bool IsArmor() { return GetClass() == ItemClass.Armor; }
|
||||
|
||||
public bool IsRangedWeapon()
|
||||
{
|
||||
return IsWeapon() && (GetSubClass() == (uint)ItemSubClassWeapon.Bow ||
|
||||
|
||||
@@ -2959,14 +2959,17 @@ namespace Game.Entities
|
||||
}
|
||||
}
|
||||
|
||||
public InventoryResult CanRollForItemInLFG(ItemTemplate proto, Map map)
|
||||
public InventoryResult CanRollNeedForItem(ItemTemplate proto, Map map, bool restrictOnlyLfg)
|
||||
{
|
||||
if (!GetGroup() || !GetGroup().IsLFGGroup())
|
||||
return InventoryResult.Ok; // not in LFG group
|
||||
if (restrictOnlyLfg)
|
||||
{
|
||||
if (!GetGroup() || !GetGroup().IsLFGGroup())
|
||||
return InventoryResult.Ok; // not in LFG group
|
||||
|
||||
// check if looted object is inside the lfg dungeon
|
||||
if (!Global.LFGMgr.InLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID()))
|
||||
return InventoryResult.Ok;
|
||||
// check if looted object is inside the lfg dungeon
|
||||
if (!Global.LFGMgr.InLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID()))
|
||||
return InventoryResult.Ok;
|
||||
}
|
||||
|
||||
if (proto == null)
|
||||
return InventoryResult.ItemNotFound;
|
||||
@@ -2986,41 +2989,14 @@ namespace Game.Entities
|
||||
return InventoryResult.CantEquipSkill;
|
||||
}
|
||||
|
||||
Class _class = GetClass();
|
||||
if (proto.GetClass() == ItemClass.Weapon && GetSkillValue(proto.GetSkill()) == 0)
|
||||
return InventoryResult.ProficiencyNeeded;
|
||||
|
||||
if (proto.GetClass() == ItemClass.Armor && proto.GetSubClass() > (uint)ItemSubClassArmor.Miscellaneous
|
||||
&& proto.GetSubClass() < (uint)ItemSubClassArmor.Cosmetic && proto.GetInventoryType() != InventoryType.Cloak)
|
||||
if (proto.GetClass() == ItemClass.Armor && proto.GetInventoryType() != InventoryType.Cloak)
|
||||
{
|
||||
if (_class == Class.Warrior || _class == Class.Paladin || _class == Class.Deathknight)
|
||||
{
|
||||
if (GetLevel() < 40)
|
||||
{
|
||||
if (proto.GetSubClass() != (uint)ItemSubClassArmor.Mail)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
}
|
||||
else if (proto.GetSubClass() != (uint)ItemSubClassArmor.Plate)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
}
|
||||
else if (_class == Class.Hunter || _class == Class.Shaman)
|
||||
{
|
||||
if (GetLevel() < 40)
|
||||
{
|
||||
if (proto.GetSubClass() != (uint)ItemSubClassArmor.Leather)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
}
|
||||
else if (proto.GetSubClass() != (uint)ItemSubClassArmor.Mail)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
}
|
||||
|
||||
if (_class == Class.Rogue || _class == Class.Druid)
|
||||
if (proto.GetSubClass() != (uint)ItemSubClassArmor.Leather)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
|
||||
if (_class == Class.Mage || _class == Class.Priest || _class == Class.Warlock)
|
||||
if (proto.GetSubClass() != (uint)ItemSubClassArmor.Cloth)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
ChrClassesRecord classesEntry = CliDB.ChrClassesStorage.LookupByKey(GetClass());
|
||||
if ((classesEntry.ArmorTypeMask & 1 << (int)proto.GetSubClass()) == 0)
|
||||
return InventoryResult.ClientLockedOut;
|
||||
}
|
||||
|
||||
return InventoryResult.Ok;
|
||||
|
||||
@@ -3141,8 +3141,7 @@ namespace Game.Entities
|
||||
|
||||
switch (loot.GetLootMethod())
|
||||
{
|
||||
case LootMethod.PersonalLoot:// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot)
|
||||
return false;
|
||||
case LootMethod.PersonalLoot:
|
||||
case LootMethod.FreeForAll:
|
||||
return true;
|
||||
case LootMethod.RoundRobin:
|
||||
|
||||
@@ -704,9 +704,9 @@ namespace Game.Entities
|
||||
|
||||
Creature creature = victim.ToCreature();
|
||||
|
||||
bool isRewardAllowed = true;
|
||||
bool isRewardAllowed = attacker != victim;
|
||||
if (creature != null)
|
||||
isRewardAllowed = !creature.GetTapList().Empty();
|
||||
isRewardAllowed = isRewardAllowed && !creature.GetTapList().Empty();
|
||||
|
||||
List<Player> tappers = new();
|
||||
if (isRewardAllowed && creature)
|
||||
@@ -727,11 +727,6 @@ namespace Game.Entities
|
||||
// call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop)
|
||||
if (isRewardAllowed)
|
||||
{
|
||||
PartyKillLog partyKillLog = new();
|
||||
partyKillLog.Player = player.GetGUID();
|
||||
partyKillLog.Victim = victim.GetGUID();
|
||||
partyKillLog.Write();
|
||||
|
||||
HashSet<Group> groups = new();
|
||||
foreach (Player tapper in tappers)
|
||||
{
|
||||
@@ -740,6 +735,11 @@ namespace Game.Entities
|
||||
{
|
||||
if (groups.Add(tapperGroup))
|
||||
{
|
||||
PartyKillLog partyKillLog = new();
|
||||
partyKillLog.Player = player && tapperGroup.IsMember(player.GetGUID()) ? player.GetGUID() : tapper.GetGUID();
|
||||
partyKillLog.Victim = victim.GetGUID();
|
||||
partyKillLog.Write();
|
||||
|
||||
tapperGroup.BroadcastPacket(partyKillLog, tapperGroup.GetMemberGroup(tapper.GetGUID()) != 0);
|
||||
|
||||
if (creature)
|
||||
@@ -747,7 +747,12 @@ namespace Game.Entities
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PartyKillLog partyKillLog = new();
|
||||
partyKillLog.Player = tapper.GetGUID();
|
||||
partyKillLog.Victim = victim.GetGUID();
|
||||
tapper.SendPacket(partyKillLog);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate loot before updating looter
|
||||
@@ -763,30 +768,33 @@ namespace Game.Entities
|
||||
Group group = !groups.Empty() ? groups.First() : null;
|
||||
Player looter = group ? Global.ObjAccessor.GetPlayer(creature, group.GetLooterGuid()) : tappers[0];
|
||||
|
||||
Loot loot = new(creature.GetMap(), creature.GetGUID(), LootType.Corpse, dungeonEncounter != null ? group : null);
|
||||
|
||||
if (dungeonEncounter != null)
|
||||
loot.SetDungeonEncounterId(dungeonEncounter.Id);
|
||||
|
||||
uint lootid = creature.GetCreatureTemplate().LootId;
|
||||
if (lootid != 0)
|
||||
loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext());
|
||||
|
||||
if (creature.GetLootMode() > 0)
|
||||
loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold);
|
||||
|
||||
if (group)
|
||||
loot.NotifyLootList(creature.GetMap());
|
||||
|
||||
if (dungeonEncounter != null || groups.Empty())
|
||||
creature._loot = loot; // TODO: personal boss loot
|
||||
{
|
||||
creature.m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(dungeonEncounter.Id, creature.GetCreatureTemplate().LootId,
|
||||
LootStorage.Creature, LootType.Corpse, creature, creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold,
|
||||
(ushort)creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext(), tappers);
|
||||
}
|
||||
else
|
||||
{
|
||||
Loot loot = new(creature.GetMap(), creature.GetGUID(), LootType.Corpse, dungeonEncounter != null ? group : null);
|
||||
|
||||
uint lootid = creature.GetCreatureTemplate().LootId;
|
||||
if (lootid != 0)
|
||||
loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext());
|
||||
|
||||
if (creature.GetLootMode() > 0)
|
||||
loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold);
|
||||
|
||||
if (group)
|
||||
loot.NotifyLootList(creature.GetMap());
|
||||
|
||||
creature.m_personalLoot[looter.GetGUID()] = loot; // trash mob loot is personal, generated with round robin rules
|
||||
|
||||
// Update round robin looter only if the creature had loot
|
||||
if (!loot.IsLooted())
|
||||
foreach (Group tapperGroup in groups)
|
||||
tapperGroup.UpdateLooterGuid(creature);
|
||||
// Update round robin looter only if the creature had loot
|
||||
if (!loot.IsLooted())
|
||||
foreach (Group tapperGroup in groups)
|
||||
tapperGroup.UpdateLooterGuid(creature);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Game.Groups
|
||||
m_dungeonDifficulty = Difficulty.Normal;
|
||||
m_raidDifficulty = Difficulty.NormalRaid;
|
||||
m_legacyRaidDifficulty = Difficulty.Raid10N;
|
||||
m_lootMethod = LootMethod.FreeForAll;
|
||||
m_lootMethod = LootMethod.PersonalLoot;
|
||||
m_lootThreshold = ItemQuality.Uncommon;
|
||||
}
|
||||
|
||||
@@ -120,9 +120,6 @@ namespace Game.Groups
|
||||
if (m_groupFlags.HasAnyFlag(GroupFlags.Raid))
|
||||
_initRaidSubGroupsCounter();
|
||||
|
||||
if (!IsLFGGroup())
|
||||
m_lootMethod = LootMethod.GroupLoot;
|
||||
|
||||
m_lootThreshold = ItemQuality.Uncommon;
|
||||
m_looterGuid = leaderGuid;
|
||||
|
||||
@@ -246,7 +243,7 @@ namespace Game.Groups
|
||||
{
|
||||
m_groupFlags = (m_groupFlags | GroupFlags.Lfg | GroupFlags.LfgRestricted);
|
||||
m_groupCategory = GroupCategory.Instance;
|
||||
m_lootMethod = LootMethod.GroupLoot;
|
||||
m_lootMethod = LootMethod.PersonalLoot;
|
||||
if (!IsBGGroup() && !IsBFGroup())
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_TYPE);
|
||||
|
||||
@@ -350,11 +350,12 @@ namespace Game
|
||||
[WorldPacketHandler(ClientOpcodes.SetLootMethod)]
|
||||
void HandleSetLootMethod(SetLootMethod packet)
|
||||
{
|
||||
// not allowed to change
|
||||
/*
|
||||
Group group = GetPlayer().GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
return;
|
||||
|
||||
/** error handling **/
|
||||
if (!group.IsLeader(GetPlayer().GetGUID()))
|
||||
return;
|
||||
|
||||
@@ -383,6 +384,7 @@ namespace Game
|
||||
group.SetMasterLooterGuid(packet.LootMasterGUID);
|
||||
group.SetLootThreshold(packet.LootThreshold);
|
||||
group.SendUpdate();
|
||||
*/
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.MinimapPing)]
|
||||
|
||||
+19
-16
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user